Source code for kqcircuits.util.gui_helper

# This code is part of KQCircuits
# Copyright (C) 2021 IQM Finland Oy
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with this program. If not, see
# https://www.gnu.org/licenses/gpl-3.0.html.
#
# The software distribution should follow IQM trademark policy for open-source software
# (meetiqm.com/iqm-open-source-trademark-policy). IQM welcomes contributions to the code.
# Please see our contribution agreements for individuals (meetiqm.com/iqm-individual-contributor-license-agreement)
# and organizations (meetiqm.com/iqm-organization-contributor-license-agreement).


import ast
import re

from kqcircuits.pya_resolver import pya
from kqcircuits.elements.waveguide_composite import Node, WaveguideComposite
from kqcircuits.util.library_helper import load_libraries, element_by_class_name


[docs] def get_nodes_near_position(top_cell, position, box_size=10, require_gui_editing_enabled=True): """Find all WaveguideComposite nodes near a specified position. Considers only waveguides that are a direct child of the specified ``top_cell``. Args: top_cell: cell in which to search for WaveguideComposite instances position: pya.DPoint position where to search for nodes box_size: capture distance in x,y away from ``position`` where the node can be require_gui_editing_enabled: if True, only instances with ``enable_gui_editing==True`` are considered. Returns: a list of tuples ``(instance, node, node_index)`` where ``instance`` is the WaveguideComposite Instance, ``node`` is the ``Node`` object that ``node_index`` is the index of ``node`` in the ``nodes`` parameter of the waveguide. """ box = pya.DBox(pya.DPoint(-box_size, -box_size), pya.DPoint(box_size, box_size)).moved(pya.DVector(position)) found_nodes = [] for inst in top_cell.each_inst(): if inst.is_pcell() and isinstance(inst.pcell_declaration(), WaveguideComposite): if (not require_gui_editing_enabled) or inst.pcell_parameter("enable_gui_editing"): dtrans = inst.dcplx_trans nodes = Node.nodes_from_string(inst.pcell_parameter("nodes")) for i, node in enumerate(nodes): node_position = dtrans * node.position if box.contains(node_position): found_nodes.append((inst, node, i)) return found_nodes
[docs] def node_to_text(node): """Convert ``Node`` object to text fields that can be used for GUI editing. The inverse of this function is ``node_from_text``. Args: node: Node to convert Returns: tuple of strings ``(x, y, element, inst_name, angle, length_before, length_increment, align, parameters)`` """ x = str(node.position.x) y = str(node.position.y) inst_name = node.inst_name if node.inst_name is not None else "" angle = str(node.angle) if node.angle is not None else "" length_before = str(node.length_before) if node.length_before is not None else "" length_increment = str(node.length_increment) if node.length_increment is not None else "" element = node.element.__name__ if node.element is not None else "" align = ",".join(node.align) if node.align is not None else "" parameters = "\n".join([f"{key}={repr(value)}" for key, value in node.params.items()]) return (x, y, element, inst_name, angle, length_before, length_increment, align, parameters)
[docs] def node_from_text(x, y, element, inst_name, angle, length_before, length_increment, align, parameters): """Create Node from text inputs. This is the inverse of ``node_to_text``. For all arguments except x and y, an empty string is treated as default value. Spaces are stripped from the inputs. For ``parameters``, the values will be parsed by ``ast.literal_eval``, which accepts most standard python literals. Args: x (str): x position, will be converted to float y (str): y position, will be converted to float element (str): class name of the element, must exist in the `kqcircuits.elements` namespace inst_name (str): instance name to use for the element angle (str): angle of the node, will be converted to float length_before (str): length before this node, will be converted to float length_increment (str): length increment produced by meander before this node, will be converted to float align (str): input and output refpoint to use for aligning the element, separated by a comma parameters (str): multiline string, where each line contains a parameter=value pair. Returns: Node Raises: ValueError: if an input value cannot be converted to the correct type. """ x = float(x) y = float(y) params = {} if inst_name != "": params["inst_name"] = inst_name.strip() if angle != "": params["angle"] = float(angle) if length_before != "": params["length_before"] = float(length_before) if length_increment != "": params["length_increment"] = float(length_increment) element = element.strip() if element == "": element = None if align != "": params["align"] = tuple(s.strip() for s in align.split(",")) param_lines = parameters.split("\n") for param_line in param_lines: param_line = param_line.strip() if param_line != "": m = re.fullmatch(r"([a-zA-Z0-9_]+)\s*=\s*(.*)", param_line) if not m: raise ValueError("Element parameters should be one key=value pair on each line") key = m.groups()[0] value = ast.literal_eval(m.groups()[1]) params[key] = value return Node.deserialize((x, y, element, params))
[docs] def replace_node(waveguide_instance, node_index, node): """Replace a Node in a WaveguideComposite by index. Args: waveguide_instance: Instance of the waveguide node_index: (int) index of the node to replace node: new Node """ nodes = Node.nodes_from_string(waveguide_instance.pcell_parameter("nodes")) nodes[node_index] = node waveguide_instance.change_pcell_parameter("nodes", [str(node) for node in nodes]) # Update gui_path and gui_path_shadow to reflect possible position changes in the node if waveguide_instance.pcell_parameter("enable_gui_editing"): path = pya.DPath([node.position for node in nodes], 1) # Round to database units since the KLayout partial tool also works in database units dbu = waveguide_instance.layout().dbu path = path.to_itype(dbu).to_dtype(dbu) waveguide_instance.change_pcell_parameter("gui_path", path) waveguide_instance.change_pcell_parameter("gui_path_shadow", path)
[docs] def get_all_node_elements(): """Returns all class names from PCells in the Element library which can be used in WaveguideComposite nodes Returns: List of class names (str) """ valid_elements = ["Airbridge"] layout = load_libraries(path="elements")["Element Library"].layout() for pcell_id in layout.pcell_ids(): pcell = layout.pcell_declaration(pcell_id) valid_elements.append(pcell.__class__.__name__) return valid_elements
[docs] def get_valid_node_elements(): """Returns a list of all element class names which would be, at least in principle, usable as ```Node.element```. An element is considered valid if it has at least two refpoint pairs ```X``` and ```X_corner```, for any value of x. Note: this function creates each element with default parameter values. Since this is generally slow and clumsy, this function is not used at startup. Instead, a curated list ```node_editor_valid_elements``` is kept in ```kqcircuits.defaults```. Returns: List of class names (str) """ layout = pya.Layout() all_elements = get_all_node_elements() valid_elements = [] for classname in all_elements: element = element_by_class_name(classname) if element is not None: cell, refp = element.create_with_refpoints(layout) refpoints_with_corner = [ name.rstrip("_corner") for name in refp if name.endswith("_corner") and name.rstrip("_corner") in refp ] layout.delete_cell(cell.cell_index()) if len(refpoints_with_corner) > 1: valid_elements.append(classname) else: valid_elements.append(classname) return valid_elements