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.

Files changed (318) hide show
  1. pyedb/__init__.py +23 -1
  2. pyedb/common/__init__.py +21 -0
  3. pyedb/common/nets.py +22 -0
  4. pyedb/component_libraries/ansys_components.py +22 -0
  5. pyedb/configuration/__init__.py +21 -0
  6. pyedb/configuration/cfg_boundaries.py +1 -1
  7. pyedb/configuration/cfg_common.py +1 -1
  8. pyedb/configuration/cfg_components.py +36 -8
  9. pyedb/configuration/cfg_data.py +1 -1
  10. pyedb/configuration/cfg_general.py +1 -1
  11. pyedb/configuration/cfg_modeler.py +1 -1
  12. pyedb/configuration/cfg_nets.py +1 -1
  13. pyedb/configuration/cfg_operations.py +1 -1
  14. pyedb/configuration/cfg_package_definition.py +1 -1
  15. pyedb/configuration/cfg_padstacks.py +1 -1
  16. pyedb/configuration/cfg_pin_groups.py +1 -1
  17. pyedb/configuration/cfg_ports_sources.py +3 -2
  18. pyedb/configuration/cfg_s_parameter_models.py +1 -1
  19. pyedb/configuration/cfg_setup.py +5 -1
  20. pyedb/configuration/cfg_spice_models.py +1 -1
  21. pyedb/configuration/cfg_stackup.py +1 -1
  22. pyedb/configuration/cfg_terminals.py +22 -0
  23. pyedb/configuration/configuration.py +6 -5
  24. pyedb/dotnet/__init__.py +21 -0
  25. pyedb/dotnet/clr_module.py +22 -0
  26. pyedb/dotnet/database/Variables.py +1 -1
  27. pyedb/dotnet/database/__init__.py +22 -0
  28. pyedb/dotnet/database/cell/__init__.py +21 -0
  29. pyedb/dotnet/database/cell/connectable.py +1 -1
  30. pyedb/dotnet/database/cell/hierarchy/__init__.py +21 -0
  31. pyedb/dotnet/database/cell/hierarchy/component.py +9 -7
  32. pyedb/dotnet/database/cell/hierarchy/hierarchy_obj.py +1 -1
  33. pyedb/dotnet/database/cell/hierarchy/model.py +2 -29
  34. pyedb/dotnet/database/cell/hierarchy/netlist_model.py +1 -1
  35. pyedb/dotnet/database/cell/hierarchy/pin_pair_model.py +1 -1
  36. pyedb/dotnet/database/cell/hierarchy/s_parameter_model.py +11 -15
  37. pyedb/dotnet/database/cell/hierarchy/spice_model.py +14 -8
  38. pyedb/dotnet/database/cell/layout.py +5 -4
  39. pyedb/dotnet/database/cell/layout_obj.py +1 -1
  40. pyedb/dotnet/database/cell/primitive/__init__.py +22 -0
  41. pyedb/dotnet/database/cell/primitive/bondwire.py +1 -1
  42. pyedb/dotnet/database/cell/primitive/path.py +1 -1
  43. pyedb/dotnet/database/cell/primitive/primitive.py +1 -1
  44. pyedb/dotnet/database/cell/terminal/__init__.py +21 -0
  45. pyedb/dotnet/database/cell/terminal/bundle_terminal.py +1 -1
  46. pyedb/dotnet/database/cell/terminal/edge_terminal.py +1 -1
  47. pyedb/dotnet/database/cell/terminal/padstack_instance_terminal.py +1 -1
  48. pyedb/dotnet/database/cell/terminal/pingroup_terminal.py +1 -1
  49. pyedb/dotnet/database/cell/terminal/point_terminal.py +1 -1
  50. pyedb/dotnet/database/cell/terminal/terminal.py +7 -2
  51. pyedb/dotnet/database/cell/voltage_regulator.py +1 -1
  52. pyedb/dotnet/database/components.py +6 -2
  53. pyedb/dotnet/database/definition/__init__.py +21 -0
  54. pyedb/dotnet/database/definition/component_def.py +1 -1
  55. pyedb/dotnet/database/definition/component_model.py +1 -1
  56. pyedb/dotnet/database/definition/definition_obj.py +1 -1
  57. pyedb/dotnet/database/definition/definitions.py +1 -1
  58. pyedb/dotnet/database/definition/package_def.py +1 -1
  59. pyedb/dotnet/database/dotnet/__init__.py +21 -0
  60. pyedb/dotnet/database/dotnet/database.py +1 -1
  61. pyedb/dotnet/database/dotnet/primitive.py +1 -1
  62. pyedb/dotnet/database/edb_data/__init__.py +21 -0
  63. pyedb/dotnet/database/edb_data/control_file.py +1 -1
  64. pyedb/dotnet/database/edb_data/design_options.py +1 -1
  65. pyedb/dotnet/database/edb_data/edbvalue.py +1 -1
  66. pyedb/dotnet/database/edb_data/hfss_extent_info.py +1 -1
  67. pyedb/dotnet/database/edb_data/layer_data.py +1 -1
  68. pyedb/dotnet/database/edb_data/nets_data.py +1 -1
  69. pyedb/dotnet/database/edb_data/padstacks_data.py +6 -4
  70. pyedb/dotnet/database/edb_data/ports.py +1 -1
  71. pyedb/dotnet/database/edb_data/primitives_data.py +1 -1
  72. pyedb/dotnet/database/edb_data/raptor_x_simulation_setup_data.py +1 -1
  73. pyedb/dotnet/database/edb_data/simulation_configuration.py +1 -1
  74. pyedb/dotnet/database/edb_data/sources.py +1 -1
  75. pyedb/dotnet/database/edb_data/utilities.py +1 -1
  76. pyedb/dotnet/database/edb_data/variables.py +1 -1
  77. pyedb/dotnet/database/general.py +1 -1
  78. pyedb/dotnet/database/geometry/__init__.py +21 -0
  79. pyedb/dotnet/database/geometry/point_data.py +1 -1
  80. pyedb/dotnet/database/geometry/polygon_data.py +1 -1
  81. pyedb/dotnet/database/hfss.py +1 -1
  82. pyedb/dotnet/database/layout_obj_instance.py +1 -1
  83. pyedb/dotnet/database/layout_validation.py +1 -1
  84. pyedb/dotnet/database/materials.py +1 -1
  85. pyedb/dotnet/database/modeler.py +3 -2
  86. pyedb/dotnet/database/net_class.py +1 -1
  87. pyedb/dotnet/database/nets.py +1 -1
  88. pyedb/dotnet/database/padstack.py +188 -2
  89. pyedb/dotnet/database/sim_setup_data/__init__.py +22 -0
  90. pyedb/dotnet/database/sim_setup_data/data/__init__.py +22 -0
  91. pyedb/dotnet/database/sim_setup_data/data/adaptive_frequency_data.py +1 -1
  92. pyedb/dotnet/database/sim_setup_data/data/mesh_operation.py +1 -1
  93. pyedb/dotnet/database/sim_setup_data/data/settings.py +1 -1
  94. pyedb/dotnet/database/sim_setup_data/data/sim_setup_info.py +1 -1
  95. pyedb/dotnet/database/sim_setup_data/data/simulation_settings.py +1 -1
  96. pyedb/dotnet/database/sim_setup_data/data/siw_dc_ir_settings.py +1 -1
  97. pyedb/dotnet/database/sim_setup_data/data/sweep_data.py +1 -1
  98. pyedb/dotnet/database/sim_setup_data/io/__init__.py +21 -0
  99. pyedb/dotnet/database/sim_setup_data/io/siwave.py +1 -1
  100. pyedb/dotnet/database/siwave.py +1 -1
  101. pyedb/dotnet/database/stackup.py +1 -1
  102. pyedb/dotnet/database/utilities/__init__.py +22 -0
  103. pyedb/dotnet/database/utilities/heatsink.py +23 -0
  104. pyedb/dotnet/database/utilities/hfss_simulation_setup.py +1 -1
  105. pyedb/dotnet/database/utilities/obj_base.py +1 -1
  106. pyedb/dotnet/database/utilities/simulation_setup.py +1 -1
  107. pyedb/dotnet/database/utilities/siwave_cpa_simulation_setup.py +22 -0
  108. pyedb/dotnet/database/utilities/siwave_simulation_setup.py +22 -0
  109. pyedb/dotnet/database/utilities/value.py +1 -1
  110. pyedb/dotnet/edb.py +119 -123
  111. pyedb/edb_logger.py +1 -1
  112. pyedb/exceptions.py +22 -0
  113. pyedb/extensions/__init__.py +21 -0
  114. pyedb/extensions/create_cell_array.py +1 -1
  115. pyedb/extensions/via_design_backend.py +22 -0
  116. pyedb/generic/__init__.py +21 -0
  117. pyedb/generic/constants.py +1 -1
  118. pyedb/generic/data_handlers.py +22 -0
  119. pyedb/generic/design_types.py +1 -1
  120. pyedb/generic/filesystem.py +22 -0
  121. pyedb/generic/general_methods.py +22 -1
  122. pyedb/generic/grpc_warnings.py +22 -0
  123. pyedb/generic/plot.py +22 -0
  124. pyedb/generic/process.py +29 -2
  125. pyedb/generic/settings.py +1 -1
  126. pyedb/grpc/__init__.py +21 -0
  127. pyedb/grpc/database/__init__.py +21 -0
  128. pyedb/grpc/database/_typing.py +21 -0
  129. pyedb/grpc/database/components.py +9 -8
  130. pyedb/grpc/database/control_file.py +1 -1
  131. pyedb/grpc/database/definition/__init__.py +21 -0
  132. pyedb/grpc/database/definition/component_def.py +1 -1
  133. pyedb/grpc/database/definition/component_model.py +1 -1
  134. pyedb/grpc/database/definition/component_pin.py +1 -1
  135. pyedb/grpc/database/definition/materials.py +2 -2
  136. pyedb/grpc/database/definition/n_port_component_model.py +1 -1
  137. pyedb/grpc/database/definition/package_def.py +1 -1
  138. pyedb/grpc/database/definition/padstack_def.py +17 -10
  139. pyedb/grpc/database/definitions.py +1 -1
  140. pyedb/grpc/database/general.py +1 -1
  141. pyedb/grpc/database/geometry/__init__.py +21 -0
  142. pyedb/grpc/database/geometry/arc_data.py +1 -1
  143. pyedb/grpc/database/geometry/point_3d_data.py +1 -1
  144. pyedb/grpc/database/geometry/point_data.py +1 -1
  145. pyedb/grpc/database/geometry/polygon_data.py +1 -1
  146. pyedb/grpc/database/hfss.py +1 -1
  147. pyedb/grpc/database/hierarchy/__init__.py +21 -0
  148. pyedb/grpc/database/hierarchy/component.py +1 -1
  149. pyedb/grpc/database/hierarchy/model.py +1 -1
  150. pyedb/grpc/database/hierarchy/netlist_model.py +1 -1
  151. pyedb/grpc/database/hierarchy/pin_pair_model.py +1 -1
  152. pyedb/grpc/database/hierarchy/pingroup.py +1 -1
  153. pyedb/grpc/database/hierarchy/s_parameter_model.py +1 -1
  154. pyedb/grpc/database/hierarchy/spice_model.py +1 -1
  155. pyedb/grpc/database/layers/__init__.py +21 -0
  156. pyedb/grpc/database/layers/layer.py +22 -0
  157. pyedb/grpc/database/layers/stackup_layer.py +1 -1
  158. pyedb/grpc/database/layout/__init__.py +21 -0
  159. pyedb/grpc/database/layout/cell.py +1 -1
  160. pyedb/grpc/database/layout/layout.py +1 -1
  161. pyedb/grpc/database/layout/voltage_regulator.py +1 -1
  162. pyedb/grpc/database/layout_validation.py +1 -1
  163. pyedb/grpc/database/modeler.py +31 -9
  164. pyedb/grpc/database/net/__init__.py +21 -0
  165. pyedb/grpc/database/net/differential_pair.py +1 -1
  166. pyedb/grpc/database/net/extended_net.py +1 -1
  167. pyedb/grpc/database/net/net.py +1 -1
  168. pyedb/grpc/database/net/net_class.py +1 -1
  169. pyedb/grpc/database/nets.py +1 -1
  170. pyedb/grpc/database/padstacks.py +209 -9
  171. pyedb/grpc/database/ports/__init__.py +21 -0
  172. pyedb/grpc/database/ports/ports.py +1 -1
  173. pyedb/grpc/database/primitive/__init__.py +22 -0
  174. pyedb/grpc/database/primitive/bondwire.py +1 -1
  175. pyedb/grpc/database/primitive/circle.py +1 -1
  176. pyedb/grpc/database/primitive/padstack_instance.py +111 -16
  177. pyedb/grpc/database/primitive/path.py +1 -1
  178. pyedb/grpc/database/primitive/polygon.py +6 -4
  179. pyedb/grpc/database/primitive/primitive.py +1 -6
  180. pyedb/grpc/database/primitive/rectangle.py +1 -1
  181. pyedb/grpc/database/simulation_setup/__init__.py +21 -0
  182. pyedb/grpc/database/simulation_setup/adaptive_frequency.py +1 -1
  183. pyedb/grpc/database/simulation_setup/hfss_advanced_meshing_settings.py +1 -1
  184. pyedb/grpc/database/simulation_setup/hfss_advanced_settings.py +1 -1
  185. pyedb/grpc/database/simulation_setup/hfss_dcr_settings.py +1 -1
  186. pyedb/grpc/database/simulation_setup/hfss_general_settings.py +1 -1
  187. pyedb/grpc/database/simulation_setup/hfss_settings_options.py +1 -1
  188. pyedb/grpc/database/simulation_setup/hfss_simulation_settings.py +1 -1
  189. pyedb/grpc/database/simulation_setup/hfss_simulation_setup.py +1 -1
  190. pyedb/grpc/database/simulation_setup/hfss_solver_settings.py +1 -1
  191. pyedb/grpc/database/simulation_setup/mesh_operation.py +1 -1
  192. pyedb/grpc/database/simulation_setup/raptor_x_advanced_settings.py +1 -1
  193. pyedb/grpc/database/simulation_setup/raptor_x_general_settings.py +1 -1
  194. pyedb/grpc/database/simulation_setup/raptor_x_simulation_settings.py +1 -1
  195. pyedb/grpc/database/simulation_setup/raptor_x_simulation_setup.py +1 -1
  196. pyedb/grpc/database/simulation_setup/siwave_cpa_simulation_setup.py +22 -0
  197. pyedb/grpc/database/simulation_setup/siwave_dcir_simulation_setup.py +1 -1
  198. pyedb/grpc/database/simulation_setup/siwave_simulation_setup.py +1 -1
  199. pyedb/grpc/database/simulation_setup/sweep_data.py +1 -1
  200. pyedb/grpc/database/siwave.py +1 -1
  201. pyedb/grpc/database/source_excitations.py +1 -1
  202. pyedb/grpc/database/stackup.py +1 -1
  203. pyedb/grpc/database/terminal/__init__.py +21 -0
  204. pyedb/grpc/database/terminal/bundle_terminal.py +1 -1
  205. pyedb/grpc/database/terminal/edge_terminal.py +1 -1
  206. pyedb/grpc/database/terminal/padstack_instance_terminal.py +1 -1
  207. pyedb/grpc/database/terminal/pingroup_terminal.py +1 -1
  208. pyedb/grpc/database/terminal/point_terminal.py +1 -1
  209. pyedb/grpc/database/terminal/terminal.py +1 -1
  210. pyedb/grpc/database/utility/__init__.py +22 -0
  211. pyedb/grpc/database/utility/constants.py +1 -1
  212. pyedb/grpc/database/utility/heat_sink.py +1 -1
  213. pyedb/grpc/database/utility/hfss_extent_info.py +1 -1
  214. pyedb/grpc/database/utility/layout_statistics.py +1 -1
  215. pyedb/grpc/database/utility/rlc.py +1 -1
  216. pyedb/grpc/database/utility/sources.py +1 -1
  217. pyedb/grpc/database/utility/sweep_data_distribution.py +1 -1
  218. pyedb/grpc/database/utility/value.py +1 -1
  219. pyedb/grpc/database/utility/xml_control_file.py +1 -1
  220. pyedb/grpc/edb.py +230 -990
  221. pyedb/grpc/edb_init.py +1 -1
  222. pyedb/grpc/rpc_session.py +17 -4
  223. pyedb/ipc2581/__init__.py +21 -0
  224. pyedb/ipc2581/bom/__init__.py +21 -0
  225. pyedb/ipc2581/bom/bom.py +1 -1
  226. pyedb/ipc2581/bom/bom_item.py +1 -1
  227. pyedb/ipc2581/bom/characteristics.py +1 -1
  228. pyedb/ipc2581/bom/refdes.py +1 -1
  229. pyedb/ipc2581/content/__init__.py +21 -0
  230. pyedb/ipc2581/content/color.py +1 -1
  231. pyedb/ipc2581/content/content.py +1 -1
  232. pyedb/ipc2581/content/dictionary_color.py +1 -1
  233. pyedb/ipc2581/content/dictionary_fill.py +1 -1
  234. pyedb/ipc2581/content/dictionary_line.py +1 -1
  235. pyedb/ipc2581/content/entry_color.py +1 -1
  236. pyedb/ipc2581/content/entry_line.py +1 -1
  237. pyedb/ipc2581/content/fill.py +1 -1
  238. pyedb/ipc2581/content/layer_ref.py +1 -1
  239. pyedb/ipc2581/content/standard_geometries_dictionary.py +1 -1
  240. pyedb/ipc2581/ecad/__init__.py +21 -0
  241. pyedb/ipc2581/ecad/cad_data/__init__.py +21 -0
  242. pyedb/ipc2581/ecad/cad_data/assembly_drawing.py +1 -1
  243. pyedb/ipc2581/ecad/cad_data/cad_data.py +1 -1
  244. pyedb/ipc2581/ecad/cad_data/component.py +1 -1
  245. pyedb/ipc2581/ecad/cad_data/drill.py +1 -1
  246. pyedb/ipc2581/ecad/cad_data/feature.py +1 -1
  247. pyedb/ipc2581/ecad/cad_data/layer.py +1 -1
  248. pyedb/ipc2581/ecad/cad_data/layer_feature.py +1 -1
  249. pyedb/ipc2581/ecad/cad_data/logical_net.py +1 -1
  250. pyedb/ipc2581/ecad/cad_data/outline.py +1 -1
  251. pyedb/ipc2581/ecad/cad_data/package.py +1 -1
  252. pyedb/ipc2581/ecad/cad_data/padstack_def.py +1 -1
  253. pyedb/ipc2581/ecad/cad_data/padstack_hole_def.py +1 -1
  254. pyedb/ipc2581/ecad/cad_data/padstack_instance.py +1 -1
  255. pyedb/ipc2581/ecad/cad_data/padstack_pad_def.py +1 -1
  256. pyedb/ipc2581/ecad/cad_data/path.py +1 -1
  257. pyedb/ipc2581/ecad/cad_data/phy_net.py +1 -1
  258. pyedb/ipc2581/ecad/cad_data/pin.py +1 -1
  259. pyedb/ipc2581/ecad/cad_data/polygon.py +1 -1
  260. pyedb/ipc2581/ecad/cad_data/profile.py +1 -1
  261. pyedb/ipc2581/ecad/cad_data/stackup.py +1 -1
  262. pyedb/ipc2581/ecad/cad_data/stackup_group.py +1 -1
  263. pyedb/ipc2581/ecad/cad_data/stackup_layer.py +1 -1
  264. pyedb/ipc2581/ecad/cad_data/step.py +1 -1
  265. pyedb/ipc2581/ecad/cad_header.py +1 -1
  266. pyedb/ipc2581/ecad/ecad.py +1 -1
  267. pyedb/ipc2581/ecad/spec.py +1 -1
  268. pyedb/ipc2581/history_record.py +1 -1
  269. pyedb/ipc2581/ipc2581.py +1 -1
  270. pyedb/ipc2581/logistic_header.py +1 -1
  271. pyedb/libraries/common.py +1 -1
  272. pyedb/libraries/rf_libraries/base_functions.py +1 -1
  273. pyedb/libraries/rf_libraries/planar_antennas.py +1 -1
  274. pyedb/misc/__init__.py +21 -0
  275. pyedb/misc/aedtlib_personalib_install.py +1 -1
  276. pyedb/misc/decorators.py +22 -0
  277. pyedb/misc/downloads.py +1 -1
  278. pyedb/misc/misc.py +1 -1
  279. pyedb/misc/siw_feature_config/__init__.py +21 -0
  280. pyedb/misc/siw_feature_config/emc/__init__.py +21 -0
  281. pyedb/misc/siw_feature_config/emc/component_tags.py +22 -0
  282. pyedb/misc/siw_feature_config/emc/net_tags.py +22 -0
  283. pyedb/misc/siw_feature_config/emc/tag_library.py +22 -0
  284. pyedb/misc/siw_feature_config/emc/xml_generic.py +22 -0
  285. pyedb/misc/siw_feature_config/emc_rule_checker_settings.py +1 -1
  286. pyedb/misc/siw_feature_config/xtalk_scan/fd_xtalk_scan_config.py +1 -1
  287. pyedb/misc/siw_feature_config/xtalk_scan/impedance_scan_config.py +1 -1
  288. pyedb/misc/siw_feature_config/xtalk_scan/net.py +1 -1
  289. pyedb/misc/siw_feature_config/xtalk_scan/pins.py +1 -1
  290. pyedb/misc/siw_feature_config/xtalk_scan/scan_config.py +1 -1
  291. pyedb/misc/siw_feature_config/xtalk_scan/td_xtalk_config.py +1 -1
  292. pyedb/misc/utilities.py +1 -1
  293. pyedb/modeler/geometry_operators.py +22 -0
  294. pyedb/siwave.py +22 -0
  295. pyedb/siwave_core/__init__.py +21 -0
  296. pyedb/siwave_core/cpa/__init__.py +21 -0
  297. pyedb/siwave_core/cpa/simulation_setup_data_model.py +22 -0
  298. pyedb/siwave_core/icepak.py +1 -1
  299. pyedb/siwave_core/product_properties.py +23 -0
  300. pyedb/workflow.py +22 -0
  301. pyedb/workflows/__init__.py +21 -0
  302. pyedb/workflows/job_manager/__init__.py +21 -0
  303. pyedb/workflows/job_manager/backend/__init__.py +21 -0
  304. pyedb/workflows/job_manager/backend/job_manager_handler.py +910 -0
  305. pyedb/workflows/job_manager/backend/job_submission.py +1169 -0
  306. pyedb/workflows/job_manager/backend/service.py +1663 -0
  307. pyedb/workflows/job_manager/backend/start_service.py +86 -0
  308. pyedb/workflows/job_manager/backend/submit_job_on_scheduler.py +168 -0
  309. pyedb/workflows/job_manager/backend/submit_local_job.py +166 -0
  310. pyedb/workflows/sipi/hfss_auto_configuration.py +1 -1
  311. pyedb/workflows/utilities/__init__.py +21 -0
  312. pyedb/workflows/utilities/cutout.py +1428 -0
  313. pyedb/workflows/utilities/hfss_log_parser.py +446 -0
  314. {pyedb-0.59.0.dist-info → pyedb-0.61.0.dist-info}/METADATA +7 -4
  315. pyedb-0.61.0.dist-info/RECORD +318 -0
  316. {pyedb-0.59.0.dist-info → pyedb-0.61.0.dist-info}/licenses/LICENSE +7 -7
  317. pyedb-0.59.0.dist-info/RECORD +0 -306
  318. {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