# -*- 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.'
)