Skip to content

Commit f9dffbf

Browse files
authored
FEATURE: Added primitive instance collection support (#510)
1 parent 7511ed5 commit f9dffbf

File tree

12 files changed

+179
-12
lines changed

12 files changed

+179
-12
lines changed

doc/source/api/glossary.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,13 @@ Glossary
6262
- :obj:`str`
6363
- :class:`Layer <ansys.edb.core.layer.layer.Layer>`
6464

65+
NetLike
66+
67+
Any of the following data types that represent a net.
68+
69+
- :obj:`str`
70+
- :class:`Layer <ansys.edb.core.net.net.Net>`
71+
6572

6673
RoughnessModel
6774

doc/source/api/primitive.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Classes
1818
polygon.Polygon
1919
rectangle.Rectangle
2020
text.Text
21+
primitive_instance_collection.PrimitiveInstanceCollection
2122

2223
Enums
2324
-----

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ classifiers = [
2525

2626

2727
dependencies = [
28-
"ansys-api-edb==0.2.dev2",
28+
"ansys-api-edb==0.2.dev3",
2929
"protobuf>=3.19.3,<5",
3030
"grpcio>=1.44.0",
3131
"Django>=4.2.16"

src/ansys/edb/core/inner/factory.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ def _initialize_primitive_type_creator_params_dict():
5656
from ansys.edb.core.primitive.path import Path
5757
from ansys.edb.core.primitive.polygon import Polygon
5858
from ansys.edb.core.primitive.primitive import PrimitiveType
59+
from ansys.edb.core.primitive.primitive_instance_collection import PrimitiveInstanceCollection
5960
from ansys.edb.core.primitive.rectangle import Rectangle
6061
from ansys.edb.core.primitive.text import Text
6162

@@ -67,6 +68,7 @@ def _initialize_primitive_type_creator_params_dict():
6768
PrimitiveType.POLYGON: _CreatorParams(Polygon),
6869
PrimitiveType.RECTANGLE: _CreatorParams(Rectangle),
6970
PrimitiveType.TEXT: _CreatorParams(Text),
71+
PrimitiveType.PRIM_INST_COLLECTION: _CreatorParams(PrimitiveInstanceCollection),
7072
}
7173
return _primitive_type_creator_params_dict
7274

src/ansys/edb/core/inner/parser.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
def to_point_data(fn):
1919
"""Decorate a function that returns a message to return it as a ``PointData`` object."""
20-
return _wraps(fn, _to_point_data)
20+
return _wraps(fn, msg_to_point_data)
2121

2222

2323
def to_point_data_list(fn):
@@ -42,7 +42,7 @@ def to_3_point3d_data(fn):
4242

4343
def to_polygon_data(fn):
4444
"""Decorate a function that returns a message to return it as a ``PolygonData`` object."""
45-
return _wraps(fn, _to_polygon_data)
45+
return _wraps(fn, msg_to_polygon_data)
4646

4747

4848
def to_polygon_data_list(fn):
@@ -107,7 +107,7 @@ def wrapper(*args, **kwargs):
107107
return wrapper_fn(fn)
108108

109109

110-
def _to_point_data(message):
110+
def msg_to_point_data(message):
111111
"""Convert a ``PointMessage`` object to a ``PointData`` object.
112112
113113
Parameters
@@ -135,7 +135,7 @@ def _to_point_data_pair(message):
135135
-------
136136
tuple[:class:`.PointData`, :class:`.PointData`]
137137
"""
138-
return _to_point_data(message.point_0), _to_point_data(message.point_1)
138+
return msg_to_point_data(message.point_0), msg_to_point_data(message.point_1)
139139

140140

141141
def _to_point_data_list(message):
@@ -149,7 +149,7 @@ def _to_point_data_list(message):
149149
-------
150150
list[:class:`.PointData`]
151151
"""
152-
return [_to_point_data(m) for m in message]
152+
return [msg_to_point_data(m) for m in message]
153153

154154

155155
def _to_3_point3d_data(message):
@@ -183,7 +183,7 @@ def _to_point3d_data(message):
183183
return Point3DData(Value(message.x), Value(message.y), Value(message.z))
184184

185185

186-
def _to_polygon_data(message):
186+
def msg_to_polygon_data(message):
187187
"""Convert an arbitrary message to a ``PolygonData`` object if possible.
188188
189189
Parameters
@@ -218,11 +218,11 @@ def _to_polygon_data_list(message):
218218
list[:class:`.PolygonData`]
219219
"""
220220
if hasattr(message, "polygons"):
221-
return [_to_polygon_data(m) for m in message.polygons]
221+
return [msg_to_polygon_data(m) for m in message.polygons]
222222
elif hasattr(message, "points"):
223-
return [_to_polygon_data(m) for m in message.points]
223+
return [msg_to_polygon_data(m) for m in message.points]
224224
else:
225-
return [_to_polygon_data(m) for m in message]
225+
return [msg_to_polygon_data(m) for m in message]
226226

227227

228228
def _to_box(message):
@@ -237,7 +237,7 @@ def _to_box(message):
237237
tuple[:class:`.PointData`, :class:`.PointData`]
238238
"""
239239
if hasattr(message, "lower_left") and hasattr(message, "upper_right"):
240-
return _to_point_data(message.lower_left), _to_point_data(message.upper_right)
240+
return msg_to_point_data(message.lower_left), msg_to_point_data(message.upper_right)
241241

242242

243243
def _to_circle(message):
@@ -254,7 +254,7 @@ def _to_circle(message):
254254
from ansys.edb.core.utility.value import Value
255255

256256
if hasattr(message, "center") and hasattr(message, "radius"):
257-
return _to_point_data(message.center), Value(message.radius)
257+
return msg_to_point_data(message.center), Value(message.radius)
258258

259259

260260
def _to_rlc(message):

src/ansys/edb/core/inner/rpc_info.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,7 @@ def invalidates_cache(self):
528528
"GetBoardBendDefs": _RpcInfo(read_no_cache=True),
529529
"StreamBoardBendDefs": _RpcInfo(read_no_cache=True),
530530
"GetLayoutInstance": _RpcInfo(cache=True),
531+
"CompressPrimitives": _RpcInfo(buffer=True),
531532
},
532533
"ansys.api.edb.v1.LayoutComponentService": {
533534
"ExportLayoutComponent": _RpcInfo(write_no_cache_invalidation=True),
@@ -859,6 +860,13 @@ def invalidates_cache(self):
859860
"IsZonePrimitive": _RpcInfo(cache=True),
860861
"MakeZonePrimitive": _RpcInfo(buffer=True),
861862
},
863+
"ansys.api.edb.v1.PrimitiveInstanceCollectionService": {
864+
"GetGeometry": _RpcInfo(cache=True),
865+
"SetGeometry": _RpcInfo(buffer=True),
866+
"GetPositions": _RpcInfo(read_no_cache=True),
867+
"GetInstantiatedGeometry": _RpcInfo(read_no_cache=True),
868+
"Decompose": _RpcInfo(buffer=True),
869+
},
862870
"ansys.api.edb.v1.RaptorXGeneralSettingsService": {
863871
"GetUseGoldEMSolver": _RpcInfo(cache=True),
864872
"SetUseGoldEMSolver": _RpcInfo(buffer=True),

src/ansys/edb/core/inner/utils.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,13 @@ def add_msgs_to_items(edb_obj_collection_msg):
4343
for streamed_items in unary_streaming_rpc(request):
4444
add_msgs_to_items(streamed_items)
4545
return items
46+
47+
48+
def stream_items_from_server(parser, stream, chunk_items_att_name):
49+
"""Stream all items from the provided unary server stream and convert them to \
50+
the corresponding pyedb-core data type using the provided parser."""
51+
return [
52+
parser(chunk_entry)
53+
for chunk in stream
54+
for chunk_entry in getattr(chunk, chunk_items_att_name)
55+
]

src/ansys/edb/core/layout/layout.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,3 +533,17 @@ def create_mesh_region(
533533
num_y_partitions=num_y_partitions,
534534
)
535535
)
536+
537+
def compress_primitives(self):
538+
"""Compress :class:`primitives <.Primitive>` into \
539+
:class:`primitive instance collections <.PrimitiveInstanceCollection>`.
540+
541+
Primitives whose only geometric difference is location will be compressed. \
542+
For example, a 4x4 grid of rectangles with the same width and height will be \
543+
compressed into one primitive instance collection.
544+
545+
.. note::
546+
Only :class:`.Circle`, :class:`.Rectangle`, and :class:`.Polygon` primitives \
547+
are supported in primitive instance collections.
548+
"""
549+
self.__stub.CompressPrimitives(self.msg)

src/ansys/edb/core/primitive/primitive.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class PrimitiveType(Enum):
2222
TEXT = primitive_pb2.TEXT
2323
PATH_3D = primitive_pb2.PATH_3D
2424
BOARD_BEND = primitive_pb2.BOARD_BEND
25+
PRIM_INST_COLLECTION = primitive_pb2.PRIM_INST_COLLECTION
2526
INVALID_TYPE = primitive_pb2.INVALID_TYPE
2627

2728

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
"""Primitive Instance Collection."""
2+
from __future__ import annotations
3+
4+
from typing import TYPE_CHECKING, List
5+
6+
if TYPE_CHECKING:
7+
from ansys.edb.core.geometry.polygon_data import PolygonData
8+
from ansys.edb.core.geometry.point_data import PointData
9+
from ansys.edb.core.typing import PointLike, LayerLike, NetLike
10+
from ansys.edb.core.layout.layout import Layout
11+
12+
from ansys.api.edb.v1.primitive_instance_collection_pb2 import (
13+
PrimitiveInstanceCollectionDataMessage,
14+
)
15+
from ansys.api.edb.v1.primitive_instance_collection_pb2_grpc import (
16+
PrimitiveInstanceCollectionServiceStub,
17+
)
18+
19+
from ansys.edb.core.inner.layout_obj import LayoutObj
20+
import ansys.edb.core.inner.messages as messages
21+
from ansys.edb.core.inner.parser import msg_to_point_data, msg_to_polygon_data, to_polygon_data
22+
from ansys.edb.core.inner.utils import stream_items_from_server
23+
from ansys.edb.core.session import StubAccessor, StubType
24+
25+
26+
class PrimitiveInstanceCollection(LayoutObj):
27+
"""Efficiently represents large quantities of geometry as \
28+
numerous instantiations of the same geometry at different locations."""
29+
30+
__stub: PrimitiveInstanceCollectionServiceStub = StubAccessor(
31+
StubType.primitive_instance_collection
32+
)
33+
34+
@staticmethod
35+
def _point_request_iterator(points, starting_chunk):
36+
chunk = starting_chunk
37+
max_size = 8000
38+
for point in points:
39+
point_msg = messages.point_message(point)
40+
if chunk.ByteSize() + point_msg.ByteSize() > max_size:
41+
yield chunk
42+
chunk = PrimitiveInstanceCollectionDataMessage()
43+
chunk.points.points.append(point_msg)
44+
yield chunk
45+
46+
@classmethod
47+
def create(
48+
cls,
49+
layout: Layout,
50+
net: NetLike,
51+
layer: LayerLike,
52+
geometry: PolygonData,
53+
positions: List[PointLike],
54+
) -> PrimitiveInstanceCollection:
55+
"""Create a primitive instance collection containing the specified geometry instantiated \
56+
at the provided locations. All geometry will be created on the specified layer and net.
57+
58+
Parameters
59+
----------
60+
layout : .Layout
61+
Layout to create the primitive instance collection in.
62+
net : :term:`NetLike`
63+
Net that the primitive instance collection geometry will exist on.
64+
layer : :term:`LayerLike`
65+
Layer that the primitive instance collection geometry will exist on.
66+
geometry : .PolygonData
67+
The geometry that will be instantiated.
68+
positions : list of :term:`PointLike`
69+
The points to instantiate the geometry at.
70+
71+
Returns
72+
-------
73+
.PrimitiveInstanceCollection
74+
"""
75+
chunk = PrimitiveInstanceCollectionDataMessage(
76+
lyt_or_prim_inst_col=layout.msg,
77+
net=messages.net_ref_message(net),
78+
layer=messages.layer_ref_message(layer),
79+
geometry=messages.polygon_data_message(geometry),
80+
)
81+
return PrimitiveInstanceCollection(
82+
cls.__stub.Create(cls._point_request_iterator(positions, chunk))
83+
)
84+
85+
@property
86+
@to_polygon_data
87+
def geometry(self) -> PolygonData:
88+
""":class:`.PolygonData`: The geometry that the primitive instance collection instantiates."""
89+
return self.__stub.GetGeometry(self.msg)
90+
91+
@geometry.setter
92+
def geometry(self, geometry: PolygonData):
93+
self.__stub.SetGeometry(messages.polygon_data_property_message(self, geometry))
94+
95+
@property
96+
def positions(self) -> List[PointData]:
97+
""":obj:`list` of :class:`.PointData`: The positions geometry is instantiated at."""
98+
return stream_items_from_server(
99+
msg_to_point_data, self.__stub.GetPositions(self.msg), "points"
100+
)
101+
102+
@positions.setter
103+
def positions(self, positions: List[PointData]):
104+
chunk = PrimitiveInstanceCollectionDataMessage(lyt_or_prim_inst_col=self.msg)
105+
self.__stub.SetPositions(self._point_request_iterator(positions, chunk))
106+
107+
@property
108+
def instantiated_geometry(self) -> List[PolygonData]:
109+
""":obj:`list` of :class:`.PolygonData`: The geometry instantiated at each location in \
110+
the primitive instance collection."""
111+
return stream_items_from_server(
112+
msg_to_polygon_data, self.__stub.GetInstantiatedGeometry(self.msg), "polygons"
113+
)
114+
115+
def decompose(self):
116+
"""Decompose into individual :class:`primitives <.Primitive>`. A \
117+
:class:`primitive <.Primitive>` will be created for each geometry instantiation."""
118+
return self.__stub.Decompose(self.msg)

0 commit comments

Comments
 (0)