rocket-engine 0.0.2b1__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.
- rocket_engine/__init__.py +8 -0
- rocket_engine/__main__.py +12 -0
- rocket_engine/adapters/README.md +54 -0
- rocket_engine/adapters/__init__.py +7 -0
- rocket_engine/adapters/api.py +39 -0
- rocket_engine/adapters/base.py +46 -0
- rocket_engine/adapters/cace/__init__.py +163 -0
- rocket_engine/adapters/circulax/README.md +10 -0
- rocket_engine/adapters/circulax/__init__.py +112 -0
- rocket_engine/adapters/circulax/_driver.py +190 -0
- rocket_engine/adapters/circulax/adapter.py +538 -0
- rocket_engine/adapters/circulax/results.py +291 -0
- rocket_engine/adapters/cli/README.md +34 -0
- rocket_engine/adapters/cli/__init__.py +305 -0
- rocket_engine/adapters/connectivity/__init__.py +196 -0
- rocket_engine/adapters/connectivity/results.py +214 -0
- rocket_engine/adapters/cosim/README.md +9 -0
- rocket_engine/adapters/cosim/__init__.py +1390 -0
- rocket_engine/adapters/cosim/results.py +214 -0
- rocket_engine/adapters/devsim/__init__.py +171 -0
- rocket_engine/adapters/digital_flow/README.md +9 -0
- rocket_engine/adapters/digital_flow/__init__.py +1121 -0
- rocket_engine/adapters/digital_flow/results.py +227 -0
- rocket_engine/adapters/digital_flow/state.py +462 -0
- rocket_engine/adapters/drc/README.klayout.md +7 -0
- rocket_engine/adapters/drc/README.md +7 -0
- rocket_engine/adapters/drc/__init__.py +1 -0
- rocket_engine/adapters/drc/klayout.py +952 -0
- rocket_engine/adapters/drc/magic.py +287 -0
- rocket_engine/adapters/drc/results.py +184 -0
- rocket_engine/adapters/drc/results_kl.py +188 -0
- rocket_engine/adapters/elmer/__init__.py +163 -0
- rocket_engine/adapters/envelope.py +120 -0
- rocket_engine/adapters/equivalence/__init__.py +156 -0
- rocket_engine/adapters/errors.py +181 -0
- rocket_engine/adapters/extract_magic/README.md +15 -0
- rocket_engine/adapters/extract_magic/__init__.py +425 -0
- rocket_engine/adapters/extract_magic/results.py +209 -0
- rocket_engine/adapters/formal_sby/README.md +7 -0
- rocket_engine/adapters/formal_sby/__init__.py +773 -0
- rocket_engine/adapters/formal_sby/formal_edge.py +195 -0
- rocket_engine/adapters/formal_sby/results.py +185 -0
- rocket_engine/adapters/gdsfactory/__init__.py +194 -0
- rocket_engine/adapters/gdsfactory/_driver.py +459 -0
- rocket_engine/adapters/gdsfactory/adapter.py +609 -0
- rocket_engine/adapters/gdsfactory/results.py +181 -0
- rocket_engine/adapters/hdl_sim/README.md +7 -0
- rocket_engine/adapters/hdl_sim/__init__.py +369 -0
- rocket_engine/adapters/hdl_sim/ghdl.py +155 -0
- rocket_engine/adapters/hdl_sim/results.py +178 -0
- rocket_engine/adapters/hdl_sim/verilator.py +167 -0
- rocket_engine/adapters/http/README.md +15 -0
- rocket_engine/adapters/http/__init__.py +458 -0
- rocket_engine/adapters/http/events.py +63 -0
- rocket_engine/adapters/ipc/README.md +9 -0
- rocket_engine/adapters/ipc/__init__.py +111 -0
- rocket_engine/adapters/lvs_netgen/README.md +20 -0
- rocket_engine/adapters/lvs_netgen/__init__.py +889 -0
- rocket_engine/adapters/lvs_netgen/results.py +168 -0
- rocket_engine/adapters/mcp/README.md +19 -0
- rocket_engine/adapters/mcp/__init__.py +85 -0
- rocket_engine/adapters/mcp/api-schema.json +6107 -0
- rocket_engine/adapters/mcp/pull.py +246 -0
- rocket_engine/adapters/mcp/server.py +794 -0
- rocket_engine/adapters/meep/__init__.py +167 -0
- rocket_engine/adapters/meow/README.md +7 -0
- rocket_engine/adapters/meow/__init__.py +190 -0
- rocket_engine/adapters/meow/_driver.py +227 -0
- rocket_engine/adapters/meow/_envelope.py +108 -0
- rocket_engine/adapters/meow/adapter.py +491 -0
- rocket_engine/adapters/meow/results.py +218 -0
- rocket_engine/adapters/mutation/__init__.py +192 -0
- rocket_engine/adapters/ngspice/README.md +28 -0
- rocket_engine/adapters/ngspice/__init__.py +627 -0
- rocket_engine/adapters/ngspice/results.py +215 -0
- rocket_engine/adapters/postlayout/README.md +28 -0
- rocket_engine/adapters/postlayout/__init__.py +17 -0
- rocket_engine/adapters/postlayout/adapter.py +1062 -0
- rocket_engine/adapters/postlayout/results.py +330 -0
- rocket_engine/adapters/postlayout/translate.py +71 -0
- rocket_engine/adapters/profile.py +220 -0
- rocket_engine/adapters/sax/README.md +23 -0
- rocket_engine/adapters/sax/__init__.py +516 -0
- rocket_engine/adapters/sax/_driver.py +148 -0
- rocket_engine/adapters/sax/results.py +291 -0
- rocket_engine/adapters/stdio_console.py +1151 -0
- rocket_engine/adapters/syntax/README.md +5 -0
- rocket_engine/adapters/syntax/__init__.py +366 -0
- rocket_engine/adapters/syntax/results.py +181 -0
- rocket_engine/adapters/synth_yosys/README.md +7 -0
- rocket_engine/adapters/synth_yosys/__init__.py +655 -0
- rocket_engine/adapters/synth_yosys/amaranth.py +120 -0
- rocket_engine/adapters/synth_yosys/ghdl.py +128 -0
- rocket_engine/adapters/synth_yosys/results.py +173 -0
- rocket_engine/adapters/timing_sta/README.md +9 -0
- rocket_engine/adapters/timing_sta/__init__.py +726 -0
- rocket_engine/adapters/view_sync/__init__.py +185 -0
- rocket_engine/adapters/view_sync/results.py +189 -0
- rocket_engine/adapters/vlsir/__init__.py +142 -0
- rocket_engine/adapters/xschem/__init__.py +440 -0
- rocket_engine/adapters/xschem/deck.py +266 -0
- rocket_engine/clients/__init__.py +5 -0
- rocket_engine/clients/runtime.py +255 -0
- rocket_engine/composition.py +2279 -0
- rocket_engine/execution/README.md +30 -0
- rocket_engine/execution/__init__.py +150 -0
- rocket_engine/execution/apig/__init__.py +1 -0
- rocket_engine/execution/apig/pool.py +463 -0
- rocket_engine/execution/apig/submit.py +124 -0
- rocket_engine/execution/block_runs_scope.py +479 -0
- rocket_engine/execution/coalesce.py +414 -0
- rocket_engine/execution/console/__init__.py +1 -0
- rocket_engine/execution/console/apply_session.py +318 -0
- rocket_engine/execution/console/lifecycle.py +265 -0
- rocket_engine/execution/dispatch.py +205 -0
- rocket_engine/execution/errors.py +31 -0
- rocket_engine/execution/ports.py +797 -0
- rocket_engine/execution/pyexec/__init__.py +1 -0
- rocket_engine/execution/pyexec/apply_context.py +547 -0
- rocket_engine/execution/pyexec/approvals.py +173 -0
- rocket_engine/execution/pyexec/runner.py +404 -0
- rocket_engine/execution/pyexec/support.py +464 -0
- rocket_engine/execution/python_exec.py +897 -0
- rocket_engine/execution/queue.py +85 -0
- rocket_engine/execution/replay_safe.py +601 -0
- rocket_engine/execution/routing/__init__.py +1 -0
- rocket_engine/execution/routing/calls.py +364 -0
- rocket_engine/execution/routing/namespace.py +785 -0
- rocket_engine/execution/routing/ports.py +102 -0
- rocket_engine/execution/routing/tools.py +346 -0
- rocket_engine/execution/runner.py +311 -0
- rocket_engine/execution/runtime.py +1149 -0
- rocket_engine/execution/service.py +279 -0
- rocket_engine/execution/worker.py +464 -0
- rocket_engine/interfaces/README.md +26 -0
- rocket_engine/interfaces/__init__.py +58 -0
- rocket_engine/interfaces/apig_assets.py +407 -0
- rocket_engine/interfaces/celllink.py +461 -0
- rocket_engine/interfaces/documents.py +92 -0
- rocket_engine/interfaces/envelopes.py +131 -0
- rocket_engine/interfaces/errors.py +16 -0
- rocket_engine/interfaces/layout.py +410 -0
- rocket_engine/interfaces/layout_schema.yaml +278 -0
- rocket_engine/interfaces/paths.py +223 -0
- rocket_engine/interfaces/pdks.py +550 -0
- rocket_engine/interfaces/ports.py +72 -0
- rocket_engine/interfaces/references.py +115 -0
- rocket_engine/interfaces/refs.py +85 -0
- rocket_engine/interfaces/runparams.py +304 -0
- rocket_engine/interfaces/setup.py +552 -0
- rocket_engine/interfaces/setup_catalogue.yaml +243 -0
- rocket_engine/interfaces/setup_probes.py +66 -0
- rocket_engine/interfaces/vocabulary.py +58 -0
- rocket_engine/interfaces/yaml_dump.py +31 -0
- rocket_engine/library/README.md +22 -0
- rocket_engine/library/__init__.py +13 -0
- rocket_engine/library/apig_catalog.py +371 -0
- rocket_engine/library/catalog.py +185 -0
- rocket_engine/library/errors.py +16 -0
- rocket_engine/mutate/__init__.py +776 -0
- rocket_engine/mutate/operators.py +700 -0
- rocket_engine/ops/__init__.py +1 -0
- rocket_engine/ops/deferred.py +279 -0
- rocket_engine/ops/editors.py +404 -0
- rocket_engine/ops/evidence.py +194 -0
- rocket_engine/ops/gates.py +262 -0
- rocket_engine/ops/ports.py +76 -0
- rocket_engine/ops/queries.py +417 -0
- rocket_engine/ops/runs.py +553 -0
- rocket_engine/ops/submit.py +614 -0
- rocket_engine/project/README.md +27 -0
- rocket_engine/project/__init__.py +6 -0
- rocket_engine/project/canvas/__init__.py +55 -0
- rocket_engine/project/canvas/compile.py +109 -0
- rocket_engine/project/canvas/integrate.py +48 -0
- rocket_engine/project/canvas/schema.py +241 -0
- rocket_engine/project/canvas/templates.py +46 -0
- rocket_engine/project/canvas/validate.py +603 -0
- rocket_engine/project/edit/__init__.py +5 -0
- rocket_engine/project/edit/cas.py +87 -0
- rocket_engine/project/edit/transactions.py +282 -0
- rocket_engine/project/errors.py +16 -0
- rocket_engine/project/frontend13.py +685 -0
- rocket_engine/project/geometry/__init__.py +23 -0
- rocket_engine/project/geometry/ops.py +451 -0
- rocket_engine/project/geometry/snap.py +103 -0
- rocket_engine/project/ip/__init__.py +1 -0
- rocket_engine/project/ip/blocks.py +195 -0
- rocket_engine/project/ip/entry.schema.json +78 -0
- rocket_engine/project/ip/lifecycle.py +402 -0
- rocket_engine/project/ip/verbs.py +123 -0
- rocket_engine/project/layers/__init__.py +6 -0
- rocket_engine/project/layers/bindings.py +115 -0
- rocket_engine/project/layers/catalog.py +228 -0
- rocket_engine/project/layers/presets.py +43 -0
- rocket_engine/project/ledger/__init__.py +1 -0
- rocket_engine/project/ledger/docs.py +167 -0
- rocket_engine/project/ledger/entities.py +176 -0
- rocket_engine/project/ledger/events.py +29 -0
- rocket_engine/project/ledger/runs.py +257 -0
- rocket_engine/project/ledger/shared.py +174 -0
- rocket_engine/project/ledger/snapshots.py +385 -0
- rocket_engine/project/ledger/wires.py +552 -0
- rocket_engine/project/process/__init__.py +14 -0
- rocket_engine/project/process/model.py +193 -0
- rocket_engine/project/process/recipes.py +121 -0
- rocket_engine/project/schematic/__init__.py +1 -0
- rocket_engine/project/schematic/hierarchy.py +312 -0
- rocket_engine/project/schematic/instances.py +393 -0
- rocket_engine/project/schematic/sheets.py +198 -0
- rocket_engine/project/service.py +2941 -0
- rocket_engine/project/symbols/__init__.py +1 -0
- rocket_engine/project/symbols/cells.py +324 -0
- rocket_engine/project/symbols/linkage.py +356 -0
- rocket_engine/project/symbols/symbols.py +352 -0
- rocket_engine/project/templates/__init__.py +303 -0
- rocket_engine/project/templates/errors.py +25 -0
- rocket_engine/project/testbench/__init__.py +49 -0
- rocket_engine/project/testbench/blocks.py +619 -0
- rocket_engine/project/testbench/templates.py +134 -0
- rocket_engine/stores/README.md +12 -0
- rocket_engine/stores/__init__.py +6 -0
- rocket_engine/stores/ecs.py +1508 -0
- rocket_engine/stores/layers.py +1023 -0
- rocket_engine/stores/settings.py +1188 -0
- rocket_engine/stores/wireplace.py +1324 -0
- rocket_engine/workflows/README.md +18 -0
- rocket_engine/workflows/__init__.py +53 -0
- rocket_engine/workflows/capability_plan.py +102 -0
- rocket_engine/workflows/completion.py +228 -0
- rocket_engine/workflows/connectgraph.py +734 -0
- rocket_engine/workflows/connectgraph_emit.py +336 -0
- rocket_engine/workflows/gates.py +144 -0
- rocket_engine/workflows/photonic_wire.py +76 -0
- rocket_engine/workflows/stages.py +324 -0
- rocket_engine/workflows/viewsync.py +399 -0
- rocket_engine-0.0.2b1.dist-info/METADATA +200 -0
- rocket_engine-0.0.2b1.dist-info/RECORD +241 -0
- rocket_engine-0.0.2b1.dist-info/WHEEL +4 -0
- rocket_engine-0.0.2b1.dist-info/entry_points.txt +2 -0
- rocket_engine-0.0.2b1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Typed layout geometry operations and snap grids."""
|
|
2
|
+
|
|
3
|
+
from .ops import (
|
|
4
|
+
copy_shape,
|
|
5
|
+
create_shape,
|
|
6
|
+
delete_shape,
|
|
7
|
+
move_shape,
|
|
8
|
+
read_shape,
|
|
9
|
+
update_shape,
|
|
10
|
+
)
|
|
11
|
+
from .snap import check_mfg_grid, quantize_storage, snap_to_grid
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"copy_shape",
|
|
15
|
+
"create_shape",
|
|
16
|
+
"delete_shape",
|
|
17
|
+
"move_shape",
|
|
18
|
+
"read_shape",
|
|
19
|
+
"update_shape",
|
|
20
|
+
"check_mfg_grid",
|
|
21
|
+
"quantize_storage",
|
|
22
|
+
"snap_to_grid",
|
|
23
|
+
]
|
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
"""Typed layout geometry records over engine entities.
|
|
2
|
+
|
|
3
|
+
Rectangles, polygons, paths, and labels live as kind=shape
|
|
4
|
+
entities with a geom component (shape_type, layer lid, units,
|
|
5
|
+
data) plus provenance (actor, profile pin). Object identity is
|
|
6
|
+
the stable entity id; cell hierarchy, ports, and links stay on
|
|
7
|
+
sibling components the ops never touch. Transforms (move,
|
|
8
|
+
rotate, mirror, array, copy) mint or reuse identity explicitly:
|
|
9
|
+
move-style ops keep the id, copy/array mint new ones.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import copy
|
|
13
|
+
import math
|
|
14
|
+
|
|
15
|
+
SHAPE_TYPES = ("rect", "polygon", "path", "label")
|
|
16
|
+
GEOM_COMPONENT = "geom"
|
|
17
|
+
PROVENANCE_COMPONENT = "shape_provenance"
|
|
18
|
+
SHAPE_KIND = "shape"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def validate_shape(shape_type, data):
|
|
22
|
+
"""Check one shape payload against its type schema.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
shape_type: One of rect, polygon, path, or label.
|
|
26
|
+
data: Shape payload mapping to check.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
Deep copy of the validated payload.
|
|
30
|
+
|
|
31
|
+
Raises:
|
|
32
|
+
ValueError: For an unknown type or a malformed
|
|
33
|
+
payload naming the offending field.
|
|
34
|
+
"""
|
|
35
|
+
if shape_type not in SHAPE_TYPES:
|
|
36
|
+
raise ValueError(f"Unknown shape type: {shape_type!r}.")
|
|
37
|
+
body = copy.deepcopy(dict(data))
|
|
38
|
+
if shape_type == "rect":
|
|
39
|
+
for field in ("x", "y", "w", "h"):
|
|
40
|
+
if field not in body:
|
|
41
|
+
raise ValueError(
|
|
42
|
+
f"Rect is missing field: {field}.")
|
|
43
|
+
if body["w"] <= 0 or body["h"] <= 0:
|
|
44
|
+
raise ValueError("Rect needs positive w and h.")
|
|
45
|
+
elif shape_type == "polygon":
|
|
46
|
+
points = body.get("points", [])
|
|
47
|
+
if len(points) < 3:
|
|
48
|
+
raise ValueError("Polygon needs 3+ points.")
|
|
49
|
+
elif shape_type == "path":
|
|
50
|
+
if len(body.get("points", [])) < 2:
|
|
51
|
+
raise ValueError("Path needs 2+ points.")
|
|
52
|
+
if body.get("width", 0) <= 0:
|
|
53
|
+
raise ValueError("Path needs a positive width.")
|
|
54
|
+
else:
|
|
55
|
+
for field in ("x", "y", "text"):
|
|
56
|
+
if field not in body:
|
|
57
|
+
raise ValueError(
|
|
58
|
+
f"Label is missing field: {field}.")
|
|
59
|
+
return body
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _live_revision(service, entity):
|
|
63
|
+
"""Read the live entity revision an unnamed write builds on.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
service: ProjectService handle to read through.
|
|
67
|
+
entity: Shape entity id.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
Live entity revision for the gate.
|
|
71
|
+
"""
|
|
72
|
+
names = service.components_of(entity)["components"]
|
|
73
|
+
if not names:
|
|
74
|
+
return 1
|
|
75
|
+
return service.get_component(entity, names[0]).get(
|
|
76
|
+
"entity_revision", 1)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _geom(shape_type, layer, units, data, profile=None):
|
|
80
|
+
"""Build one geom component payload.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
shape_type: Validated shape type name.
|
|
84
|
+
layer: Stable layer lid the shape is drawn on.
|
|
85
|
+
units: Coordinate units label.
|
|
86
|
+
data: Validated shape payload.
|
|
87
|
+
profile: Technology profile pin, if resolved.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Geom component mapping.
|
|
91
|
+
"""
|
|
92
|
+
return {
|
|
93
|
+
"data": validate_shape(shape_type, data),
|
|
94
|
+
"layer": layer,
|
|
95
|
+
"profile": copy.deepcopy(profile),
|
|
96
|
+
"shape_type": shape_type,
|
|
97
|
+
"units": units,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def create_shape(service, request_id, command_text, shape_type,
|
|
102
|
+
layer, data, units="nm", profile=None,
|
|
103
|
+
anchor=None, expected_revision=None):
|
|
104
|
+
"""Create one shape entity with provenance.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
service: ProjectService handle to write through.
|
|
108
|
+
request_id: Client envelope id.
|
|
109
|
+
command_text: Command text for identity comparison.
|
|
110
|
+
shape_type: One of rect, polygon, path, label.
|
|
111
|
+
layer: Stable layer lid (mapped or not — both draw).
|
|
112
|
+
data: Shape payload for the type.
|
|
113
|
+
units: Coordinate units label.
|
|
114
|
+
profile: Technology profile pin for provenance.
|
|
115
|
+
anchor: Optional cell-port anchor {x, y, port}.
|
|
116
|
+
expected_revision: Accepted and ignored (minted ids
|
|
117
|
+
never conflict).
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Creation mapping with entity, shape_type, layer,
|
|
121
|
+
and revision transition.
|
|
122
|
+
|
|
123
|
+
Raises:
|
|
124
|
+
ValueError: For an unknown shape type or payload.
|
|
125
|
+
Exception: Whatever the service raises.
|
|
126
|
+
"""
|
|
127
|
+
components = {
|
|
128
|
+
GEOM_COMPONENT: _geom(
|
|
129
|
+
shape_type, layer, units, data, profile),
|
|
130
|
+
PROVENANCE_COMPONENT: {
|
|
131
|
+
"anchor": copy.deepcopy(anchor),
|
|
132
|
+
"profile": copy.deepcopy(profile),
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
created = service.create_entity(
|
|
136
|
+
request_id, command_text, kind=SHAPE_KIND,
|
|
137
|
+
components=components,
|
|
138
|
+
expected_revision=expected_revision,
|
|
139
|
+
)
|
|
140
|
+
return {
|
|
141
|
+
"current_revision": created["current_revision"],
|
|
142
|
+
"entity": created["entity"],
|
|
143
|
+
"entity_revision": created["entity_revision"],
|
|
144
|
+
"layer": layer,
|
|
145
|
+
"shape_type": shape_type,
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def read_shape(service, entity):
|
|
150
|
+
"""Read one shape with its provenance.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
service: ProjectService handle to read through.
|
|
154
|
+
entity: Shape entity id.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
Mapping with entity, shape_type, layer, units,
|
|
158
|
+
data, anchor, profile, and entity_revision.
|
|
159
|
+
|
|
160
|
+
Raises:
|
|
161
|
+
Exception: Whatever the service raises for unknown
|
|
162
|
+
entities or components.
|
|
163
|
+
"""
|
|
164
|
+
geom = service.get_component(entity, GEOM_COMPONENT)
|
|
165
|
+
try:
|
|
166
|
+
provenance = service.get_component(
|
|
167
|
+
entity, PROVENANCE_COMPONENT)["data"]
|
|
168
|
+
except Exception:
|
|
169
|
+
provenance = {}
|
|
170
|
+
body = geom["data"]
|
|
171
|
+
return {
|
|
172
|
+
"anchor": copy.deepcopy(provenance.get("anchor")),
|
|
173
|
+
"current_revision": geom["current_revision"],
|
|
174
|
+
"data": copy.deepcopy(body["data"]),
|
|
175
|
+
"entity": entity,
|
|
176
|
+
"entity_revision": geom["entity_revision"],
|
|
177
|
+
"layer": body["layer"],
|
|
178
|
+
"profile": copy.deepcopy(body.get("profile")),
|
|
179
|
+
"shape_type": body["shape_type"],
|
|
180
|
+
"units": body["units"],
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def update_shape(service, request_id, command_text, entity,
|
|
185
|
+
data=None, layer=None,
|
|
186
|
+
expected_entity_revision=None,
|
|
187
|
+
expected_revision=None):
|
|
188
|
+
"""Update one shape payload gated on its entity revision.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
service: ProjectService handle to write through.
|
|
192
|
+
request_id: Client envelope id.
|
|
193
|
+
command_text: Command text for identity comparison.
|
|
194
|
+
entity: Shape entity id.
|
|
195
|
+
data: Replacement shape payload, if changing.
|
|
196
|
+
layer: New stable layer lid, if moving layers.
|
|
197
|
+
expected_entity_revision: Entity revision the writer
|
|
198
|
+
read; stale values fail with revision_conflict.
|
|
199
|
+
expected_revision: Global head gate (skipped when an
|
|
200
|
+
entity revision is named).
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
Mutation mapping with the bumped entity_revision.
|
|
204
|
+
|
|
205
|
+
Raises:
|
|
206
|
+
Exception: revision_conflict for a stale entity
|
|
207
|
+
revision, or whatever the service raises.
|
|
208
|
+
"""
|
|
209
|
+
current = read_shape(service, entity)
|
|
210
|
+
payload = _geom(
|
|
211
|
+
current["shape_type"],
|
|
212
|
+
layer if layer is not None else current["layer"],
|
|
213
|
+
current["units"],
|
|
214
|
+
data if data is not None else current["data"],
|
|
215
|
+
current["profile"],
|
|
216
|
+
)
|
|
217
|
+
gate = expected_entity_revision
|
|
218
|
+
if gate is None and expected_revision is None:
|
|
219
|
+
gate = _live_revision(service, entity)
|
|
220
|
+
return service.set_component(
|
|
221
|
+
entity, GEOM_COMPONENT, payload, request_id,
|
|
222
|
+
command_text, expected_revision=expected_revision,
|
|
223
|
+
expected_entity_revision=gate,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def delete_shape(service, request_id, command_text, entity,
|
|
228
|
+
expected_entity_revision=None,
|
|
229
|
+
expected_revision=None):
|
|
230
|
+
"""Delete one shape entity.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
service: ProjectService handle to write through.
|
|
234
|
+
request_id: Client envelope id.
|
|
235
|
+
command_text: Command text for identity comparison.
|
|
236
|
+
entity: Shape entity id.
|
|
237
|
+
expected_entity_revision: Entity revision gate.
|
|
238
|
+
expected_revision: Global head gate.
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
Service deletion mapping.
|
|
242
|
+
|
|
243
|
+
Raises:
|
|
244
|
+
Exception: Whatever the service raises.
|
|
245
|
+
"""
|
|
246
|
+
return service.delete_entity(
|
|
247
|
+
entity, request_id, command_text,
|
|
248
|
+
expected_revision=expected_revision,
|
|
249
|
+
expected_entity_revision=expected_entity_revision,
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def move_shape(service, request_id, command_text, entity, dx,
|
|
254
|
+
dy, expected_entity_revision=None):
|
|
255
|
+
"""Move one shape by a delta, keeping its identity.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
service: ProjectService handle to write through.
|
|
259
|
+
request_id: Client envelope id.
|
|
260
|
+
command_text: Command text for identity comparison.
|
|
261
|
+
entity: Shape entity id.
|
|
262
|
+
dx: Delta in x storage units.
|
|
263
|
+
dy: Delta in y storage units.
|
|
264
|
+
expected_entity_revision: Entity revision gate.
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
Mutation mapping with the bumped entity_revision.
|
|
268
|
+
|
|
269
|
+
Raises:
|
|
270
|
+
Exception: Whatever update_shape raises.
|
|
271
|
+
"""
|
|
272
|
+
current = read_shape(service, entity)
|
|
273
|
+
data = copy.deepcopy(current["data"])
|
|
274
|
+
if current["shape_type"] in ("rect", "label"):
|
|
275
|
+
data["x"] += dx
|
|
276
|
+
data["y"] += dy
|
|
277
|
+
else:
|
|
278
|
+
data["points"] = [[px + dx, py + dy] for px, py in
|
|
279
|
+
data["points"]]
|
|
280
|
+
out = update_shape(
|
|
281
|
+
service, request_id, command_text, entity, data=data,
|
|
282
|
+
expected_entity_revision=expected_entity_revision,
|
|
283
|
+
)
|
|
284
|
+
return {"entity": entity, "entity_revision": out.get(
|
|
285
|
+
"entity_revision"), "response": out}
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def _rotate_point(px, py, degrees):
|
|
289
|
+
"""Rotate one point about the origin.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
px: X coordinate.
|
|
293
|
+
py: Y coordinate.
|
|
294
|
+
degrees: Rotation angle in degrees.
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
Rotated [x, y] pair.
|
|
298
|
+
"""
|
|
299
|
+
radians = math.radians(degrees)
|
|
300
|
+
cosine, sine = math.cos(radians), math.sin(radians)
|
|
301
|
+
return [px * cosine - py * sine, px * sine + py * cosine]
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def transform_shape(service, request_id, command_text, entity,
|
|
305
|
+
rotate_deg=0, mirror=None,
|
|
306
|
+
expected_entity_revision=None):
|
|
307
|
+
"""Rotate or mirror one shape in place.
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
service: ProjectService handle to write through.
|
|
311
|
+
request_id: Client envelope id.
|
|
312
|
+
command_text: Command text for identity comparison.
|
|
313
|
+
entity: Shape entity id.
|
|
314
|
+
rotate_deg: Rotation angle in degrees (0 skips).
|
|
315
|
+
mirror: Mirror axis (x, y, or None).
|
|
316
|
+
expected_entity_revision: Entity revision gate.
|
|
317
|
+
|
|
318
|
+
Returns:
|
|
319
|
+
Mapping with entity and bumped entity_revision.
|
|
320
|
+
|
|
321
|
+
Raises:
|
|
322
|
+
ValueError: For an unknown mirror axis.
|
|
323
|
+
Exception: Whatever update_shape raises.
|
|
324
|
+
"""
|
|
325
|
+
if mirror not in (None, "x", "y"):
|
|
326
|
+
raise ValueError(f"Unknown mirror axis: {mirror!r}.")
|
|
327
|
+
current = read_shape(service, entity)
|
|
328
|
+
data = copy.deepcopy(current["data"])
|
|
329
|
+
|
|
330
|
+
def _point(px, py):
|
|
331
|
+
if mirror == "x":
|
|
332
|
+
px = -px
|
|
333
|
+
elif mirror == "y":
|
|
334
|
+
py = -py
|
|
335
|
+
if rotate_deg:
|
|
336
|
+
px, py = _rotate_point(px, py, rotate_deg)
|
|
337
|
+
return [px, py]
|
|
338
|
+
|
|
339
|
+
if current["shape_type"] in ("rect", "label"):
|
|
340
|
+
data["x"], data["y"] = _point(data["x"], data["y"])
|
|
341
|
+
else:
|
|
342
|
+
data["points"] = [_point(px, py) for px, py in
|
|
343
|
+
data["points"]]
|
|
344
|
+
out = update_shape(
|
|
345
|
+
service, request_id, command_text, entity, data=data,
|
|
346
|
+
expected_entity_revision=expected_entity_revision,
|
|
347
|
+
)
|
|
348
|
+
return {"entity": entity, "entity_revision": out.get(
|
|
349
|
+
"entity_revision"), "response": out}
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def copy_shape(service, request_id, command_text, entity,
|
|
353
|
+
dx=0, dy=0):
|
|
354
|
+
"""Copy one shape to a new entity with an optional offset.
|
|
355
|
+
|
|
356
|
+
Args:
|
|
357
|
+
service: ProjectService handle to write through.
|
|
358
|
+
request_id: Client envelope id.
|
|
359
|
+
command_text: Command text for identity comparison.
|
|
360
|
+
entity: Source shape entity id.
|
|
361
|
+
dx: Offset in x storage units.
|
|
362
|
+
dy: Offset in y storage units.
|
|
363
|
+
|
|
364
|
+
Returns:
|
|
365
|
+
Creation mapping for the new shape entity.
|
|
366
|
+
|
|
367
|
+
Raises:
|
|
368
|
+
Exception: Whatever the service raises.
|
|
369
|
+
"""
|
|
370
|
+
current = read_shape(service, entity)
|
|
371
|
+
data = copy.deepcopy(current["data"])
|
|
372
|
+
if current["shape_type"] in ("rect", "label"):
|
|
373
|
+
data["x"] += dx
|
|
374
|
+
data["y"] += dy
|
|
375
|
+
else:
|
|
376
|
+
data["points"] = [[px + dx, py + dy] for px, py in
|
|
377
|
+
data["points"]]
|
|
378
|
+
return create_shape(
|
|
379
|
+
service, request_id, command_text,
|
|
380
|
+
current["shape_type"], current["layer"], data,
|
|
381
|
+
units=current["units"], profile=current["profile"],
|
|
382
|
+
anchor=current["anchor"],
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def array_shape(service, request_id, command_text, entity,
|
|
387
|
+
count_x, count_y, pitch_x, pitch_y):
|
|
388
|
+
"""Array one shape into a grid of new entities.
|
|
389
|
+
|
|
390
|
+
Args:
|
|
391
|
+
service: ProjectService handle to write through.
|
|
392
|
+
request_id: Client envelope id; members derive
|
|
393
|
+
sub-envelopes from it.
|
|
394
|
+
command_text: Command text for identity comparison.
|
|
395
|
+
entity: Source shape entity id.
|
|
396
|
+
count_x: Copies along x (at least 1).
|
|
397
|
+
count_y: Copies along y (at least 1).
|
|
398
|
+
pitch_x: Pitch between columns in storage units.
|
|
399
|
+
pitch_y: Pitch between rows in storage units.
|
|
400
|
+
|
|
401
|
+
Returns:
|
|
402
|
+
List of creation mappings, one per member.
|
|
403
|
+
|
|
404
|
+
Raises:
|
|
405
|
+
ValueError: For counts below 1.
|
|
406
|
+
Exception: Whatever the service raises.
|
|
407
|
+
"""
|
|
408
|
+
if count_x < 1 or count_y < 1:
|
|
409
|
+
raise ValueError("Array counts must be at least 1.")
|
|
410
|
+
members = []
|
|
411
|
+
for row in range(count_y):
|
|
412
|
+
for col in range(count_x):
|
|
413
|
+
members.append(copy_shape(
|
|
414
|
+
service, f"{request_id}:array:{col}:{row}",
|
|
415
|
+
f"{command_text} array {col},{row}", entity,
|
|
416
|
+
dx=col * pitch_x, dy=row * pitch_y,
|
|
417
|
+
))
|
|
418
|
+
return members
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def set_anchor(service, request_id, command_text, entity,
|
|
422
|
+
anchor, expected_entity_revision=None):
|
|
423
|
+
"""Store one cell-port anchor on a shape.
|
|
424
|
+
|
|
425
|
+
Args:
|
|
426
|
+
service: ProjectService handle to write through.
|
|
427
|
+
request_id: Client envelope id.
|
|
428
|
+
command_text: Command text for identity comparison.
|
|
429
|
+
entity: Shape entity id.
|
|
430
|
+
anchor: Anchor mapping {x, y, port}.
|
|
431
|
+
expected_entity_revision: Entity revision gate.
|
|
432
|
+
|
|
433
|
+
Returns:
|
|
434
|
+
Mutation mapping with the bumped entity_revision.
|
|
435
|
+
|
|
436
|
+
Raises:
|
|
437
|
+
Exception: Whatever the service raises.
|
|
438
|
+
"""
|
|
439
|
+
current = read_shape(service, entity)
|
|
440
|
+
provenance = {
|
|
441
|
+
"anchor": copy.deepcopy(anchor),
|
|
442
|
+
"profile": current["profile"],
|
|
443
|
+
}
|
|
444
|
+
gate = expected_entity_revision
|
|
445
|
+
if gate is None:
|
|
446
|
+
gate = _live_revision(service, entity)
|
|
447
|
+
return service.set_component(
|
|
448
|
+
entity, PROVENANCE_COMPONENT, provenance, request_id,
|
|
449
|
+
command_text, expected_revision=None,
|
|
450
|
+
expected_entity_revision=gate,
|
|
451
|
+
)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Project grid separation: storage, UI snap, manufacturing grid.
|
|
2
|
+
|
|
3
|
+
Authored geometry is stored exactly as drawn. The UI snap grid
|
|
4
|
+
only guides new pointer positions and the profile manufacturing
|
|
5
|
+
grid only constrains process checks and export — neither ever
|
|
6
|
+
rewrites stored coordinates.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def snap_to_grid(value, grid):
|
|
11
|
+
"""Snap one coordinate to the nearest grid multiple.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
value: Authored coordinate in storage units.
|
|
15
|
+
grid: Snap grid pitch in the same units.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
Nearest multiple of the grid pitch.
|
|
19
|
+
|
|
20
|
+
Raises:
|
|
21
|
+
ValueError: For a non-positive grid pitch.
|
|
22
|
+
"""
|
|
23
|
+
if grid <= 0:
|
|
24
|
+
raise ValueError(f"Grid pitch must be positive: {grid!r}.")
|
|
25
|
+
return round(value / grid) * grid
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def quantize_storage(value, units):
|
|
29
|
+
"""Quantize one value for integer storage units.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
value: Authored coordinate value.
|
|
33
|
+
units: Storage units label (nm stores integers).
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Stored value (rounded integer for nm, else input).
|
|
37
|
+
"""
|
|
38
|
+
if units == "nm":
|
|
39
|
+
return int(round(value))
|
|
40
|
+
return value
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def check_mfg_grid(point, grid):
|
|
44
|
+
"""Check one authored point against the manufacturing grid.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
point: Mapping with x and y authored coordinates.
|
|
48
|
+
grid: Manufacturing grid pitch in storage units.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Verdict with state ok or violation; violations keep
|
|
52
|
+
the authored point untouched for the caller to move.
|
|
53
|
+
|
|
54
|
+
Raises:
|
|
55
|
+
ValueError: For a non-positive grid pitch.
|
|
56
|
+
"""
|
|
57
|
+
if grid <= 0:
|
|
58
|
+
raise ValueError(f"Grid pitch must be positive: {grid!r}.")
|
|
59
|
+
off_x = point["x"] % grid
|
|
60
|
+
off_y = point["y"] % grid
|
|
61
|
+
if off_x == 0 and off_y == 0:
|
|
62
|
+
return {"authored": dict(point), "state": "ok"}
|
|
63
|
+
return {
|
|
64
|
+
"authored": dict(point),
|
|
65
|
+
"reason": (
|
|
66
|
+
f"Point {point} is off the {grid}-unit "
|
|
67
|
+
"manufacturing grid; authored geometry unchanged."
|
|
68
|
+
),
|
|
69
|
+
"state": "violation",
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def snap_point(point, grid):
|
|
74
|
+
"""Snap one point dict to the UI grid for new edits.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
point: Mapping with x and y coordinates.
|
|
78
|
+
grid: UI snap grid pitch in storage units.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
New point mapping with snapped coordinates.
|
|
82
|
+
"""
|
|
83
|
+
return {
|
|
84
|
+
"x": snap_to_grid(point["x"], grid),
|
|
85
|
+
"y": snap_to_grid(point["y"], grid),
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def storage_box(x, y, units):
|
|
90
|
+
"""Store one anchor point without grid rewriting.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
x: Authored x coordinate.
|
|
94
|
+
y: Authored y coordinate.
|
|
95
|
+
units: Storage units label.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
Mapping with quantized x and y.
|
|
99
|
+
"""
|
|
100
|
+
return {
|
|
101
|
+
"x": quantize_storage(x, units),
|
|
102
|
+
"y": quantize_storage(y, units),
|
|
103
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""IP package marker: prebuilt hierarchical block lifecycle."""
|