pyedb 0.59.0__py3-none-any.whl → 0.61.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pyedb might be problematic. Click here for more details.
- pyedb/__init__.py +23 -1
- pyedb/common/__init__.py +21 -0
- pyedb/common/nets.py +22 -0
- pyedb/component_libraries/ansys_components.py +22 -0
- pyedb/configuration/__init__.py +21 -0
- pyedb/configuration/cfg_boundaries.py +1 -1
- pyedb/configuration/cfg_common.py +1 -1
- pyedb/configuration/cfg_components.py +36 -8
- pyedb/configuration/cfg_data.py +1 -1
- pyedb/configuration/cfg_general.py +1 -1
- pyedb/configuration/cfg_modeler.py +1 -1
- pyedb/configuration/cfg_nets.py +1 -1
- pyedb/configuration/cfg_operations.py +1 -1
- pyedb/configuration/cfg_package_definition.py +1 -1
- pyedb/configuration/cfg_padstacks.py +1 -1
- pyedb/configuration/cfg_pin_groups.py +1 -1
- pyedb/configuration/cfg_ports_sources.py +3 -2
- pyedb/configuration/cfg_s_parameter_models.py +1 -1
- pyedb/configuration/cfg_setup.py +5 -1
- pyedb/configuration/cfg_spice_models.py +1 -1
- pyedb/configuration/cfg_stackup.py +1 -1
- pyedb/configuration/cfg_terminals.py +22 -0
- pyedb/configuration/configuration.py +6 -5
- pyedb/dotnet/__init__.py +21 -0
- pyedb/dotnet/clr_module.py +22 -0
- pyedb/dotnet/database/Variables.py +1 -1
- pyedb/dotnet/database/__init__.py +22 -0
- pyedb/dotnet/database/cell/__init__.py +21 -0
- pyedb/dotnet/database/cell/connectable.py +1 -1
- pyedb/dotnet/database/cell/hierarchy/__init__.py +21 -0
- pyedb/dotnet/database/cell/hierarchy/component.py +9 -7
- pyedb/dotnet/database/cell/hierarchy/hierarchy_obj.py +1 -1
- pyedb/dotnet/database/cell/hierarchy/model.py +2 -29
- pyedb/dotnet/database/cell/hierarchy/netlist_model.py +1 -1
- pyedb/dotnet/database/cell/hierarchy/pin_pair_model.py +1 -1
- pyedb/dotnet/database/cell/hierarchy/s_parameter_model.py +11 -15
- pyedb/dotnet/database/cell/hierarchy/spice_model.py +14 -8
- pyedb/dotnet/database/cell/layout.py +5 -4
- pyedb/dotnet/database/cell/layout_obj.py +1 -1
- pyedb/dotnet/database/cell/primitive/__init__.py +22 -0
- pyedb/dotnet/database/cell/primitive/bondwire.py +1 -1
- pyedb/dotnet/database/cell/primitive/path.py +1 -1
- pyedb/dotnet/database/cell/primitive/primitive.py +1 -1
- pyedb/dotnet/database/cell/terminal/__init__.py +21 -0
- pyedb/dotnet/database/cell/terminal/bundle_terminal.py +1 -1
- pyedb/dotnet/database/cell/terminal/edge_terminal.py +1 -1
- pyedb/dotnet/database/cell/terminal/padstack_instance_terminal.py +1 -1
- pyedb/dotnet/database/cell/terminal/pingroup_terminal.py +1 -1
- pyedb/dotnet/database/cell/terminal/point_terminal.py +1 -1
- pyedb/dotnet/database/cell/terminal/terminal.py +7 -2
- pyedb/dotnet/database/cell/voltage_regulator.py +1 -1
- pyedb/dotnet/database/components.py +6 -2
- pyedb/dotnet/database/definition/__init__.py +21 -0
- pyedb/dotnet/database/definition/component_def.py +1 -1
- pyedb/dotnet/database/definition/component_model.py +1 -1
- pyedb/dotnet/database/definition/definition_obj.py +1 -1
- pyedb/dotnet/database/definition/definitions.py +1 -1
- pyedb/dotnet/database/definition/package_def.py +1 -1
- pyedb/dotnet/database/dotnet/__init__.py +21 -0
- pyedb/dotnet/database/dotnet/database.py +1 -1
- pyedb/dotnet/database/dotnet/primitive.py +1 -1
- pyedb/dotnet/database/edb_data/__init__.py +21 -0
- pyedb/dotnet/database/edb_data/control_file.py +1 -1
- pyedb/dotnet/database/edb_data/design_options.py +1 -1
- pyedb/dotnet/database/edb_data/edbvalue.py +1 -1
- pyedb/dotnet/database/edb_data/hfss_extent_info.py +1 -1
- pyedb/dotnet/database/edb_data/layer_data.py +1 -1
- pyedb/dotnet/database/edb_data/nets_data.py +1 -1
- pyedb/dotnet/database/edb_data/padstacks_data.py +6 -4
- pyedb/dotnet/database/edb_data/ports.py +1 -1
- pyedb/dotnet/database/edb_data/primitives_data.py +1 -1
- pyedb/dotnet/database/edb_data/raptor_x_simulation_setup_data.py +1 -1
- pyedb/dotnet/database/edb_data/simulation_configuration.py +1 -1
- pyedb/dotnet/database/edb_data/sources.py +1 -1
- pyedb/dotnet/database/edb_data/utilities.py +1 -1
- pyedb/dotnet/database/edb_data/variables.py +1 -1
- pyedb/dotnet/database/general.py +1 -1
- pyedb/dotnet/database/geometry/__init__.py +21 -0
- pyedb/dotnet/database/geometry/point_data.py +1 -1
- pyedb/dotnet/database/geometry/polygon_data.py +1 -1
- pyedb/dotnet/database/hfss.py +1 -1
- pyedb/dotnet/database/layout_obj_instance.py +1 -1
- pyedb/dotnet/database/layout_validation.py +1 -1
- pyedb/dotnet/database/materials.py +1 -1
- pyedb/dotnet/database/modeler.py +3 -2
- pyedb/dotnet/database/net_class.py +1 -1
- pyedb/dotnet/database/nets.py +1 -1
- pyedb/dotnet/database/padstack.py +188 -2
- pyedb/dotnet/database/sim_setup_data/__init__.py +22 -0
- pyedb/dotnet/database/sim_setup_data/data/__init__.py +22 -0
- pyedb/dotnet/database/sim_setup_data/data/adaptive_frequency_data.py +1 -1
- pyedb/dotnet/database/sim_setup_data/data/mesh_operation.py +1 -1
- pyedb/dotnet/database/sim_setup_data/data/settings.py +1 -1
- pyedb/dotnet/database/sim_setup_data/data/sim_setup_info.py +1 -1
- pyedb/dotnet/database/sim_setup_data/data/simulation_settings.py +1 -1
- pyedb/dotnet/database/sim_setup_data/data/siw_dc_ir_settings.py +1 -1
- pyedb/dotnet/database/sim_setup_data/data/sweep_data.py +1 -1
- pyedb/dotnet/database/sim_setup_data/io/__init__.py +21 -0
- pyedb/dotnet/database/sim_setup_data/io/siwave.py +1 -1
- pyedb/dotnet/database/siwave.py +1 -1
- pyedb/dotnet/database/stackup.py +1 -1
- pyedb/dotnet/database/utilities/__init__.py +22 -0
- pyedb/dotnet/database/utilities/heatsink.py +23 -0
- pyedb/dotnet/database/utilities/hfss_simulation_setup.py +1 -1
- pyedb/dotnet/database/utilities/obj_base.py +1 -1
- pyedb/dotnet/database/utilities/simulation_setup.py +1 -1
- pyedb/dotnet/database/utilities/siwave_cpa_simulation_setup.py +22 -0
- pyedb/dotnet/database/utilities/siwave_simulation_setup.py +22 -0
- pyedb/dotnet/database/utilities/value.py +1 -1
- pyedb/dotnet/edb.py +119 -123
- pyedb/edb_logger.py +1 -1
- pyedb/exceptions.py +22 -0
- pyedb/extensions/__init__.py +21 -0
- pyedb/extensions/create_cell_array.py +1 -1
- pyedb/extensions/via_design_backend.py +22 -0
- pyedb/generic/__init__.py +21 -0
- pyedb/generic/constants.py +1 -1
- pyedb/generic/data_handlers.py +22 -0
- pyedb/generic/design_types.py +1 -1
- pyedb/generic/filesystem.py +22 -0
- pyedb/generic/general_methods.py +22 -1
- pyedb/generic/grpc_warnings.py +22 -0
- pyedb/generic/plot.py +22 -0
- pyedb/generic/process.py +29 -2
- pyedb/generic/settings.py +1 -1
- pyedb/grpc/__init__.py +21 -0
- pyedb/grpc/database/__init__.py +21 -0
- pyedb/grpc/database/_typing.py +21 -0
- pyedb/grpc/database/components.py +9 -8
- pyedb/grpc/database/control_file.py +1 -1
- pyedb/grpc/database/definition/__init__.py +21 -0
- pyedb/grpc/database/definition/component_def.py +1 -1
- pyedb/grpc/database/definition/component_model.py +1 -1
- pyedb/grpc/database/definition/component_pin.py +1 -1
- pyedb/grpc/database/definition/materials.py +2 -2
- pyedb/grpc/database/definition/n_port_component_model.py +1 -1
- pyedb/grpc/database/definition/package_def.py +1 -1
- pyedb/grpc/database/definition/padstack_def.py +17 -10
- pyedb/grpc/database/definitions.py +1 -1
- pyedb/grpc/database/general.py +1 -1
- pyedb/grpc/database/geometry/__init__.py +21 -0
- pyedb/grpc/database/geometry/arc_data.py +1 -1
- pyedb/grpc/database/geometry/point_3d_data.py +1 -1
- pyedb/grpc/database/geometry/point_data.py +1 -1
- pyedb/grpc/database/geometry/polygon_data.py +1 -1
- pyedb/grpc/database/hfss.py +1 -1
- pyedb/grpc/database/hierarchy/__init__.py +21 -0
- pyedb/grpc/database/hierarchy/component.py +1 -1
- pyedb/grpc/database/hierarchy/model.py +1 -1
- pyedb/grpc/database/hierarchy/netlist_model.py +1 -1
- pyedb/grpc/database/hierarchy/pin_pair_model.py +1 -1
- pyedb/grpc/database/hierarchy/pingroup.py +1 -1
- pyedb/grpc/database/hierarchy/s_parameter_model.py +1 -1
- pyedb/grpc/database/hierarchy/spice_model.py +1 -1
- pyedb/grpc/database/layers/__init__.py +21 -0
- pyedb/grpc/database/layers/layer.py +22 -0
- pyedb/grpc/database/layers/stackup_layer.py +1 -1
- pyedb/grpc/database/layout/__init__.py +21 -0
- pyedb/grpc/database/layout/cell.py +1 -1
- pyedb/grpc/database/layout/layout.py +1 -1
- pyedb/grpc/database/layout/voltage_regulator.py +1 -1
- pyedb/grpc/database/layout_validation.py +1 -1
- pyedb/grpc/database/modeler.py +31 -9
- pyedb/grpc/database/net/__init__.py +21 -0
- pyedb/grpc/database/net/differential_pair.py +1 -1
- pyedb/grpc/database/net/extended_net.py +1 -1
- pyedb/grpc/database/net/net.py +1 -1
- pyedb/grpc/database/net/net_class.py +1 -1
- pyedb/grpc/database/nets.py +1 -1
- pyedb/grpc/database/padstacks.py +209 -9
- pyedb/grpc/database/ports/__init__.py +21 -0
- pyedb/grpc/database/ports/ports.py +1 -1
- pyedb/grpc/database/primitive/__init__.py +22 -0
- pyedb/grpc/database/primitive/bondwire.py +1 -1
- pyedb/grpc/database/primitive/circle.py +1 -1
- pyedb/grpc/database/primitive/padstack_instance.py +111 -16
- pyedb/grpc/database/primitive/path.py +1 -1
- pyedb/grpc/database/primitive/polygon.py +6 -4
- pyedb/grpc/database/primitive/primitive.py +1 -6
- pyedb/grpc/database/primitive/rectangle.py +1 -1
- pyedb/grpc/database/simulation_setup/__init__.py +21 -0
- pyedb/grpc/database/simulation_setup/adaptive_frequency.py +1 -1
- pyedb/grpc/database/simulation_setup/hfss_advanced_meshing_settings.py +1 -1
- pyedb/grpc/database/simulation_setup/hfss_advanced_settings.py +1 -1
- pyedb/grpc/database/simulation_setup/hfss_dcr_settings.py +1 -1
- pyedb/grpc/database/simulation_setup/hfss_general_settings.py +1 -1
- pyedb/grpc/database/simulation_setup/hfss_settings_options.py +1 -1
- pyedb/grpc/database/simulation_setup/hfss_simulation_settings.py +1 -1
- pyedb/grpc/database/simulation_setup/hfss_simulation_setup.py +1 -1
- pyedb/grpc/database/simulation_setup/hfss_solver_settings.py +1 -1
- pyedb/grpc/database/simulation_setup/mesh_operation.py +1 -1
- pyedb/grpc/database/simulation_setup/raptor_x_advanced_settings.py +1 -1
- pyedb/grpc/database/simulation_setup/raptor_x_general_settings.py +1 -1
- pyedb/grpc/database/simulation_setup/raptor_x_simulation_settings.py +1 -1
- pyedb/grpc/database/simulation_setup/raptor_x_simulation_setup.py +1 -1
- pyedb/grpc/database/simulation_setup/siwave_cpa_simulation_setup.py +22 -0
- pyedb/grpc/database/simulation_setup/siwave_dcir_simulation_setup.py +1 -1
- pyedb/grpc/database/simulation_setup/siwave_simulation_setup.py +1 -1
- pyedb/grpc/database/simulation_setup/sweep_data.py +1 -1
- pyedb/grpc/database/siwave.py +1 -1
- pyedb/grpc/database/source_excitations.py +1 -1
- pyedb/grpc/database/stackup.py +1 -1
- pyedb/grpc/database/terminal/__init__.py +21 -0
- pyedb/grpc/database/terminal/bundle_terminal.py +1 -1
- pyedb/grpc/database/terminal/edge_terminal.py +1 -1
- pyedb/grpc/database/terminal/padstack_instance_terminal.py +1 -1
- pyedb/grpc/database/terminal/pingroup_terminal.py +1 -1
- pyedb/grpc/database/terminal/point_terminal.py +1 -1
- pyedb/grpc/database/terminal/terminal.py +1 -1
- pyedb/grpc/database/utility/__init__.py +22 -0
- pyedb/grpc/database/utility/constants.py +1 -1
- pyedb/grpc/database/utility/heat_sink.py +1 -1
- pyedb/grpc/database/utility/hfss_extent_info.py +1 -1
- pyedb/grpc/database/utility/layout_statistics.py +1 -1
- pyedb/grpc/database/utility/rlc.py +1 -1
- pyedb/grpc/database/utility/sources.py +1 -1
- pyedb/grpc/database/utility/sweep_data_distribution.py +1 -1
- pyedb/grpc/database/utility/value.py +1 -1
- pyedb/grpc/database/utility/xml_control_file.py +1 -1
- pyedb/grpc/edb.py +230 -990
- pyedb/grpc/edb_init.py +1 -1
- pyedb/grpc/rpc_session.py +17 -4
- pyedb/ipc2581/__init__.py +21 -0
- pyedb/ipc2581/bom/__init__.py +21 -0
- pyedb/ipc2581/bom/bom.py +1 -1
- pyedb/ipc2581/bom/bom_item.py +1 -1
- pyedb/ipc2581/bom/characteristics.py +1 -1
- pyedb/ipc2581/bom/refdes.py +1 -1
- pyedb/ipc2581/content/__init__.py +21 -0
- pyedb/ipc2581/content/color.py +1 -1
- pyedb/ipc2581/content/content.py +1 -1
- pyedb/ipc2581/content/dictionary_color.py +1 -1
- pyedb/ipc2581/content/dictionary_fill.py +1 -1
- pyedb/ipc2581/content/dictionary_line.py +1 -1
- pyedb/ipc2581/content/entry_color.py +1 -1
- pyedb/ipc2581/content/entry_line.py +1 -1
- pyedb/ipc2581/content/fill.py +1 -1
- pyedb/ipc2581/content/layer_ref.py +1 -1
- pyedb/ipc2581/content/standard_geometries_dictionary.py +1 -1
- pyedb/ipc2581/ecad/__init__.py +21 -0
- pyedb/ipc2581/ecad/cad_data/__init__.py +21 -0
- pyedb/ipc2581/ecad/cad_data/assembly_drawing.py +1 -1
- pyedb/ipc2581/ecad/cad_data/cad_data.py +1 -1
- pyedb/ipc2581/ecad/cad_data/component.py +1 -1
- pyedb/ipc2581/ecad/cad_data/drill.py +1 -1
- pyedb/ipc2581/ecad/cad_data/feature.py +1 -1
- pyedb/ipc2581/ecad/cad_data/layer.py +1 -1
- pyedb/ipc2581/ecad/cad_data/layer_feature.py +1 -1
- pyedb/ipc2581/ecad/cad_data/logical_net.py +1 -1
- pyedb/ipc2581/ecad/cad_data/outline.py +1 -1
- pyedb/ipc2581/ecad/cad_data/package.py +1 -1
- pyedb/ipc2581/ecad/cad_data/padstack_def.py +1 -1
- pyedb/ipc2581/ecad/cad_data/padstack_hole_def.py +1 -1
- pyedb/ipc2581/ecad/cad_data/padstack_instance.py +1 -1
- pyedb/ipc2581/ecad/cad_data/padstack_pad_def.py +1 -1
- pyedb/ipc2581/ecad/cad_data/path.py +1 -1
- pyedb/ipc2581/ecad/cad_data/phy_net.py +1 -1
- pyedb/ipc2581/ecad/cad_data/pin.py +1 -1
- pyedb/ipc2581/ecad/cad_data/polygon.py +1 -1
- pyedb/ipc2581/ecad/cad_data/profile.py +1 -1
- pyedb/ipc2581/ecad/cad_data/stackup.py +1 -1
- pyedb/ipc2581/ecad/cad_data/stackup_group.py +1 -1
- pyedb/ipc2581/ecad/cad_data/stackup_layer.py +1 -1
- pyedb/ipc2581/ecad/cad_data/step.py +1 -1
- pyedb/ipc2581/ecad/cad_header.py +1 -1
- pyedb/ipc2581/ecad/ecad.py +1 -1
- pyedb/ipc2581/ecad/spec.py +1 -1
- pyedb/ipc2581/history_record.py +1 -1
- pyedb/ipc2581/ipc2581.py +1 -1
- pyedb/ipc2581/logistic_header.py +1 -1
- pyedb/libraries/common.py +1 -1
- pyedb/libraries/rf_libraries/base_functions.py +1 -1
- pyedb/libraries/rf_libraries/planar_antennas.py +1 -1
- pyedb/misc/__init__.py +21 -0
- pyedb/misc/aedtlib_personalib_install.py +1 -1
- pyedb/misc/decorators.py +22 -0
- pyedb/misc/downloads.py +1 -1
- pyedb/misc/misc.py +1 -1
- pyedb/misc/siw_feature_config/__init__.py +21 -0
- pyedb/misc/siw_feature_config/emc/__init__.py +21 -0
- pyedb/misc/siw_feature_config/emc/component_tags.py +22 -0
- pyedb/misc/siw_feature_config/emc/net_tags.py +22 -0
- pyedb/misc/siw_feature_config/emc/tag_library.py +22 -0
- pyedb/misc/siw_feature_config/emc/xml_generic.py +22 -0
- pyedb/misc/siw_feature_config/emc_rule_checker_settings.py +1 -1
- pyedb/misc/siw_feature_config/xtalk_scan/fd_xtalk_scan_config.py +1 -1
- pyedb/misc/siw_feature_config/xtalk_scan/impedance_scan_config.py +1 -1
- pyedb/misc/siw_feature_config/xtalk_scan/net.py +1 -1
- pyedb/misc/siw_feature_config/xtalk_scan/pins.py +1 -1
- pyedb/misc/siw_feature_config/xtalk_scan/scan_config.py +1 -1
- pyedb/misc/siw_feature_config/xtalk_scan/td_xtalk_config.py +1 -1
- pyedb/misc/utilities.py +1 -1
- pyedb/modeler/geometry_operators.py +22 -0
- pyedb/siwave.py +22 -0
- pyedb/siwave_core/__init__.py +21 -0
- pyedb/siwave_core/cpa/__init__.py +21 -0
- pyedb/siwave_core/cpa/simulation_setup_data_model.py +22 -0
- pyedb/siwave_core/icepak.py +1 -1
- pyedb/siwave_core/product_properties.py +23 -0
- pyedb/workflow.py +22 -0
- pyedb/workflows/__init__.py +21 -0
- pyedb/workflows/job_manager/__init__.py +21 -0
- pyedb/workflows/job_manager/backend/__init__.py +21 -0
- pyedb/workflows/job_manager/backend/job_manager_handler.py +910 -0
- pyedb/workflows/job_manager/backend/job_submission.py +1169 -0
- pyedb/workflows/job_manager/backend/service.py +1663 -0
- pyedb/workflows/job_manager/backend/start_service.py +86 -0
- pyedb/workflows/job_manager/backend/submit_job_on_scheduler.py +168 -0
- pyedb/workflows/job_manager/backend/submit_local_job.py +166 -0
- pyedb/workflows/sipi/hfss_auto_configuration.py +1 -1
- pyedb/workflows/utilities/__init__.py +21 -0
- pyedb/workflows/utilities/cutout.py +1428 -0
- pyedb/workflows/utilities/hfss_log_parser.py +446 -0
- {pyedb-0.59.0.dist-info → pyedb-0.61.0.dist-info}/METADATA +7 -4
- pyedb-0.61.0.dist-info/RECORD +318 -0
- {pyedb-0.59.0.dist-info → pyedb-0.61.0.dist-info}/licenses/LICENSE +7 -7
- pyedb-0.59.0.dist-info/RECORD +0 -306
- {pyedb-0.59.0.dist-info → pyedb-0.61.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,1428 @@
|
|
|
1
|
+
# Copyright (C) 2023 - 2025 ANSYS, Inc. and/or its affiliates.
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
#
|
|
4
|
+
#
|
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
# furnished to do so, subject to the following conditions:
|
|
11
|
+
#
|
|
12
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
# copies or substantial portions of the Software.
|
|
14
|
+
#
|
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
# SOFTWARE.
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
import os
|
|
25
|
+
import shutil
|
|
26
|
+
import time
|
|
27
|
+
from typing import List, Union
|
|
28
|
+
|
|
29
|
+
from ansys.edb.core.geometry.polygon_data import ExtentType as GrpcExtentType, PolygonData as GrpcPolygonData
|
|
30
|
+
|
|
31
|
+
from pyedb.dotnet.database.general import convert_py_list_to_net_list
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Cutout:
|
|
35
|
+
def __new__(self, edb):
|
|
36
|
+
if edb.grpc:
|
|
37
|
+
return GrpcCutout(edb)
|
|
38
|
+
else:
|
|
39
|
+
return DotNetCutout(edb)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class GrpcCutout:
|
|
43
|
+
"""Create a clipped (cut-out) EDB cell from an existing layout.
|
|
44
|
+
High-performance EDB cut-out utility.
|
|
45
|
+
|
|
46
|
+
Attributes
|
|
47
|
+
----------
|
|
48
|
+
signals : list[str]
|
|
49
|
+
List of signal net names to keep in the cut-out.
|
|
50
|
+
references : list[str]
|
|
51
|
+
List of reference net names to keep in the cut-out.
|
|
52
|
+
extent_type : str
|
|
53
|
+
Extent algorithm: ``ConvexHull`` (default), ``Conforming``, ``Bounding``.
|
|
54
|
+
expansion_size : float
|
|
55
|
+
Additional margin (metres) around the computed extent. Default 0.002.
|
|
56
|
+
use_round_corner : bool
|
|
57
|
+
Round the corners of the expanded extent. Default ``False``.
|
|
58
|
+
custom_extent : list[tuple[float, float]] | None
|
|
59
|
+
Optional closed polygon [(x1,y1), …] overriding any automatic extent.
|
|
60
|
+
custom_extent_units : str
|
|
61
|
+
Length unit for *custom_extent*. Default ``mm``.
|
|
62
|
+
include_voids_in_extents : bool
|
|
63
|
+
Include voids ≥ 5 % of the extent area when building the clip polygon.
|
|
64
|
+
open_cutout_at_end : bool
|
|
65
|
+
Open the resulting cut-out database in the active Edb object. Default ``True``.
|
|
66
|
+
use_pyaedt_cutout : bool
|
|
67
|
+
Use the PyAEDT based implementation instead of native EDB API. Default ``True``.
|
|
68
|
+
smart_cutout : bool
|
|
69
|
+
Automatically enlarge *expansion_size* until all ports have reference. Default ``False``.
|
|
70
|
+
expansion_factor : float
|
|
71
|
+
If > 0, compute initial *expansion_size* from trace-width/dielectric. Default 0.
|
|
72
|
+
maximum_iterations : int
|
|
73
|
+
Maximum attempts for *smart_cutout* before giving up. Default 10.
|
|
74
|
+
number_of_threads : int
|
|
75
|
+
Worker threads for polygon clipping and padstack cleaning. Default 1.
|
|
76
|
+
remove_single_pin_components : bool
|
|
77
|
+
Delete RLC components with only one pin after cut-out. Default ``False``.
|
|
78
|
+
preserve_components_with_model : bool
|
|
79
|
+
Keep every pin of components that carry a Spice/S-parameter model. Default ``False``.
|
|
80
|
+
check_terminals : bool
|
|
81
|
+
Grow extent until all reference terminals are inside the cut-out. Default ``False``.
|
|
82
|
+
include_pingroups : bool
|
|
83
|
+
Ensure complete pin-groups are included (needs *check_terminals*). Default ``False``.
|
|
84
|
+
simple_pad_check : bool
|
|
85
|
+
Use fast centre-point padstack check instead of bounding-box. Default ``True``.
|
|
86
|
+
keep_lines_as_path : bool
|
|
87
|
+
Keep clipped traces as Path objects (3D Layout only). Default ``False``.
|
|
88
|
+
extent_defeature : float
|
|
89
|
+
Defeature tolerance (metres) for conformal extent. Default 0.
|
|
90
|
+
include_partial_instances : bool
|
|
91
|
+
Include padstacks that only partially overlap the clip polygon. Default ``False``.
|
|
92
|
+
keep_voids : bool
|
|
93
|
+
Retain voids that intersect the clip polygon. Default ``True``.
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
The cut-out can be produced with three different extent strategies:
|
|
97
|
+
|
|
98
|
+
* ``ConvexHull`` (default)
|
|
99
|
+
* ``Conforming`` (tight follow of geometry)
|
|
100
|
+
* ``Bounding`` (simple bounding box)
|
|
101
|
+
|
|
102
|
+
Multi-threaded execution, automatic terminal expansion and smart
|
|
103
|
+
expansion-factor logic are supported.
|
|
104
|
+
|
|
105
|
+
Examples
|
|
106
|
+
--------
|
|
107
|
+
>>> cut = Cutout(edb)
|
|
108
|
+
>>> cut.signals = ["DDR4_DQ0", "DDR4_DQ1"]
|
|
109
|
+
>>> cut.references = ["GND"]
|
|
110
|
+
>>> cut.expansion_size = 0.001
|
|
111
|
+
>>> polygon = cut.run()
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
# ------------------------------------------------------------------
|
|
115
|
+
# Construction
|
|
116
|
+
# ------------------------------------------------------------------
|
|
117
|
+
def __init__(self, edb):
|
|
118
|
+
self._edb = edb
|
|
119
|
+
self.signals: List[str] = [] # list of signal nets
|
|
120
|
+
self.references: List[str] = [] # list of reference nets
|
|
121
|
+
self.extent_type: str = "ConvexHull" # ConvexHull | Conforming | Bounding
|
|
122
|
+
self.expansion_size: Union[str, float] = 0.002 # metres
|
|
123
|
+
self.use_round_corner: bool = False
|
|
124
|
+
self.output_file: str = "" # output .aedb folder
|
|
125
|
+
self.open_cutout_at_end: bool = True
|
|
126
|
+
self.use_pyaedt_cutout: bool = True
|
|
127
|
+
self.smart_cutout: bool = False
|
|
128
|
+
self.number_of_threads: int = 2
|
|
129
|
+
self.use_pyaedt_extent_computing: bool = True
|
|
130
|
+
self.extent_defeature: Union[int, float] = 0
|
|
131
|
+
self.remove_single_pin_components: bool = False
|
|
132
|
+
self.custom_extent: List[float, float] = None
|
|
133
|
+
self.custom_extent_units: str = "mm"
|
|
134
|
+
self.include_partial_instances: bool = False
|
|
135
|
+
self.keep_voids: bool = True
|
|
136
|
+
self.check_terminals: bool = False
|
|
137
|
+
self.include_pingroups: bool = False
|
|
138
|
+
self.expansion_factor: Union[int, float] = 0
|
|
139
|
+
self.maximum_iterations: int = 10
|
|
140
|
+
self.preserve_components_with_model: bool = False
|
|
141
|
+
self.simple_pad_check: bool = True
|
|
142
|
+
self.keep_lines_as_path: bool = False
|
|
143
|
+
self.include_voids_in_extents: bool = False
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def logger(self):
|
|
147
|
+
"""Edb logger."""
|
|
148
|
+
return self._edb.logger
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def _modeler(self):
|
|
152
|
+
return self._edb.modeler
|
|
153
|
+
|
|
154
|
+
def calculate_initial_extent(self, expansion_factor):
|
|
155
|
+
"""Compute a float representing the larger number between the dielectric thickness or trace width
|
|
156
|
+
multiplied by the nW factor. The trace width search is limited to nets with ports attached.
|
|
157
|
+
|
|
158
|
+
Parameters
|
|
159
|
+
----------
|
|
160
|
+
expansion_factor : float
|
|
161
|
+
Value for the width multiplier (nW factor).
|
|
162
|
+
|
|
163
|
+
Returns
|
|
164
|
+
-------
|
|
165
|
+
float
|
|
166
|
+
"""
|
|
167
|
+
nets = []
|
|
168
|
+
for port in self._edb.excitations.values():
|
|
169
|
+
nets.append(port.net_name)
|
|
170
|
+
for port in self._edb.sources.values():
|
|
171
|
+
nets.append(port.net_name)
|
|
172
|
+
nets = list(set(nets))
|
|
173
|
+
max_width = 0
|
|
174
|
+
for net in nets:
|
|
175
|
+
for primitive in self._edb.nets[net].primitives:
|
|
176
|
+
if primitive.type == "Path":
|
|
177
|
+
max_width = max(max_width, primitive.width)
|
|
178
|
+
|
|
179
|
+
for layer in list(self._edb.stackup.dielectric_layers.values()):
|
|
180
|
+
max_width = max(max_width, layer.thickness)
|
|
181
|
+
|
|
182
|
+
max_width = max_width * expansion_factor
|
|
183
|
+
self.logger.info("The W factor is {}, The initial extent = {:e}".format(expansion_factor, max_width))
|
|
184
|
+
return max_width
|
|
185
|
+
|
|
186
|
+
def _create_convex_hull(
|
|
187
|
+
self,
|
|
188
|
+
tolerance=1e-12,
|
|
189
|
+
):
|
|
190
|
+
_polys = []
|
|
191
|
+
_pins_to_preserve, _ = self.pins_to_preserve()
|
|
192
|
+
if _pins_to_preserve:
|
|
193
|
+
insts = self._edb.padstacks.instances
|
|
194
|
+
for i in _pins_to_preserve:
|
|
195
|
+
p = insts[i].position
|
|
196
|
+
|
|
197
|
+
pos_1 = [i - 10e-6 for i in p]
|
|
198
|
+
pos_3 = [i + 10e-6 for i in p]
|
|
199
|
+
pos_4 = [pos_1[0], pos_3[1]]
|
|
200
|
+
pos_2 = [pos_3[0], pos_1[1]]
|
|
201
|
+
pts = [pos_1, pos_2, pos_3, pos_4, pos_1]
|
|
202
|
+
rectangle_data = GrpcPolygonData(points=pts)
|
|
203
|
+
_polys.append(rectangle_data)
|
|
204
|
+
for prim in self._edb.modeler.primitives:
|
|
205
|
+
if prim is not None and prim.net_name in self.signals:
|
|
206
|
+
_polys.append(prim.polygon_data)
|
|
207
|
+
if self.smart_cutout:
|
|
208
|
+
objs_data = self._smart_cut()
|
|
209
|
+
if objs_data:
|
|
210
|
+
_polys.extend(objs_data)
|
|
211
|
+
|
|
212
|
+
_poly = GrpcPolygonData.convex_hull(_polys)
|
|
213
|
+
extent = _poly.expand(
|
|
214
|
+
offset=self.expansion_size,
|
|
215
|
+
round_corner=self.use_round_corner,
|
|
216
|
+
max_corner_ext=self.expansion_size,
|
|
217
|
+
tol=tolerance,
|
|
218
|
+
)[0]
|
|
219
|
+
return extent
|
|
220
|
+
|
|
221
|
+
def _create_conformal(
|
|
222
|
+
self,
|
|
223
|
+
tolerance=1e-12,
|
|
224
|
+
):
|
|
225
|
+
_polys = []
|
|
226
|
+
_pins_to_preserve, _ = self.pins_to_preserve()
|
|
227
|
+
if _pins_to_preserve:
|
|
228
|
+
insts = self._edb.padstacks.instances
|
|
229
|
+
for i in _pins_to_preserve:
|
|
230
|
+
p = insts[i].position
|
|
231
|
+
pos_1 = [i - 75e-6 for i in p]
|
|
232
|
+
pos_2 = [i + 75e-6 for i in p]
|
|
233
|
+
plane = self._edb.modeler.create_rectangle(lower_left_point=pos_1, upper_right_point=pos_2)
|
|
234
|
+
rectangle_data = plane.polygon_data
|
|
235
|
+
_polys.append(rectangle_data)
|
|
236
|
+
|
|
237
|
+
for prim in self._edb.modeler.primitives:
|
|
238
|
+
if prim is not None and prim.net_name in self.signals:
|
|
239
|
+
_polys.append(prim)
|
|
240
|
+
if self.smart_cutout:
|
|
241
|
+
objs_data = self._smart_cut()
|
|
242
|
+
_polys.extend(objs_data)
|
|
243
|
+
k = 0
|
|
244
|
+
expansion_size = self.expansion_size
|
|
245
|
+
delta = self.expansion_size / 5
|
|
246
|
+
_poly_unite = []
|
|
247
|
+
while k < 10:
|
|
248
|
+
unite_polys = []
|
|
249
|
+
for i in _polys:
|
|
250
|
+
if hasattr(i, "polygon_data"):
|
|
251
|
+
obj_data = i.polygon_data.expand(
|
|
252
|
+
offset=expansion_size,
|
|
253
|
+
round_corner=self.use_round_corner,
|
|
254
|
+
max_corner_ext=expansion_size,
|
|
255
|
+
tol=tolerance,
|
|
256
|
+
)
|
|
257
|
+
else:
|
|
258
|
+
obj_data = i.expand(
|
|
259
|
+
offset=expansion_size,
|
|
260
|
+
round_corner=self.use_round_corner,
|
|
261
|
+
max_corner_ext=expansion_size,
|
|
262
|
+
tol=tolerance,
|
|
263
|
+
)
|
|
264
|
+
if self.include_voids_in_extents and hasattr(i, "polygon_data") and i.has_voids and obj_data:
|
|
265
|
+
for void in i.voids:
|
|
266
|
+
void_data = void.polygon_data.expand(
|
|
267
|
+
offset=-1 * expansion_size,
|
|
268
|
+
round_corner=self.use_round_corner,
|
|
269
|
+
max_corner_ext=expansion_size,
|
|
270
|
+
tol=tolerance,
|
|
271
|
+
)
|
|
272
|
+
if void_data:
|
|
273
|
+
for v in list(void_data):
|
|
274
|
+
obj_data[0].add_hole(v)
|
|
275
|
+
if obj_data:
|
|
276
|
+
if not self.include_voids_in_extents:
|
|
277
|
+
unite_polys.extend(list(obj_data))
|
|
278
|
+
else:
|
|
279
|
+
voids_poly = []
|
|
280
|
+
try:
|
|
281
|
+
if i.has_voids:
|
|
282
|
+
area = i.area()
|
|
283
|
+
for void in i.voids:
|
|
284
|
+
void_polydata = void.polygon_data
|
|
285
|
+
if void_polydata.area() >= 0.05 * area:
|
|
286
|
+
voids_poly.append(void_polydata)
|
|
287
|
+
if voids_poly:
|
|
288
|
+
obj_data = obj_data[0].subtract(list(obj_data), voids_poly)
|
|
289
|
+
except Exception as e:
|
|
290
|
+
self.logger.error(
|
|
291
|
+
f"A(n) {type(e).__name__} error occurred in method _create_conformal of "
|
|
292
|
+
f"class GrpcCutout at iteration {k} for data {i}: {str(e)}"
|
|
293
|
+
)
|
|
294
|
+
finally:
|
|
295
|
+
unite_polys.extend(list(obj_data))
|
|
296
|
+
_poly_unite = GrpcPolygonData.unite(unite_polys)
|
|
297
|
+
if len(_poly_unite) == 1:
|
|
298
|
+
self.logger.info("Correctly computed Extension at first iteration.")
|
|
299
|
+
return _poly_unite[0]
|
|
300
|
+
k += 1
|
|
301
|
+
expansion_size += delta
|
|
302
|
+
if len(_poly_unite) == 1:
|
|
303
|
+
self.logger.info("Correctly computed Extension in {} iterations.".format(k))
|
|
304
|
+
return _poly_unite[0]
|
|
305
|
+
else:
|
|
306
|
+
self.logger.info("Failed to Correctly computed Extension.")
|
|
307
|
+
areas = [i.area() for i in _poly_unite]
|
|
308
|
+
return _poly_unite[areas.index(max(areas))]
|
|
309
|
+
|
|
310
|
+
def _smart_cut(self):
|
|
311
|
+
_polys = []
|
|
312
|
+
terms = [term for term in self._edb.layout.terminals if int(term.boundary_type) in [0, 3, 4, 7, 8]]
|
|
313
|
+
locations = []
|
|
314
|
+
for term in terms:
|
|
315
|
+
if term.net.name in self.references:
|
|
316
|
+
position = term.position
|
|
317
|
+
locations.append([position.x.value, position.y.value])
|
|
318
|
+
for point in locations:
|
|
319
|
+
points = [
|
|
320
|
+
[self._edb.value(point[0] - self.expansion_size), self._edb.value(point[1] - self.expansion_size)],
|
|
321
|
+
[self._edb.value(point[0] - self.expansion_size), self._edb.value(point[1] + self.expansion_size)],
|
|
322
|
+
[self._edb.value(point[0] + self.expansion_size), self._edb.value(point[1] - self.expansion_size)],
|
|
323
|
+
[self._edb.value(point[0] + self.expansion_size), self._edb.value(point[1] + self.expansion_size)],
|
|
324
|
+
]
|
|
325
|
+
_polys.append(GrpcPolygonData(points=points))
|
|
326
|
+
return _polys
|
|
327
|
+
|
|
328
|
+
def pins_to_preserve(self):
|
|
329
|
+
_pins_to_preserve = []
|
|
330
|
+
_nets_to_preserve = []
|
|
331
|
+
|
|
332
|
+
if self.preserve_components_with_model:
|
|
333
|
+
for el in self._edb.layout.groups:
|
|
334
|
+
if el.model_type in [
|
|
335
|
+
"SPICEModel",
|
|
336
|
+
"SParameterModel",
|
|
337
|
+
"NetlistModel",
|
|
338
|
+
] and list(set(el.nets[:]) & set(self.signals[:])):
|
|
339
|
+
_pins_to_preserve.extend([i.edb_uid for i in el.pins.values()])
|
|
340
|
+
_nets_to_preserve.extend(el.nets)
|
|
341
|
+
if self.include_pingroups:
|
|
342
|
+
for pingroup in self._edb.padstacks.pingroups:
|
|
343
|
+
for pin in pingroup.pins.values():
|
|
344
|
+
if pin.net_name in self.references:
|
|
345
|
+
_pins_to_preserve.append(pin.edb_uid)
|
|
346
|
+
return _pins_to_preserve, _nets_to_preserve
|
|
347
|
+
|
|
348
|
+
def _compute_pyaedt_extent(self):
|
|
349
|
+
signal_nets = [self._edb.nets.nets[n] for n in self.signals]
|
|
350
|
+
|
|
351
|
+
if str(self.extent_type).lower() in ["conforming", "conformal", "1"]:
|
|
352
|
+
_poly = self._create_conformal(
|
|
353
|
+
1e-12,
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
elif str(self.extent_type).lower() in ["bounding", "0", "bounding_box", "bbox", "boundingbox"]:
|
|
357
|
+
_poly = self._edb.layout.expanded_extent(
|
|
358
|
+
signal_nets,
|
|
359
|
+
GrpcExtentType.BOUNDING_BOX,
|
|
360
|
+
self.expansion_size,
|
|
361
|
+
False,
|
|
362
|
+
self.use_round_corner,
|
|
363
|
+
1,
|
|
364
|
+
)
|
|
365
|
+
else:
|
|
366
|
+
_poly = self._create_convex_hull(
|
|
367
|
+
1e-12,
|
|
368
|
+
)
|
|
369
|
+
_poly_list = [_poly]
|
|
370
|
+
_poly = GrpcPolygonData.convex_hull(_poly_list)
|
|
371
|
+
return _poly
|
|
372
|
+
|
|
373
|
+
def _compute_legacy_extent(self):
|
|
374
|
+
if str(self.extent_type).lower() in ["conforming", "conformal", "1"]:
|
|
375
|
+
extent_type = GrpcExtentType.CONFORMING
|
|
376
|
+
elif str(self.extent_type).lower() in ["bounding", "bounding_box", "bbox", "0", "boundingbox"]:
|
|
377
|
+
extent_type = GrpcExtentType.BOUNDING_BOX
|
|
378
|
+
else:
|
|
379
|
+
extent_type = self._edb.core.Geometry.ExtentType.ConvexHull
|
|
380
|
+
_poly = self._edb.layout.expanded_extent(
|
|
381
|
+
self.signals,
|
|
382
|
+
extent_type,
|
|
383
|
+
self.expansion_size,
|
|
384
|
+
False,
|
|
385
|
+
self.use_round_corner,
|
|
386
|
+
1,
|
|
387
|
+
)
|
|
388
|
+
return _poly
|
|
389
|
+
|
|
390
|
+
def _extent(self):
|
|
391
|
+
"""Compute extent with native EDB API."""
|
|
392
|
+
if self.custom_extent:
|
|
393
|
+
point_list = self.custom_extent[::]
|
|
394
|
+
if point_list[0] != point_list[-1]:
|
|
395
|
+
point_list.append(point_list[0])
|
|
396
|
+
point_list = [
|
|
397
|
+
[
|
|
398
|
+
self._edb.number_with_units(i[0], self.custom_extent_units),
|
|
399
|
+
self._edb.number_with_units(i[1], self.custom_extent_units),
|
|
400
|
+
]
|
|
401
|
+
for i in point_list
|
|
402
|
+
]
|
|
403
|
+
_poly = GrpcPolygonData(points=point_list)
|
|
404
|
+
else:
|
|
405
|
+
if self.use_pyaedt_extent_computing:
|
|
406
|
+
_poly = self._compute_pyaedt_extent()
|
|
407
|
+
else:
|
|
408
|
+
_poly = self._compute_legacy_extent()
|
|
409
|
+
_poly1 = _poly.without_arcs()
|
|
410
|
+
if self.include_voids_in_extents:
|
|
411
|
+
for hole in list(_poly.holes):
|
|
412
|
+
if hole.area() >= 0.05 * _poly1.area():
|
|
413
|
+
_poly1.add_hole(hole)
|
|
414
|
+
_poly = _poly1
|
|
415
|
+
return _poly
|
|
416
|
+
|
|
417
|
+
def _add_setups(self, _cutout):
|
|
418
|
+
id = 1
|
|
419
|
+
for _setup in self._edb.active_cell.simulation_setups:
|
|
420
|
+
# Empty string '' if coming from setup copy and don't set explicitly.
|
|
421
|
+
_setup_name = _setup.name
|
|
422
|
+
_setup.name = "HFSS Setup " + str(id) # Set name of analysis setup
|
|
423
|
+
# Write the simulation setup info into the cell/design setup
|
|
424
|
+
# _cutout.AddSimulationSetup(_setup) # Add simulation setup to the cutout design
|
|
425
|
+
# id += 1
|
|
426
|
+
# _cutout.AddSimulationSetup(_setup) # Add simulation setup to the cutout design
|
|
427
|
+
# finish adding setup for grpc
|
|
428
|
+
|
|
429
|
+
def _create_cutout_legacy(
|
|
430
|
+
self,
|
|
431
|
+
):
|
|
432
|
+
_poly = self._extent()
|
|
433
|
+
# Create new cutout cell/design
|
|
434
|
+
# validate nets in layout
|
|
435
|
+
net_signals = [net for net in self._edb.layout.nets if net.name in self.signals]
|
|
436
|
+
|
|
437
|
+
# reference nets in layout
|
|
438
|
+
ref_nets = [net for net in self._edb.layout.nets if net.name in self.references]
|
|
439
|
+
|
|
440
|
+
# validate references in layout
|
|
441
|
+
_netsClip = [net for net in self._edb.layout.nets if net.name in self.references]
|
|
442
|
+
included_nets_list = net_signals + ref_nets
|
|
443
|
+
# included_nets = [net for net in self._edb.layout.nets if net.name in included_nets_list]
|
|
444
|
+
_cutout = self._edb.active_cell.cutout(included_nets_list, _netsClip, _poly, True)
|
|
445
|
+
# Analysis setups do not come over with the clipped design copy,
|
|
446
|
+
# so add the analysis setups from the original here.
|
|
447
|
+
self._add_setups(_cutout)
|
|
448
|
+
|
|
449
|
+
_dbCells = [_cutout]
|
|
450
|
+
if self.output_file:
|
|
451
|
+
from ansys.edb.core.database import Database as GrpcDatabase
|
|
452
|
+
|
|
453
|
+
db2 = GrpcDatabase.create(self.output_file)
|
|
454
|
+
_success = db2.save()
|
|
455
|
+
db2.copy_cells(_dbCells) # Copies cutout cell/design to db2 project
|
|
456
|
+
if len(list(db2.top_circuit_cells)) > 0:
|
|
457
|
+
for net in db2.top_circuit_cells[0].layout.nets:
|
|
458
|
+
if not net.name in included_nets_list:
|
|
459
|
+
net.delete()
|
|
460
|
+
_success = db2.save()
|
|
461
|
+
for c in self._edb._db.top_circuit_cells:
|
|
462
|
+
if c.name == _cutout.name:
|
|
463
|
+
c.delete()
|
|
464
|
+
if self.open_cutout_at_end: # pragma: no cover
|
|
465
|
+
self._edb._db = db2
|
|
466
|
+
self._edb.edbpath = self.output_file
|
|
467
|
+
self._edb._active_cell = self._edb.top_circuit_cells[0]
|
|
468
|
+
self._edb.edbpath = self._edb.directory
|
|
469
|
+
self._edb._init_objects()
|
|
470
|
+
if self.remove_single_pin_components:
|
|
471
|
+
self._edb.components.delete_single_pin_rlc()
|
|
472
|
+
self.logger.info_timer("Single Pins components deleted")
|
|
473
|
+
self._edb.components.refresh_components()
|
|
474
|
+
else:
|
|
475
|
+
if self.remove_single_pin_components:
|
|
476
|
+
self._edb.components.delete_single_pin_rlc()
|
|
477
|
+
self.logger.info_timer("Single Pins components deleted")
|
|
478
|
+
self._edb.components.refresh_components()
|
|
479
|
+
db2.close()
|
|
480
|
+
source = os.path.join(self.output_file, "edb.def.tmp")
|
|
481
|
+
target = os.path.join(self.output_file, "edb.def")
|
|
482
|
+
if os.path.exists(source) and not os.path.exists(target):
|
|
483
|
+
try:
|
|
484
|
+
shutil.copy(source, target)
|
|
485
|
+
except Exception as e:
|
|
486
|
+
self.logger.error(f"Failed to copy {source} to {target} - {type(e).__name__}: {str(e)}")
|
|
487
|
+
elif self.open_cutout_at_end:
|
|
488
|
+
self._edb._active_cell = _cutout
|
|
489
|
+
self._edb._init_objects()
|
|
490
|
+
if self.remove_single_pin_components:
|
|
491
|
+
self._edb.components.delete_single_pin_rlc()
|
|
492
|
+
self.logger.info_timer("Single Pins components deleted")
|
|
493
|
+
self._edb.components.refresh_components()
|
|
494
|
+
return [[pt.x.value, pt.y.value] for pt in _poly.without_arcs().points]
|
|
495
|
+
|
|
496
|
+
def _create_cutout_multithread(self):
|
|
497
|
+
"""
|
|
498
|
+
Optimised cut-out that is GRPC friendly.
|
|
499
|
+
EDB is **NOT** thread-safe and every write flushes the cache.
|
|
500
|
+
-----------------------------------------------------------
|
|
501
|
+
1. READ – collect everything that is required
|
|
502
|
+
2. COMPUTE – decide what has to be deleted / created
|
|
503
|
+
3. WRITE – one single, serial, write-pass
|
|
504
|
+
-----------------------------------------------------------
|
|
505
|
+
"""
|
|
506
|
+
_t0 = time.time()
|
|
507
|
+
self.logger.info("GRPC cut-out started")
|
|
508
|
+
timer_start = time.time()
|
|
509
|
+
self.expansion_size = self._edb.value(self.expansion_size)
|
|
510
|
+
|
|
511
|
+
# ------------------------------------------------------------------
|
|
512
|
+
# 1. READ ONLY – everything that can be queried without side effects
|
|
513
|
+
# ------------------------------------------------------------------
|
|
514
|
+
_t = time.time()
|
|
515
|
+
all_nets = {net.name: net for net in self._edb.nets.nets.values()}
|
|
516
|
+
all_padstack_instances = list(self._edb.padstacks.instances.values())
|
|
517
|
+
all_primitives = list(self._edb.modeler.primitives)
|
|
518
|
+
all_components = list(self._edb.components.instances.values())
|
|
519
|
+
nets_num = len(all_nets)
|
|
520
|
+
inst_num = len(all_padstack_instances)
|
|
521
|
+
prim_num = len(all_primitives)
|
|
522
|
+
comp_num = len(all_components)
|
|
523
|
+
self.logger.info(f"[READ] Data collection finished in {time.time() - _t:.3f} s")
|
|
524
|
+
self.logger.info(
|
|
525
|
+
f"Nets:{nets_num}, padstack_instances:{inst_num}, primitives:{prim_num}, components:{comp_num}"
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
# preserve original net list logic
|
|
529
|
+
if self.custom_extent:
|
|
530
|
+
reference_list = (
|
|
531
|
+
all_nets.keys() if not (self.signals or self.references) else self.signals + self.references
|
|
532
|
+
)
|
|
533
|
+
full_list = reference_list
|
|
534
|
+
else:
|
|
535
|
+
full_list = self.signals + self.references
|
|
536
|
+
reference_list = self.references
|
|
537
|
+
|
|
538
|
+
pins_to_preserve, nets_to_preserve = self.pins_to_preserve()
|
|
539
|
+
|
|
540
|
+
# build extent polygon
|
|
541
|
+
_t = time.time()
|
|
542
|
+
extent_poly = self._extent()
|
|
543
|
+
if not extent_poly.points:
|
|
544
|
+
self.logger.error("Failed to create extent polygon")
|
|
545
|
+
return []
|
|
546
|
+
self.logger.info(f"[EXTENT] Polygon created in {time.time() - _t:.3f} s")
|
|
547
|
+
|
|
548
|
+
# 2. COMPUTE – decide what has to be deleted / created
|
|
549
|
+
_t = time.time()
|
|
550
|
+
# nets
|
|
551
|
+
nets_to_delete = [
|
|
552
|
+
net for name, net in all_nets.items() if name not in full_list and name not in nets_to_preserve
|
|
553
|
+
]
|
|
554
|
+
|
|
555
|
+
# padstacks
|
|
556
|
+
pins_to_delete, reference_pinsts = [], []
|
|
557
|
+
for p in all_padstack_instances:
|
|
558
|
+
if p.id in pins_to_preserve:
|
|
559
|
+
continue
|
|
560
|
+
if p.net_name not in full_list:
|
|
561
|
+
pins_to_delete.append(p)
|
|
562
|
+
elif p.net_name in reference_list:
|
|
563
|
+
reference_pinsts.append(p)
|
|
564
|
+
|
|
565
|
+
# primitives
|
|
566
|
+
signal_prims, reference_prims, reference_paths, prim_to_delete = [], [], [], []
|
|
567
|
+
for prim in all_primitives:
|
|
568
|
+
if not prim:
|
|
569
|
+
continue
|
|
570
|
+
net = prim.net_name
|
|
571
|
+
if net not in full_list and net not in nets_to_preserve:
|
|
572
|
+
prim_to_delete.append(prim)
|
|
573
|
+
elif net in self.signals:
|
|
574
|
+
signal_prims.append(prim)
|
|
575
|
+
elif net in reference_list and not prim.is_void:
|
|
576
|
+
if self.keep_lines_as_path and prim.type == "path":
|
|
577
|
+
reference_paths.append(prim)
|
|
578
|
+
else:
|
|
579
|
+
reference_prims.append(prim)
|
|
580
|
+
|
|
581
|
+
# geometry clipping – only *compute* new polygons, do **not** create them yet
|
|
582
|
+
pins_to_clip, prims_to_clip, poly_to_create = [], [], []
|
|
583
|
+
|
|
584
|
+
# padstacks
|
|
585
|
+
for p in reference_pinsts:
|
|
586
|
+
if not p.in_polygon(extent_poly, include_partial=self.include_partial_instances):
|
|
587
|
+
pins_to_clip.append(p)
|
|
588
|
+
|
|
589
|
+
# paths
|
|
590
|
+
for path in reference_paths:
|
|
591
|
+
pdata = path.polygon_data
|
|
592
|
+
if extent_poly.intersection_type(pdata) == 0:
|
|
593
|
+
prims_to_clip.append(path)
|
|
594
|
+
continue
|
|
595
|
+
if not path.set_clip_info(extent_poly, True):
|
|
596
|
+
# clipping failed – treat as polygon
|
|
597
|
+
reference_prims.append(path)
|
|
598
|
+
|
|
599
|
+
# reference primitives
|
|
600
|
+
for prim in reference_prims:
|
|
601
|
+
pdata = prim.polygon_data
|
|
602
|
+
int_type = extent_poly.intersection_type(pdata).value
|
|
603
|
+
if int_type in (0, 4): # completely outside
|
|
604
|
+
prims_to_clip.append(prim)
|
|
605
|
+
elif int_type == 2: # completely inside – keep
|
|
606
|
+
continue
|
|
607
|
+
elif int_type in (1, 3): # partial – clip
|
|
608
|
+
clipped_list = extent_poly.intersect(extent_poly, pdata)
|
|
609
|
+
for p in clipped_list:
|
|
610
|
+
if not p.points:
|
|
611
|
+
continue
|
|
612
|
+
voids_data = [v.polygon_data for v in prim.voids]
|
|
613
|
+
if voids_data:
|
|
614
|
+
for poly_void in p.subtract(p, voids_data):
|
|
615
|
+
if poly_void.points:
|
|
616
|
+
poly_to_create.append([poly_void, prim.layer.name, prim.net_name, []])
|
|
617
|
+
else:
|
|
618
|
+
poly_to_create.append([p, prim.layer.name, prim.net_name, []])
|
|
619
|
+
prims_to_clip.append(prim)
|
|
620
|
+
|
|
621
|
+
# components
|
|
622
|
+
components_to_delete = [comp for comp in all_components if comp.numpins == 0]
|
|
623
|
+
self.logger.info(f"[COMPUTE] Decision lists ready in {time.time() - _t:.3f} s")
|
|
624
|
+
# ------------------------------------------------------------------
|
|
625
|
+
# 3. WRITE – single serial pass, no interleaved reads
|
|
626
|
+
# ------------------------------------------------------------------
|
|
627
|
+
_t = time.time()
|
|
628
|
+
self.logger.info("Starting single write-pass")
|
|
629
|
+
self.logger.info(f"Deleting {len(nets_to_delete)} nets")
|
|
630
|
+
for net in nets_to_delete:
|
|
631
|
+
net.delete()
|
|
632
|
+
|
|
633
|
+
# padstacks
|
|
634
|
+
total_pins_to_delete = pins_to_delete + pins_to_clip
|
|
635
|
+
_t1 = time.time()
|
|
636
|
+
self._edb.padstacks.delete_batch_instances(total_pins_to_delete)
|
|
637
|
+
self.logger.info(f"{len(total_pins_to_delete)} pad-stack instances deleted in {time.time() - _t1:.3f} s")
|
|
638
|
+
|
|
639
|
+
# primitives
|
|
640
|
+
total_primitive_to_delete = prim_to_delete + prims_to_clip + [v for prim in prims_to_clip for v in prim.voids]
|
|
641
|
+
_t1 = time.time()
|
|
642
|
+
self._edb.modeler.delete_batch_primitives(total_primitive_to_delete)
|
|
643
|
+
self.logger.info(f"{len(total_primitive_to_delete)} primitives deleted in {time.time() - _t1:.3f} s")
|
|
644
|
+
|
|
645
|
+
# new polygons
|
|
646
|
+
_t1 = time.time()
|
|
647
|
+
for p_data, layer, net, voids in poly_to_create:
|
|
648
|
+
self._edb.modeler.create_polygon(p_data, layer, net_name=net, voids=voids)
|
|
649
|
+
self.logger.info(f"{len(poly_to_create)} primitives created in {time.time() - _t1:.3f} s")
|
|
650
|
+
|
|
651
|
+
# components
|
|
652
|
+
_t1 = time.time()
|
|
653
|
+
for comp in components_to_delete:
|
|
654
|
+
comp.delete()
|
|
655
|
+
if self.remove_single_pin_components:
|
|
656
|
+
self._edb.components.delete_single_pin_rlc()
|
|
657
|
+
self.logger.info(f"{len(components_to_delete)} components deleted in {time.time() - _t1:.3f} s")
|
|
658
|
+
self._edb.components.refresh_components()
|
|
659
|
+
self.logger.info(f"[WRITE] All writes finished in {time.time() - _t:.3f} s")
|
|
660
|
+
|
|
661
|
+
# final save
|
|
662
|
+
if self.output_file:
|
|
663
|
+
self._edb.save_as(self.output_file)
|
|
664
|
+
|
|
665
|
+
self.logger.info_timer("GRPC-safe cut-out completed", _t0)
|
|
666
|
+
return [[pt.x.value, pt.y.value] for pt in extent_poly.without_arcs().points]
|
|
667
|
+
|
|
668
|
+
def run(self):
|
|
669
|
+
if not self.use_pyaedt_cutout:
|
|
670
|
+
return self._create_cutout_legacy()
|
|
671
|
+
else:
|
|
672
|
+
out_file = self.output_file
|
|
673
|
+
expansion_size = self.expansion_size
|
|
674
|
+
if not self.smart_cutout:
|
|
675
|
+
self.maximum_iterations = 1
|
|
676
|
+
self.expansion_factor = 0
|
|
677
|
+
elif self.expansion_factor > 0:
|
|
678
|
+
expansion_size = self.calculate_initial_extent(self.expansion_factor)
|
|
679
|
+
self._edb.save()
|
|
680
|
+
self.output_file = self._edb.edbpath.replace(".aedb", "_smart_cutout_temp.aedb")
|
|
681
|
+
|
|
682
|
+
legacy_path = self._edb.edbpath
|
|
683
|
+
start = time.time()
|
|
684
|
+
working_cutout = False
|
|
685
|
+
i = 1
|
|
686
|
+
expansion = self._edb.value(expansion_size)
|
|
687
|
+
result = False
|
|
688
|
+
while i <= self.maximum_iterations:
|
|
689
|
+
self.logger.info("-----------------------------------------")
|
|
690
|
+
self.logger.info(f"Trying cutout with {expansion * 1e3}mm expansion size")
|
|
691
|
+
self.logger.info("-----------------------------------------")
|
|
692
|
+
result = self._create_cutout_multithread()
|
|
693
|
+
if result:
|
|
694
|
+
if self.smart_cutout:
|
|
695
|
+
if not self._edb.are_port_reference_terminals_connected():
|
|
696
|
+
raise RuntimeError("Smart cutout failed.")
|
|
697
|
+
self.output_file = out_file
|
|
698
|
+
if self.output_file:
|
|
699
|
+
self._edb.save_as(self.output_file)
|
|
700
|
+
else:
|
|
701
|
+
self._edb.save_as(legacy_path)
|
|
702
|
+
working_cutout = True
|
|
703
|
+
if not self.open_cutout_at_end and self._edb.edbpath != legacy_path:
|
|
704
|
+
self._edb.close()
|
|
705
|
+
self._edb.edbpath = legacy_path
|
|
706
|
+
self._edb.open_edb()
|
|
707
|
+
break
|
|
708
|
+
self._edb.close()
|
|
709
|
+
self._edb.edbpath = legacy_path
|
|
710
|
+
self._edb.open_edb()
|
|
711
|
+
i += 1
|
|
712
|
+
expansion = expansion_size * i
|
|
713
|
+
if working_cutout:
|
|
714
|
+
msg = f"Cutout completed in {i} iterations with expansion size of {float(expansion) * 1e3}mm"
|
|
715
|
+
self.logger.info_timer(msg, start)
|
|
716
|
+
else:
|
|
717
|
+
msg = f"Cutout failed after {i} iterations and expansion size of {float(expansion) * 1e3}mm"
|
|
718
|
+
self.logger.info_timer(msg, start)
|
|
719
|
+
return False
|
|
720
|
+
return result
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
class DotNetCutout:
|
|
724
|
+
"""Create a clipped (cut-out) EDB cell from an existing layout.
|
|
725
|
+
High-performance EDB cut-out utility.
|
|
726
|
+
|
|
727
|
+
Attributes
|
|
728
|
+
----------
|
|
729
|
+
signals : list[str]
|
|
730
|
+
List of signal net names to keep in the cut-out.
|
|
731
|
+
references : list[str]
|
|
732
|
+
List of reference net names to keep in the cut-out.
|
|
733
|
+
extent_type : str
|
|
734
|
+
Extent algorithm: ``ConvexHull`` (default), ``Conforming``, ``Bounding``.
|
|
735
|
+
expansion_size : float
|
|
736
|
+
Additional margin (metres) around the computed extent. Default 0.002.
|
|
737
|
+
use_round_corner : bool
|
|
738
|
+
Round the corners of the expanded extent. Default ``False``.
|
|
739
|
+
custom_extent : list[tuple[float, float]] | None
|
|
740
|
+
Optional closed polygon [(x1,y1), …] overriding any automatic extent.
|
|
741
|
+
custom_extent_units : str
|
|
742
|
+
Length unit for *custom_extent*. Default ``mm``.
|
|
743
|
+
include_voids_in_extents : bool
|
|
744
|
+
Include voids ≥ 5 % of the extent area when building the clip polygon.
|
|
745
|
+
open_cutout_at_end : bool
|
|
746
|
+
Open the resulting cut-out database in the active Edb object. Default ``True``.
|
|
747
|
+
use_pyaedt_cutout : bool
|
|
748
|
+
Use the PyAEDT based implementation instead of native EDB API. Default ``True``.
|
|
749
|
+
smart_cutout : bool
|
|
750
|
+
Automatically enlarge *expansion_size* until all ports have reference. Default ``False``.
|
|
751
|
+
expansion_factor : float
|
|
752
|
+
If > 0, compute initial *expansion_size* from trace-width/dielectric. Default 0.
|
|
753
|
+
maximum_iterations : int
|
|
754
|
+
Maximum attempts for *smart_cutout* before giving up. Default 10.
|
|
755
|
+
number_of_threads : int
|
|
756
|
+
Worker threads for polygon clipping and padstack cleaning. Default 1.
|
|
757
|
+
remove_single_pin_components : bool
|
|
758
|
+
Delete RLC components with only one pin after cut-out. Default ``False``.
|
|
759
|
+
preserve_components_with_model : bool
|
|
760
|
+
Keep every pin of components that carry a Spice/S-parameter model. Default ``False``.
|
|
761
|
+
check_terminals : bool
|
|
762
|
+
Grow extent until all reference terminals are inside the cut-out. Default ``False``.
|
|
763
|
+
include_pingroups : bool
|
|
764
|
+
Ensure complete pin-groups are included (needs *check_terminals*). Default ``False``.
|
|
765
|
+
simple_pad_check : bool
|
|
766
|
+
Use fast centre-point padstack check instead of bounding-box. Default ``True``.
|
|
767
|
+
keep_lines_as_path : bool
|
|
768
|
+
Keep clipped traces as Path objects (3D Layout only). Default ``False``.
|
|
769
|
+
extent_defeature : float
|
|
770
|
+
Defeature tolerance (metres) for conformal extent. Default 0.
|
|
771
|
+
include_partial_instances : bool
|
|
772
|
+
Include padstacks that only partially overlap the clip polygon. Default ``False``.
|
|
773
|
+
keep_voids : bool
|
|
774
|
+
Retain voids that intersect the clip polygon. Default ``True``.
|
|
775
|
+
|
|
776
|
+
|
|
777
|
+
The cut-out can be produced with three different extent strategies:
|
|
778
|
+
|
|
779
|
+
* ``ConvexHull`` (default)
|
|
780
|
+
* ``Conforming`` (tight follow of geometry)
|
|
781
|
+
* ``Bounding`` (simple bounding box)
|
|
782
|
+
|
|
783
|
+
Multi-threaded execution, automatic terminal expansion and smart
|
|
784
|
+
expansion-factor logic are supported.
|
|
785
|
+
|
|
786
|
+
Examples
|
|
787
|
+
--------
|
|
788
|
+
>>> cut = Cutout(edb)
|
|
789
|
+
>>> cut.signals = ["DDR4_DQ0", "DDR4_DQ1"]
|
|
790
|
+
>>> cut.references = ["GND"]
|
|
791
|
+
>>> cut.expansion_size = 0.001
|
|
792
|
+
>>> polygon = cut.run()
|
|
793
|
+
"""
|
|
794
|
+
|
|
795
|
+
# ------------------------------------------------------------------
|
|
796
|
+
# Construction
|
|
797
|
+
# ------------------------------------------------------------------
|
|
798
|
+
def __init__(self, edb):
|
|
799
|
+
self._edb = edb
|
|
800
|
+
self.signals: List[str] = [] # list of signal nets
|
|
801
|
+
self.references: List[str] = [] # list of reference nets
|
|
802
|
+
self.extent_type: str = "ConvexHull" # ConvexHull | Conforming | Bounding
|
|
803
|
+
self.expansion_size: Union[str, float] = 0.002 # metres
|
|
804
|
+
self.use_round_corner: bool = False
|
|
805
|
+
self.output_file: str = "" # output .aedb folder
|
|
806
|
+
self.open_cutout_at_end: bool = True
|
|
807
|
+
self.use_pyaedt_cutout: bool = True
|
|
808
|
+
self.smart_cutout: bool = False
|
|
809
|
+
self.number_of_threads: int = 2
|
|
810
|
+
self.use_pyaedt_extent_computing: bool = True
|
|
811
|
+
self.extent_defeature: Union[int, float] = 0
|
|
812
|
+
self.remove_single_pin_components: bool = False
|
|
813
|
+
self.custom_extent: List[float, float] = None
|
|
814
|
+
self.custom_extent_units: str = "mm"
|
|
815
|
+
self.include_partial_instances: bool = False
|
|
816
|
+
self.keep_voids: bool = True
|
|
817
|
+
self.check_terminals: bool = False
|
|
818
|
+
self.include_pingroups: bool = False
|
|
819
|
+
self.expansion_factor: Union[int, float] = 0
|
|
820
|
+
self.maximum_iterations: int = 10
|
|
821
|
+
self.preserve_components_with_model: bool = False
|
|
822
|
+
self.simple_pad_check: bool = True
|
|
823
|
+
self.keep_lines_as_path: bool = False
|
|
824
|
+
self.include_voids_in_extents: bool = False
|
|
825
|
+
|
|
826
|
+
@property
|
|
827
|
+
def logger(self):
|
|
828
|
+
"""Edb logger."""
|
|
829
|
+
return self._edb.logger
|
|
830
|
+
|
|
831
|
+
@property
|
|
832
|
+
def _modeler(self):
|
|
833
|
+
return self._edb.modeler
|
|
834
|
+
|
|
835
|
+
def calculate_initial_extent(self, expansion_factor):
|
|
836
|
+
"""Compute a float representing the larger number between the dielectric thickness or trace width
|
|
837
|
+
multiplied by the nW factor. The trace width search is limited to nets with ports attached.
|
|
838
|
+
|
|
839
|
+
Parameters
|
|
840
|
+
----------
|
|
841
|
+
expansion_factor : float
|
|
842
|
+
Value for the width multiplier (nW factor).
|
|
843
|
+
|
|
844
|
+
Returns
|
|
845
|
+
-------
|
|
846
|
+
float
|
|
847
|
+
"""
|
|
848
|
+
nets = []
|
|
849
|
+
for port in self._edb.excitations.values():
|
|
850
|
+
nets.append(port.net_name)
|
|
851
|
+
for port in self._edb.sources.values():
|
|
852
|
+
nets.append(port.net_name)
|
|
853
|
+
nets = list(set(nets))
|
|
854
|
+
max_width = 0
|
|
855
|
+
for net in nets:
|
|
856
|
+
for primitive in self._edb.nets[net].primitives:
|
|
857
|
+
if primitive.type == "Path":
|
|
858
|
+
max_width = max(max_width, primitive.width)
|
|
859
|
+
|
|
860
|
+
for layer in list(self._edb.stackup.dielectric_layers.values()):
|
|
861
|
+
max_width = max(max_width, layer.thickness)
|
|
862
|
+
|
|
863
|
+
max_width = max_width * expansion_factor
|
|
864
|
+
self.logger.info("The W factor is {}, The initial extent = {:e}".format(expansion_factor, max_width))
|
|
865
|
+
return max_width
|
|
866
|
+
|
|
867
|
+
def _create_convex_hull(
|
|
868
|
+
self,
|
|
869
|
+
tolerance=1e-12,
|
|
870
|
+
):
|
|
871
|
+
_polys = []
|
|
872
|
+
_pins_to_preserve, _ = self.pins_to_preserve()
|
|
873
|
+
if _pins_to_preserve:
|
|
874
|
+
insts = self._edb.padstacks.instances
|
|
875
|
+
for i in _pins_to_preserve:
|
|
876
|
+
p = insts[i].position
|
|
877
|
+
pos_1 = [i - 1e-12 for i in p]
|
|
878
|
+
pos_2 = [i + 1e-12 for i in p]
|
|
879
|
+
plane = self._edb.modeler.Shape("rectangle", pointA=pos_1, pointB=pos_2)
|
|
880
|
+
rectangle_data = self._edb.modeler.shape_to_polygon_data(plane)
|
|
881
|
+
_polys.append(rectangle_data)
|
|
882
|
+
for prim in self._edb.modeler.primitives:
|
|
883
|
+
if prim is not None and prim.net_name in self.signals:
|
|
884
|
+
_polys.append(prim.primitive_object.GetPolygonData())
|
|
885
|
+
if self.smart_cutout:
|
|
886
|
+
objs_data = self._smart_cut()
|
|
887
|
+
if objs_data:
|
|
888
|
+
_polys.extend(objs_data)
|
|
889
|
+
_poly = self._edb.core.Geometry.PolygonData.GetConvexHullOfPolygons(convert_py_list_to_net_list(_polys))
|
|
890
|
+
_poly = _poly.Expand(self.expansion_size, tolerance, self.use_round_corner, self.expansion_size)
|
|
891
|
+
_poly_list = convert_py_list_to_net_list(list(_poly)[0])
|
|
892
|
+
_poly = self._edb.core.Geometry.PolygonData.GetConvexHullOfPolygons(_poly_list)
|
|
893
|
+
return _poly
|
|
894
|
+
|
|
895
|
+
def _create_conformal(
|
|
896
|
+
self,
|
|
897
|
+
tolerance=1e-12,
|
|
898
|
+
):
|
|
899
|
+
_polys = []
|
|
900
|
+
_pins_to_preserve, _ = self.pins_to_preserve()
|
|
901
|
+
if _pins_to_preserve:
|
|
902
|
+
insts = self._edb.padstacks.instances
|
|
903
|
+
for i in _pins_to_preserve:
|
|
904
|
+
p = insts[i].position
|
|
905
|
+
pos_1 = [i - self.expansion_size for i in p]
|
|
906
|
+
pos_2 = [i + self.expansion_size for i in p]
|
|
907
|
+
plane = self._edb.modeler.Shape("rectangle", pointA=pos_1, pointB=pos_2)
|
|
908
|
+
rectangle_data = self._edb.modeler.shape_to_polygon_data(plane)
|
|
909
|
+
_polys.append(rectangle_data)
|
|
910
|
+
|
|
911
|
+
for prim in self._edb.modeler.primitives:
|
|
912
|
+
if prim is not None and prim.net_name in self.signals:
|
|
913
|
+
_polys.append(prim)
|
|
914
|
+
if self.smart_cutout:
|
|
915
|
+
objs_data = self._smart_cut()
|
|
916
|
+
_polys.extend(objs_data)
|
|
917
|
+
k = 0
|
|
918
|
+
expansion_size = self.expansion_size
|
|
919
|
+
delta = self.expansion_size / 5
|
|
920
|
+
_poly_unite = []
|
|
921
|
+
while k < 10:
|
|
922
|
+
unite_polys = []
|
|
923
|
+
for i in _polys:
|
|
924
|
+
if "PolygonData" not in str(i):
|
|
925
|
+
obj_data = i.primitive_object.GetPolygonData().Expand(
|
|
926
|
+
expansion_size, tolerance, self.use_round_corner, expansion_size
|
|
927
|
+
)
|
|
928
|
+
else:
|
|
929
|
+
obj_data = i.Expand(expansion_size, tolerance, self.use_round_corner, expansion_size)
|
|
930
|
+
if self.include_voids_in_extents and "PolygonData" not in str(i) and i.has_voids and obj_data:
|
|
931
|
+
for void in i.voids:
|
|
932
|
+
void_data = void.primitive_object.GetPolygonData().Expand(
|
|
933
|
+
-1 * expansion_size, tolerance, self.use_round_corner, expansion_size
|
|
934
|
+
)
|
|
935
|
+
if void_data:
|
|
936
|
+
for v in list(void_data):
|
|
937
|
+
obj_data[0].AddHole(v)
|
|
938
|
+
if obj_data:
|
|
939
|
+
if not self.include_voids_in_extents:
|
|
940
|
+
unite_polys.extend(list(obj_data))
|
|
941
|
+
else:
|
|
942
|
+
voids_poly = []
|
|
943
|
+
try:
|
|
944
|
+
if i.HasVoids():
|
|
945
|
+
area = i.area()
|
|
946
|
+
for void in i.Voids:
|
|
947
|
+
void_polydata = void.GetPolygonData()
|
|
948
|
+
if void_polydata.Area() >= 0.05 * area:
|
|
949
|
+
voids_poly.append(void_polydata)
|
|
950
|
+
if voids_poly:
|
|
951
|
+
obj_data = obj_data[0].Subtract(
|
|
952
|
+
convert_py_list_to_net_list(list(obj_data)),
|
|
953
|
+
convert_py_list_to_net_list(voids_poly),
|
|
954
|
+
)
|
|
955
|
+
except Exception as e:
|
|
956
|
+
self.logger.error(
|
|
957
|
+
f"A(n) {type(e).__name__} error occurred in method _create_conformal of "
|
|
958
|
+
f"class DotNetCutout at iteration {k} for data {i}: {str(e)}"
|
|
959
|
+
)
|
|
960
|
+
finally:
|
|
961
|
+
unite_polys.extend(list(obj_data))
|
|
962
|
+
_poly_unite = self._edb.core.Geometry.PolygonData.Unite(convert_py_list_to_net_list(unite_polys))
|
|
963
|
+
if len(_poly_unite) == 1:
|
|
964
|
+
self.logger.info("Correctly computed Extension at first iteration.")
|
|
965
|
+
return _poly_unite[0]
|
|
966
|
+
k += 1
|
|
967
|
+
expansion_size += delta
|
|
968
|
+
if len(_poly_unite) == 1:
|
|
969
|
+
self.logger.info("Correctly computed Extension in {} iterations.".format(k))
|
|
970
|
+
return _poly_unite[0]
|
|
971
|
+
else:
|
|
972
|
+
self.logger.info("Failed to Correctly computed Extension.")
|
|
973
|
+
areas = [i.Area() for i in _poly_unite]
|
|
974
|
+
return _poly_unite[areas.index(max(areas))]
|
|
975
|
+
|
|
976
|
+
def _smart_cut(self):
|
|
977
|
+
from pyedb.dotnet.clr_module import Tuple
|
|
978
|
+
|
|
979
|
+
_polys = []
|
|
980
|
+
terms = [
|
|
981
|
+
term for term in self._edb.layout.terminals if int(term._edb_object.GetBoundaryType()) in [0, 3, 4, 7, 8]
|
|
982
|
+
]
|
|
983
|
+
locations = []
|
|
984
|
+
for term in terms:
|
|
985
|
+
if term._edb_object.GetTerminalType().ToString() == "PointTerminal" and term.net.name in self.references:
|
|
986
|
+
pd = term._edb_object.GetParameters()[1]
|
|
987
|
+
locations.append([pd.X.ToDouble(), pd.Y.ToDouble()])
|
|
988
|
+
for point in locations:
|
|
989
|
+
pointA = self._edb.core.geometry.point_data(
|
|
990
|
+
self._edb.edb_value(point[0] - self.expansion_size),
|
|
991
|
+
self._edb.edb_value(point[1] - self.expansion_size),
|
|
992
|
+
)
|
|
993
|
+
pointB = self._edb.core.geometry.point_data(
|
|
994
|
+
self._edb.edb_value(point[0] + self.expansion_size),
|
|
995
|
+
self._edb.edb_value(point[1] + self.expansion_size),
|
|
996
|
+
)
|
|
997
|
+
|
|
998
|
+
points = Tuple[
|
|
999
|
+
self._edb.core.geometry.geometry.PointData,
|
|
1000
|
+
self._edb.core.geometry.geometry.PointData,
|
|
1001
|
+
](pointA, pointB)
|
|
1002
|
+
_polys.append(self._edb.core.geometry.polygon_data.create_from_bbox(points))
|
|
1003
|
+
return _polys
|
|
1004
|
+
|
|
1005
|
+
def pins_to_preserve(self):
|
|
1006
|
+
_pins_to_preserve = []
|
|
1007
|
+
_nets_to_preserve = []
|
|
1008
|
+
|
|
1009
|
+
if self.preserve_components_with_model:
|
|
1010
|
+
for el in self._edb.layout.groups:
|
|
1011
|
+
if el.model_type in [
|
|
1012
|
+
"SPICEModel",
|
|
1013
|
+
"SParameterModel",
|
|
1014
|
+
"NetlistModel",
|
|
1015
|
+
] and list(set(el.nets[:]) & set(self.signals[:])):
|
|
1016
|
+
_pins_to_preserve.extend([i.id for i in el.pins.values()])
|
|
1017
|
+
_nets_to_preserve.extend(el.nets)
|
|
1018
|
+
if self.include_pingroups:
|
|
1019
|
+
for pingroup in self._edb.padstacks.pingroups:
|
|
1020
|
+
for pin in pingroup.pins.values():
|
|
1021
|
+
if pin.net_name in self.references:
|
|
1022
|
+
_pins_to_preserve.append(pin.id)
|
|
1023
|
+
return _pins_to_preserve, _nets_to_preserve
|
|
1024
|
+
|
|
1025
|
+
def _compute_pyaedt_extent(self):
|
|
1026
|
+
signal_nets = [self._edb.nets.nets[n] for n in self.signals]
|
|
1027
|
+
|
|
1028
|
+
if str(self.extent_type).lower() in ["conforming", "conformal", "1"]:
|
|
1029
|
+
_poly = self._create_conformal(
|
|
1030
|
+
1e-12,
|
|
1031
|
+
)
|
|
1032
|
+
|
|
1033
|
+
elif str(self.extent_type).lower() in ["bounding", "0", "bounding_box", "bbox", "boundingbox"]:
|
|
1034
|
+
_poly = self._edb.layout.expanded_extent(
|
|
1035
|
+
signal_nets,
|
|
1036
|
+
self._edb.core.Geometry.ExtentType.BoundingBox,
|
|
1037
|
+
self.expansion_size,
|
|
1038
|
+
False,
|
|
1039
|
+
self.use_round_corner,
|
|
1040
|
+
1,
|
|
1041
|
+
)
|
|
1042
|
+
else:
|
|
1043
|
+
_poly = self._create_convex_hull(
|
|
1044
|
+
1e-12,
|
|
1045
|
+
)
|
|
1046
|
+
_poly_list = convert_py_list_to_net_list([_poly])
|
|
1047
|
+
_poly = self._edb.core.Geometry.PolygonData.GetConvexHullOfPolygons(_poly_list)
|
|
1048
|
+
return _poly
|
|
1049
|
+
|
|
1050
|
+
def _compute_legacy_extent(self):
|
|
1051
|
+
if str(self.extent_type).lower() in ["conforming", "conformal", "1"]:
|
|
1052
|
+
extent_type = self._edb.core.Geometry.ExtentType.Conforming
|
|
1053
|
+
elif str(self.extent_type).lower() in ["bounding", "0", "bounding_box", "bbox", "boundingbox"]:
|
|
1054
|
+
extent_type = self._edb.core.Geometry.ExtentType.BoundingBox
|
|
1055
|
+
else:
|
|
1056
|
+
extent_type = self._edb.core.Geometry.ExtentType.ConvexHull
|
|
1057
|
+
_poly = self._edb.layout.expanded_extent(
|
|
1058
|
+
self.signals,
|
|
1059
|
+
extent_type,
|
|
1060
|
+
self.expansion_size,
|
|
1061
|
+
False,
|
|
1062
|
+
self.use_round_corner,
|
|
1063
|
+
1,
|
|
1064
|
+
)
|
|
1065
|
+
return _poly
|
|
1066
|
+
|
|
1067
|
+
def _extent(self):
|
|
1068
|
+
"""Compute extent with native EDB API."""
|
|
1069
|
+
if self.custom_extent:
|
|
1070
|
+
point_list = self.custom_extent[::]
|
|
1071
|
+
if point_list[0] != point_list[-1]:
|
|
1072
|
+
point_list.append(point_list[0])
|
|
1073
|
+
point_list = [
|
|
1074
|
+
[
|
|
1075
|
+
self._edb.number_with_units(i[0], self.custom_extent_units),
|
|
1076
|
+
self._edb.number_with_units(i[1], self.custom_extent_units),
|
|
1077
|
+
]
|
|
1078
|
+
for i in point_list
|
|
1079
|
+
]
|
|
1080
|
+
plane = self._modeler.Shape("polygon", points=point_list)
|
|
1081
|
+
_poly = self._modeler.shape_to_polygon_data(plane)
|
|
1082
|
+
else:
|
|
1083
|
+
if self.use_pyaedt_extent_computing:
|
|
1084
|
+
_poly = self._compute_pyaedt_extent()
|
|
1085
|
+
else:
|
|
1086
|
+
_poly = self._compute_legacy_extent()
|
|
1087
|
+
_poly1 = _poly.CreateFromArcs(_poly.GetArcData(), True)
|
|
1088
|
+
if self.include_voids_in_extents:
|
|
1089
|
+
for hole in list(_poly.Holes):
|
|
1090
|
+
if hole.Area() >= 0.05 * _poly1.Area():
|
|
1091
|
+
_poly1.AddHole(hole)
|
|
1092
|
+
_poly = _poly1
|
|
1093
|
+
return _poly
|
|
1094
|
+
|
|
1095
|
+
def _add_setups(self, _cutout):
|
|
1096
|
+
id = 1
|
|
1097
|
+
for _setup in self._edb.active_cell.SimulationSetups:
|
|
1098
|
+
# Empty string '' if coming from setup copy and don't set explicitly.
|
|
1099
|
+
_setup_name = _setup.GetName()
|
|
1100
|
+
if "GetSimSetupInfo" in dir(_setup):
|
|
1101
|
+
# setup is an Ansys.Ansoft.Edb.Utility.HFSSSimulationSetup object
|
|
1102
|
+
_hfssSimSetupInfo = _setup.GetSimSetupInfo()
|
|
1103
|
+
_hfssSimSetupInfo.Name = "HFSS Setup " + str(id) # Set name of analysis setup
|
|
1104
|
+
# Write the simulation setup info into the cell/design setup
|
|
1105
|
+
_setup.SetSimSetupInfo(_hfssSimSetupInfo)
|
|
1106
|
+
_cutout.AddSimulationSetup(_setup) # Add simulation setup to the cutout design
|
|
1107
|
+
id += 1
|
|
1108
|
+
else:
|
|
1109
|
+
_cutout.AddSimulationSetup(_setup) # Add simulation setup to the cutout design
|
|
1110
|
+
|
|
1111
|
+
def _create_cutout_legacy(
|
|
1112
|
+
self,
|
|
1113
|
+
):
|
|
1114
|
+
_poly = self._extent()
|
|
1115
|
+
# Create new cutout cell/design
|
|
1116
|
+
# validate nets in layout
|
|
1117
|
+
net_signals = [net for net in self._edb.layout.nets if net.name in self.signals]
|
|
1118
|
+
|
|
1119
|
+
# reference nets in layout
|
|
1120
|
+
ref_nets = [net for net in self._edb.layout.nets if net.name in self.references]
|
|
1121
|
+
|
|
1122
|
+
# validate references in layout
|
|
1123
|
+
_netsClip = convert_py_list_to_net_list(
|
|
1124
|
+
[net.api_object for net in self._edb.layout.nets if net.name in self.references]
|
|
1125
|
+
)
|
|
1126
|
+
included_nets_list = net_signals + ref_nets
|
|
1127
|
+
included_nets = convert_py_list_to_net_list(
|
|
1128
|
+
[net.api_object for net in self._edb.layout.nets if net.name in included_nets_list]
|
|
1129
|
+
)
|
|
1130
|
+
_cutout = self._edb.active_cell.CutOut(included_nets, _netsClip, _poly, True)
|
|
1131
|
+
# Analysis setups do not come over with the clipped design copy,
|
|
1132
|
+
# so add the analysis setups from the original here.
|
|
1133
|
+
self._add_setups(_cutout)
|
|
1134
|
+
|
|
1135
|
+
_dbCells = [_cutout]
|
|
1136
|
+
if self.output_file:
|
|
1137
|
+
db2 = self._edb.core.Database.Create(self.output_file)
|
|
1138
|
+
_success = db2.Save()
|
|
1139
|
+
_dbCells = convert_py_list_to_net_list(_dbCells)
|
|
1140
|
+
db2.CopyCells(_dbCells) # Copies cutout cell/design to db2 project
|
|
1141
|
+
if len(list(db2.CircuitCells)) > 0:
|
|
1142
|
+
for net in list(list(db2.CircuitCells)[0].GetLayout().Nets):
|
|
1143
|
+
if not net.GetName() in included_nets_list:
|
|
1144
|
+
net.Delete()
|
|
1145
|
+
_success = db2.Save()
|
|
1146
|
+
for c in list(self._edb._db.TopCircuitCells):
|
|
1147
|
+
if c.GetName() == _cutout.GetName():
|
|
1148
|
+
c.Delete()
|
|
1149
|
+
if self.open_cutout_at_end: # pragma: no cover
|
|
1150
|
+
self._edb._db = db2
|
|
1151
|
+
self._edb.edbpath = self.output_file
|
|
1152
|
+
self._edb._active_cell = list(self._edb.top_circuit_cells)[0]
|
|
1153
|
+
self._edb.edbpath = self._edb.directory
|
|
1154
|
+
self._edb._init_objects()
|
|
1155
|
+
if self.remove_single_pin_components:
|
|
1156
|
+
self._edb.components.delete_single_pin_rlc()
|
|
1157
|
+
self.logger.info_timer("Single Pins components deleted")
|
|
1158
|
+
self._edb.components.refresh_components()
|
|
1159
|
+
else:
|
|
1160
|
+
if self.remove_single_pin_components:
|
|
1161
|
+
self._edb.components.delete_single_pin_rlc()
|
|
1162
|
+
self.logger.info_timer("Single Pins components deleted")
|
|
1163
|
+
self._edb.components.refresh_components()
|
|
1164
|
+
db2.Close()
|
|
1165
|
+
source = os.path.join(self.output_file, "edb.def.tmp")
|
|
1166
|
+
target = os.path.join(self.output_file, "edb.def")
|
|
1167
|
+
self._edb._wait_for_file_release(file_to_release=self.output_file)
|
|
1168
|
+
if os.path.exists(source) and not os.path.exists(target):
|
|
1169
|
+
try:
|
|
1170
|
+
shutil.copy(source, target)
|
|
1171
|
+
except Exception as e:
|
|
1172
|
+
self.logger.error(f"Failed to copy {source} to {target} - {type(e).__name__}: {str(e)}")
|
|
1173
|
+
elif self.open_cutout_at_end:
|
|
1174
|
+
self._edb._active_cell = _cutout
|
|
1175
|
+
self._edb._init_objects()
|
|
1176
|
+
if self.__remove_single_pin_components:
|
|
1177
|
+
self._edb.components.delete_single_pin_rlc()
|
|
1178
|
+
self.logger.info_timer("Single Pins components deleted")
|
|
1179
|
+
self._edb.components.refresh_components()
|
|
1180
|
+
return [[pt.X.ToDouble(), pt.Y.ToDouble()] for pt in list(_poly.GetPolygonWithoutArcs().Points)]
|
|
1181
|
+
|
|
1182
|
+
def _create_cutout_multithread(
|
|
1183
|
+
self,
|
|
1184
|
+
):
|
|
1185
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
1186
|
+
|
|
1187
|
+
if self.output_file:
|
|
1188
|
+
self._edb.save_as(self.output_file)
|
|
1189
|
+
self.logger.info("Cutout Multithread started.")
|
|
1190
|
+
self.expansion_size = self._edb.value(self.expansion_size)
|
|
1191
|
+
|
|
1192
|
+
timer_start = self.logger.reset_timer()
|
|
1193
|
+
if self.custom_extent:
|
|
1194
|
+
if not self.signals and not self.references:
|
|
1195
|
+
reference_list = self._edb.nets.netlist[::]
|
|
1196
|
+
all_list = reference_list
|
|
1197
|
+
else:
|
|
1198
|
+
reference_list = self.signals + self.references
|
|
1199
|
+
all_list = reference_list
|
|
1200
|
+
else:
|
|
1201
|
+
all_list = self.signals + self.references
|
|
1202
|
+
reference_list = self.references
|
|
1203
|
+
|
|
1204
|
+
pins_to_preserve, nets_to_preserve = self.pins_to_preserve()
|
|
1205
|
+
for i in self._edb.nets.nets.values():
|
|
1206
|
+
name = i.name
|
|
1207
|
+
if name not in all_list and name not in nets_to_preserve:
|
|
1208
|
+
i.net_object.Delete()
|
|
1209
|
+
|
|
1210
|
+
reference_pinsts = []
|
|
1211
|
+
reference_prims = []
|
|
1212
|
+
reference_paths = []
|
|
1213
|
+
pins_to_delete = []
|
|
1214
|
+
|
|
1215
|
+
def check_instances(item):
|
|
1216
|
+
net_name = item.net_name
|
|
1217
|
+
item_id = item.id
|
|
1218
|
+
if net_name not in all_list and item_id not in pins_to_preserve:
|
|
1219
|
+
pins_to_delete.append(item)
|
|
1220
|
+
elif net_name in reference_list and item_id not in pins_to_preserve:
|
|
1221
|
+
reference_pinsts.append(item)
|
|
1222
|
+
|
|
1223
|
+
with ThreadPoolExecutor(self.number_of_threads) as pool:
|
|
1224
|
+
pool.map(lambda item: check_instances(item), self._edb.layout.padstack_instances)
|
|
1225
|
+
|
|
1226
|
+
for i in pins_to_delete:
|
|
1227
|
+
i.delete()
|
|
1228
|
+
|
|
1229
|
+
prim_to_delete = []
|
|
1230
|
+
|
|
1231
|
+
def check_prims(item):
|
|
1232
|
+
if item:
|
|
1233
|
+
net_name = item.net_name
|
|
1234
|
+
if net_name not in all_list:
|
|
1235
|
+
prim_to_delete.append(item)
|
|
1236
|
+
elif net_name in reference_list and not item.is_void:
|
|
1237
|
+
if self.keep_lines_as_path and item.type == "Path":
|
|
1238
|
+
reference_paths.append(item)
|
|
1239
|
+
else:
|
|
1240
|
+
reference_prims.append(item)
|
|
1241
|
+
|
|
1242
|
+
with ThreadPoolExecutor(self.number_of_threads) as pool:
|
|
1243
|
+
pool.map(lambda item: check_prims(item), self._edb.modeler.primitives)
|
|
1244
|
+
|
|
1245
|
+
for i in prim_to_delete:
|
|
1246
|
+
i.delete()
|
|
1247
|
+
|
|
1248
|
+
self.logger.info_timer("Net clean up")
|
|
1249
|
+
self.logger.reset_timer()
|
|
1250
|
+
|
|
1251
|
+
_poly = self._extent()
|
|
1252
|
+
if not _poly or _poly.IsNull():
|
|
1253
|
+
self.logger.error("Failed to create Extent.")
|
|
1254
|
+
return []
|
|
1255
|
+
self.logger.info_timer("Extent Creation")
|
|
1256
|
+
self.logger.reset_timer()
|
|
1257
|
+
|
|
1258
|
+
_poly_list = convert_py_list_to_net_list([_poly])
|
|
1259
|
+
prims_to_delete = []
|
|
1260
|
+
poly_to_create = []
|
|
1261
|
+
pins_to_delete = []
|
|
1262
|
+
|
|
1263
|
+
def intersect(poly1, poly2):
|
|
1264
|
+
if not isinstance(poly2, list):
|
|
1265
|
+
poly2 = [poly2]
|
|
1266
|
+
return list(
|
|
1267
|
+
poly1.Intersect(
|
|
1268
|
+
convert_py_list_to_net_list(poly1),
|
|
1269
|
+
convert_py_list_to_net_list(poly2),
|
|
1270
|
+
)
|
|
1271
|
+
)
|
|
1272
|
+
|
|
1273
|
+
def subtract(poly, voids):
|
|
1274
|
+
return poly.Subtract(convert_py_list_to_net_list(poly), convert_py_list_to_net_list(voids))
|
|
1275
|
+
|
|
1276
|
+
def clip_path(path):
|
|
1277
|
+
pdata = path.polygon_data._edb_object
|
|
1278
|
+
int_data = _poly.GetIntersectionType(pdata)
|
|
1279
|
+
if int_data == 0:
|
|
1280
|
+
prims_to_delete.append(path)
|
|
1281
|
+
return
|
|
1282
|
+
result = path._edb_object.SetClipInfo(_poly, True)
|
|
1283
|
+
if not result:
|
|
1284
|
+
self.logger.info("Failed to clip path {}. Clipping as polygon.".format(path.id))
|
|
1285
|
+
reference_prims.append(path)
|
|
1286
|
+
|
|
1287
|
+
def clean_prim(prim_1): # pragma: no cover
|
|
1288
|
+
pdata = prim_1.polygon_data._edb_object
|
|
1289
|
+
int_data = _poly.GetIntersectionType(pdata)
|
|
1290
|
+
if int_data == 2:
|
|
1291
|
+
if not self.include_voids_in_extents:
|
|
1292
|
+
return
|
|
1293
|
+
skip = False
|
|
1294
|
+
for hole in list(_poly.Holes):
|
|
1295
|
+
if hole.GetIntersectionType(pdata) == 0:
|
|
1296
|
+
prims_to_delete.append(prim_1)
|
|
1297
|
+
return
|
|
1298
|
+
elif hole.GetIntersectionType(pdata) == 1:
|
|
1299
|
+
skip = True
|
|
1300
|
+
if skip:
|
|
1301
|
+
return
|
|
1302
|
+
elif int_data == 0:
|
|
1303
|
+
prims_to_delete.append(prim_1)
|
|
1304
|
+
return
|
|
1305
|
+
list_poly = intersect(_poly, pdata)
|
|
1306
|
+
if list_poly:
|
|
1307
|
+
net = prim_1.net_name
|
|
1308
|
+
voids = prim_1.voids
|
|
1309
|
+
for p in list_poly:
|
|
1310
|
+
if p.IsNull():
|
|
1311
|
+
continue
|
|
1312
|
+
# points = list(p.Points)
|
|
1313
|
+
list_void = []
|
|
1314
|
+
if voids:
|
|
1315
|
+
voids_data = [void.polygon_data._edb_object for void in voids]
|
|
1316
|
+
list_prims = subtract(p, voids_data)
|
|
1317
|
+
for prim in list_prims:
|
|
1318
|
+
if not prim.IsNull():
|
|
1319
|
+
poly_to_create.append([prim, prim_1.layer.name, net, list_void])
|
|
1320
|
+
else:
|
|
1321
|
+
poly_to_create.append([p, prim_1.layer.name, net, list_void])
|
|
1322
|
+
|
|
1323
|
+
prims_to_delete.append(prim_1)
|
|
1324
|
+
|
|
1325
|
+
def pins_clean(pinst):
|
|
1326
|
+
if not pinst.in_polygon(
|
|
1327
|
+
_poly, include_partial=self.include_partial_instances, simple_check=self.simple_pad_check
|
|
1328
|
+
):
|
|
1329
|
+
pins_to_delete.append(pinst)
|
|
1330
|
+
|
|
1331
|
+
if not self.simple_pad_check:
|
|
1332
|
+
pad_cores = 1
|
|
1333
|
+
else:
|
|
1334
|
+
pad_cores = self.number_of_threads
|
|
1335
|
+
with ThreadPoolExecutor(pad_cores) as pool:
|
|
1336
|
+
pool.map(lambda item: pins_clean(item), reference_pinsts)
|
|
1337
|
+
|
|
1338
|
+
for pin in pins_to_delete:
|
|
1339
|
+
pin.delete()
|
|
1340
|
+
|
|
1341
|
+
self.logger.info_timer("{} Padstack Instances deleted.".format(len(pins_to_delete)))
|
|
1342
|
+
self.logger.reset_timer()
|
|
1343
|
+
|
|
1344
|
+
with ThreadPoolExecutor(self.number_of_threads) as pool:
|
|
1345
|
+
pool.map(lambda item: clip_path(item), reference_paths)
|
|
1346
|
+
with ThreadPoolExecutor(self.number_of_threads) as pool:
|
|
1347
|
+
pool.map(lambda item: clean_prim(item), reference_prims)
|
|
1348
|
+
|
|
1349
|
+
for el in poly_to_create:
|
|
1350
|
+
self._edb.modeler.create_polygon(el[0], el[1], net_name=el[2], voids=el[3])
|
|
1351
|
+
|
|
1352
|
+
for prim in prims_to_delete:
|
|
1353
|
+
prim.delete()
|
|
1354
|
+
|
|
1355
|
+
self.logger.info_timer("{} Primitives deleted.".format(len(prims_to_delete)))
|
|
1356
|
+
self.logger.reset_timer()
|
|
1357
|
+
|
|
1358
|
+
i = 0
|
|
1359
|
+
for _, val in self._edb.components.instances.items():
|
|
1360
|
+
if val.numpins == 0:
|
|
1361
|
+
val.edbcomponent.Delete()
|
|
1362
|
+
i += 1
|
|
1363
|
+
i += 1
|
|
1364
|
+
self.logger.info("{} components deleted".format(i))
|
|
1365
|
+
if self.remove_single_pin_components:
|
|
1366
|
+
self._edb.components.delete_single_pin_rlc()
|
|
1367
|
+
self.logger.info_timer("Single Pins components deleted")
|
|
1368
|
+
|
|
1369
|
+
self._edb.components.refresh_components()
|
|
1370
|
+
if self.output_file:
|
|
1371
|
+
self._edb.save_edb()
|
|
1372
|
+
self.logger.info_timer("Cutout completed.", timer_start)
|
|
1373
|
+
self.logger.reset_timer()
|
|
1374
|
+
return [[pt.X.ToDouble(), pt.Y.ToDouble()] for pt in list(_poly.GetPolygonWithoutArcs().Points)]
|
|
1375
|
+
|
|
1376
|
+
def run(self):
|
|
1377
|
+
if not self.use_pyaedt_cutout:
|
|
1378
|
+
return self._create_cutout_legacy()
|
|
1379
|
+
else:
|
|
1380
|
+
out_file = self.output_file
|
|
1381
|
+
expansion_size = self.expansion_size
|
|
1382
|
+
if not self.smart_cutout:
|
|
1383
|
+
self.maximum_iterations = 1
|
|
1384
|
+
self.expansion_factor = 0
|
|
1385
|
+
elif self.expansion_factor > 0:
|
|
1386
|
+
expansion_size = self.calculate_initial_extent(self.expansion_factor)
|
|
1387
|
+
self._edb.save()
|
|
1388
|
+
self.output_file = self._edb.edbpath.replace(".aedb", "_smart_cutout_temp.aedb")
|
|
1389
|
+
|
|
1390
|
+
legacy_path = self._edb.edbpath
|
|
1391
|
+
start = time.time()
|
|
1392
|
+
working_cutout = False
|
|
1393
|
+
i = 1
|
|
1394
|
+
expansion = self._edb.value(expansion_size)
|
|
1395
|
+
result = False
|
|
1396
|
+
while i <= self.maximum_iterations:
|
|
1397
|
+
self.logger.info("-----------------------------------------")
|
|
1398
|
+
self.logger.info(f"Trying cutout with {expansion * 1e3}mm expansion size")
|
|
1399
|
+
self.logger.info("-----------------------------------------")
|
|
1400
|
+
result = self._create_cutout_multithread()
|
|
1401
|
+
if result:
|
|
1402
|
+
if self.smart_cutout:
|
|
1403
|
+
if not self._edb.are_port_reference_terminals_connected():
|
|
1404
|
+
raise RuntimeError("Smart cutout failed.")
|
|
1405
|
+
self.output_file = out_file
|
|
1406
|
+
if self.output_file:
|
|
1407
|
+
self._edb.save_as(self.output_file)
|
|
1408
|
+
else:
|
|
1409
|
+
self._edb.save_as(legacy_path)
|
|
1410
|
+
working_cutout = True
|
|
1411
|
+
if not self.open_cutout_at_end and self._edb.edbpath != legacy_path:
|
|
1412
|
+
self._edb.close()
|
|
1413
|
+
self._edb.edbpath = legacy_path
|
|
1414
|
+
self._edb.open_edb()
|
|
1415
|
+
break
|
|
1416
|
+
self._edb.close()
|
|
1417
|
+
self._edb.edbpath = legacy_path
|
|
1418
|
+
self._edb.open_edb()
|
|
1419
|
+
i += 1
|
|
1420
|
+
expansion = expansion_size * i
|
|
1421
|
+
if working_cutout:
|
|
1422
|
+
msg = "Cutout completed in {} iterations with expansion size of {}mm".format(i, expansion * 1e3)
|
|
1423
|
+
self.logger.info_timer(msg, start)
|
|
1424
|
+
else:
|
|
1425
|
+
msg = "Cutout failed after {} iterations and expansion size of {}mm".format(i, expansion * 1e3)
|
|
1426
|
+
self.logger.info_timer(msg, start)
|
|
1427
|
+
return False
|
|
1428
|
+
return result
|