# -*- coding: utf-8 -*-
from re import sub
from typing import List, Tuple, Union
from copy import deepcopy
import pandas as pd
[docs]
def determine_larger_box(
minx: Union[None, float],
miny: Union[None, float],
maxx: Union[None, float],
maxy: Union[None, float],
chip_box: tuple,
) -> Tuple[float, float, float, float]:
"""Return box which includes the two boxes.
Args:
minx (Union[None, float]): Minimum of x coordinate
miny (Union[None, float]): Minimum of y coordinate
maxx (Union[None, float]): Maximum of x coordinate
maxy (Union[None, float]): Maximum of y coordinate
chip_box (tuple): Second box in following format:
minx, miny, maxx, maxy
Returns:
Tuple[float, float, float, float]: The size: minx, miny, maxx, maxy of
box which includes both boxes.
"""
large_minx, large_miny, large_maxx, large_maxy = None, None, None, None
if minx and miny and maxx and maxy:
chip_minx, chip_miny, chip_maxx, chip_maxy = chip_box
large_minx = min(minx, chip_minx)
large_miny = min(miny, chip_miny)
large_maxx = max(maxx, chip_maxx)
large_maxy = max(maxy, chip_maxy)
else:
# First time getting chip size, so just use chip_box
large_minx, large_miny, large_maxx, large_maxy = chip_box
return large_minx, large_miny, large_maxx, large_maxy
[docs]
class BoundsForPathAndPolyTables:
"""Create class which can be used by multiple renderers. In particular, this class
assumes a LayerStack is being used within QDesign.
"""
def __init__(self, design: "MultiPlanar"):
self.design = design
self.chip_names_matched = None # bool
self.valid_chip_names = None # set of valid chip names from layer_stack
[docs]
def get_bounds_of_path_and_poly_tables(
self,
box_plus_buffer: bool,
qcomp_ids: List,
case: int,
x_buff: float,
y_buff: float,
) -> tuple[
tuple[float, float, float, float],
pd.DataFrame,
pd.DataFrame,
bool,
Union[None, set],
]:
"""Return bounds and concatenated geometry tables for selected components.
Args:
box_plus_buffer: If True, use selected components' bounding box plus
the supplied buffer; otherwise use the chip size.
qcomp_ids: Component ids to include (may be empty to include all).
case: Return code from ``QRenderer.get_unique_component_ids``.
x_buff: Buffer to add in x when ``box_plus_buffer`` is True.
y_buff: Buffer to add in y when ``box_plus_buffer`` is True.
Returns:
tuple: Bounding box (minx, miny, maxx, maxy).
pd.DataFrame: Path and poly tables concatenated for the selection.
pd.DataFrame: Path, poly, and junction tables concatenated.
bool: True if chip names match between design and layer stack.
set | None: Valid chip names if they match, otherwise None.
"""
box_for_xy_bounds = None
self.chip_names_matched = None
self.valid_chip_names = None
path_dataframe = self.design.qgeometry.tables["path"]
poly_dataframe = self.design.qgeometry.tables["poly"]
junction_dataframe = self.design.qgeometry.tables["junction"]
# NOTE:get_box_for_xy_bounds populates self.chip_names_matched and self.valid_chip_names
chip_minx, chip_miny, chip_maxx, chip_maxy = self.get_box_for_xy_bounds()
chip_bounds_xy = (chip_minx, chip_miny, chip_maxx, chip_maxy)
if box_plus_buffer:
# Based on component selection, determine the bounds for box_plus_buffer.
frames = None
frames_with_jj = None
path_and_poly_with_valid_comps = None
if case == 2: # One or more components not in QDesign.
self.design.logger.warning("One or more components not found.")
elif case == 1: # Render all components
frames = [path_dataframe, poly_dataframe]
frames_with_jj = [path_dataframe, poly_dataframe, junction_dataframe]
else: # Strict subset rendered.
mask_path = path_dataframe["component"].isin(qcomp_ids)
mask_poly = poly_dataframe["component"].isin(qcomp_ids)
mask_junction = junction_dataframe["component"].isin(qcomp_ids)
subset_path_df = path_dataframe[mask_path]
subset_poly_df = poly_dataframe[mask_poly]
subset_junction_df = junction_dataframe[mask_junction]
frames = [subset_path_df, subset_poly_df]
frames_with_jj = [subset_path_df, subset_poly_df, subset_junction_df]
# Concat the frames and then determine the total bounds of all the geometries.
# maybe, change name to package_cavity
path_and_poly_with_valid_comps = pd.concat(frames, ignore_index=True)
path_poly_and_junction_valid_comps = pd.concat(
frames_with_jj, ignore_index=True
)
minx, miny, maxx, maxy = list(
path_and_poly_with_valid_comps["geometry"].total_bounds
)
# minx, miny, maxx, maxy = list(
# pd.concat(frames, ignore_index=True)['geometry'].total_bounds)
# # Add the buffer, using options for renderer.
# x_buff = parse_entry(self._options["x_buffer_width_mm"])
# y_buff = parse_entry(self._options["y_buffer_width_mm"])
minx -= x_buff
miny -= y_buff
maxx += x_buff
maxy += y_buff
box_for_xy_bounds = (minx, miny, maxx, maxy)
safe_xy_bounds = self.ensure_component_box_smaller_than_chip_box_(
box_for_xy_bounds, chip_bounds_xy
)
return (
safe_xy_bounds,
path_and_poly_with_valid_comps,
path_poly_and_junction_valid_comps,
self.chip_names_matched,
self.valid_chip_names,
)
else: # Incorporate all the chip sizes.
frames = [path_dataframe, poly_dataframe]
frames_with_jj = [path_dataframe, poly_dataframe, junction_dataframe]
path_and_poly_with_valid_comps = pd.concat(frames, ignore_index=True)
path_poly_and_junction_valid_comps = pd.concat(
frames_with_jj, ignore_index=True
)
return (
chip_bounds_xy,
path_and_poly_with_valid_comps,
path_poly_and_junction_valid_comps,
self.chip_names_matched,
self.valid_chip_names,
)
[docs]
@classmethod
def ensure_component_box_smaller_than_chip_box_(
cls, box_for_xy_bounds: Tuple, chip_bounds_xy: Tuple
) -> Tuple:
"""If the box_plus_buffer is larger than the aggregate chip bounds from DesignPlanar,
use the chip bounds as the cutoff.
Args:
box_for_xy_bounds (Tuple): In xy plane, the bounding box for the components to render.
Box from QGeometry tables.
chip_bounds_xy (Tuple): In xy plane, the bounding box for aggregate chip size.
Box from MultiPlanar chip size.
Returns:
Tuple: In xy plane, the box_for_xy_bounds will be limited by the chip_bounds_xy
if box_for_xy_bounds is larger than chip_bounds_xy.
If chip_bounds_xy is None (bad input), no checking
will happen and box_for_xy_bounds will be returned.
"""
chip_minx, chip_miny, chip_maxx, chip_maxy = chip_bounds_xy
if (
chip_minx is None
or chip_miny is None
or chip_maxx is None
or chip_maxy is None
):
input_xy_box = deepcopy(box_for_xy_bounds)
return input_xy_box
box_minx, box_miny, box_maxx, box_maxy = box_for_xy_bounds
safe_xy_box = list()
# Keep the order of appends in this way. It should match (minx, miny, maxx, maxy)
# yapf: disable
safe_xy_box.append(chip_minx) if box_minx < chip_minx else safe_xy_box.append(box_minx)
safe_xy_box.append(chip_miny) if box_miny < chip_miny else safe_xy_box.append(box_miny)
safe_xy_box.append(chip_maxx) if box_maxx > chip_maxx else safe_xy_box.append(box_maxx)
safe_xy_box.append(chip_maxy) if box_maxy > chip_maxy else safe_xy_box.append(box_maxy)
# yapf: enable
return tuple(safe_xy_box)
[docs]
def get_box_for_xy_bounds(
self,
) -> Union[None, Union[Tuple[float, float, float, float], None]]:
"""Assuming the chip size is used from Multiplanar design, and list of chip_names
comes from layer_stack that will be used to determine the box size for simulation.
Returns:
Union[None, Union[Tuple[float, float, float, float], None]]:
None if not able to get the chip information
Tuple holds the box to use for simulation [minx, miny, maxx, maxy]
"""
self.chip_names_matched, self.valid_chip_names = (
self.are_all_chipnames_in_design()
)
minx, miny, maxx, maxy = None, None, None, None
if self.chip_names_matched:
# Using the chip size from Multiplanar design and z_coord from layer_stack, get the box
# for chip_name in self.chip_names_matched:
for chip_name in self.valid_chip_names:
if self.design.chips[chip_name]["size"]:
chip_box, return_code = self.get_x_y_for_chip(chip_name)
if return_code == 0: # All was found and good.
minx, miny, maxx, maxy = determine_larger_box(
minx, miny, maxx, maxy, chip_box
)
else:
self.chip_size_not_in_chipname_within_design(chip_name)
return minx, miny, maxx, maxy
[docs]
def are_all_chipnames_in_design(self) -> Tuple[bool, Union[set, None]]:
"""Using chip names in layer_stack information,
then check if the information is in MultiPlanar design.
Returns:
Tuple[bool, Union[set, None]]: bool if there is a key in design with same chip_name as in layer_stack
Union has either None if the names don't match,
or the set of chip_names that can be used.
"""
chip_set_from_design = set(self.design.chips.keys())
chip_set_from_layer_stack = self.design.ls.get_unique_chip_names()
if not chip_set_from_layer_stack.issubset(chip_set_from_design):
self.chip_names_not_in_design(
chip_set_from_layer_stack, chip_set_from_design
)
return False, None
return True, chip_set_from_layer_stack
[docs]
def get_x_y_for_chip(self, chip_name: str) -> Tuple[tuple, int]:
"""If the chip_name is in self.chips, along with entry for size
information then return a tuple=(minx, miny, maxx, maxy). Used for
subtraction while exporting design.
Args:
chip_name (str): Name of chip that you want the size of.
Returns:
Tuple[tuple, int]:
tuple: The exact placement on rectangle coordinate (minx, miny, maxx, maxy).
int: 0=all is good
1=chip_name not in self._chips
2=size information missing or no good
"""
x_y_location = tuple()
if chip_name in self.design.chips:
if "size" in self.design.chips[chip_name]:
size = self.design.parse_value(self.design.chips[chip_name]["size"])
if (
"center_x" in size
and "center_y" in size
and "size_x" in size
and "size_y" in size
):
if (
type(size.center_x) in [int, float]
and type(size.center_y) in [int, float]
and type(size.size_x) in [int, float]
and type(size.size_y) in [int, float]
):
x_y_location = (
size["center_x"] - (size["size_x"] / 2.0),
size["center_y"] - (size["size_y"] / 2.0),
size["center_x"] + (size["size_x"] / 2.0),
size["center_y"] + (size["size_y"] / 2.0),
)
return x_y_location, 0
self.design.logger.warning(
f'Size information within self.design.chips[{chip_name}]["size"]'
f" is NOT an int or float."
)
return x_y_location, 2
self.design.logger.warning(
"center_x or center_y or size_x or size_y "
f' NOT in self.design.chips[{chip_name}]["size"]'
)
return x_y_location, 2
self.design.logger.warning(
f"Information for size in NOT in self.design.chips[{chip_name}]"
' dict. Return "None" in tuple.'
)
return x_y_location, 2
self.design.logger.warning(
f'Chip name "{chip_name}" is not in self.design.chips dict. Return "None" in tuple.'
)
return x_y_location, 1
######### Warnings and Errors##################################################
[docs]
def chip_names_not_in_design(self, layer_stack_names: set, design_names: set):
"""
Tell user to check the chip name and data in design.
Args:
layer_stack_names (set): Chip names from layer_stack.
design_names (set): Chip names from design.
"""
self.design.logger.warning(
f"\nThe chip_names from layer_stack are not in design. "
f"\n The names in layer_stack:{layer_stack_names}."
f"\n The names in design:{design_names}."
)
[docs]
def chip_size_not_in_chipname_within_design(self, chip_name: str):
"""
Tell user to check the chip size data within design.
Args:
chip_name (str): Chip names from layer_stack.
"""
self.design.logger.error(
f"\nThe chip_name:{chip_name} within design. "
f"\n Update your QDesign or subclass to see confirm the size information is provided."
)