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.
Files changed (241) hide show
  1. rocket_engine/__init__.py +8 -0
  2. rocket_engine/__main__.py +12 -0
  3. rocket_engine/adapters/README.md +54 -0
  4. rocket_engine/adapters/__init__.py +7 -0
  5. rocket_engine/adapters/api.py +39 -0
  6. rocket_engine/adapters/base.py +46 -0
  7. rocket_engine/adapters/cace/__init__.py +163 -0
  8. rocket_engine/adapters/circulax/README.md +10 -0
  9. rocket_engine/adapters/circulax/__init__.py +112 -0
  10. rocket_engine/adapters/circulax/_driver.py +190 -0
  11. rocket_engine/adapters/circulax/adapter.py +538 -0
  12. rocket_engine/adapters/circulax/results.py +291 -0
  13. rocket_engine/adapters/cli/README.md +34 -0
  14. rocket_engine/adapters/cli/__init__.py +305 -0
  15. rocket_engine/adapters/connectivity/__init__.py +196 -0
  16. rocket_engine/adapters/connectivity/results.py +214 -0
  17. rocket_engine/adapters/cosim/README.md +9 -0
  18. rocket_engine/adapters/cosim/__init__.py +1390 -0
  19. rocket_engine/adapters/cosim/results.py +214 -0
  20. rocket_engine/adapters/devsim/__init__.py +171 -0
  21. rocket_engine/adapters/digital_flow/README.md +9 -0
  22. rocket_engine/adapters/digital_flow/__init__.py +1121 -0
  23. rocket_engine/adapters/digital_flow/results.py +227 -0
  24. rocket_engine/adapters/digital_flow/state.py +462 -0
  25. rocket_engine/adapters/drc/README.klayout.md +7 -0
  26. rocket_engine/adapters/drc/README.md +7 -0
  27. rocket_engine/adapters/drc/__init__.py +1 -0
  28. rocket_engine/adapters/drc/klayout.py +952 -0
  29. rocket_engine/adapters/drc/magic.py +287 -0
  30. rocket_engine/adapters/drc/results.py +184 -0
  31. rocket_engine/adapters/drc/results_kl.py +188 -0
  32. rocket_engine/adapters/elmer/__init__.py +163 -0
  33. rocket_engine/adapters/envelope.py +120 -0
  34. rocket_engine/adapters/equivalence/__init__.py +156 -0
  35. rocket_engine/adapters/errors.py +181 -0
  36. rocket_engine/adapters/extract_magic/README.md +15 -0
  37. rocket_engine/adapters/extract_magic/__init__.py +425 -0
  38. rocket_engine/adapters/extract_magic/results.py +209 -0
  39. rocket_engine/adapters/formal_sby/README.md +7 -0
  40. rocket_engine/adapters/formal_sby/__init__.py +773 -0
  41. rocket_engine/adapters/formal_sby/formal_edge.py +195 -0
  42. rocket_engine/adapters/formal_sby/results.py +185 -0
  43. rocket_engine/adapters/gdsfactory/__init__.py +194 -0
  44. rocket_engine/adapters/gdsfactory/_driver.py +459 -0
  45. rocket_engine/adapters/gdsfactory/adapter.py +609 -0
  46. rocket_engine/adapters/gdsfactory/results.py +181 -0
  47. rocket_engine/adapters/hdl_sim/README.md +7 -0
  48. rocket_engine/adapters/hdl_sim/__init__.py +369 -0
  49. rocket_engine/adapters/hdl_sim/ghdl.py +155 -0
  50. rocket_engine/adapters/hdl_sim/results.py +178 -0
  51. rocket_engine/adapters/hdl_sim/verilator.py +167 -0
  52. rocket_engine/adapters/http/README.md +15 -0
  53. rocket_engine/adapters/http/__init__.py +458 -0
  54. rocket_engine/adapters/http/events.py +63 -0
  55. rocket_engine/adapters/ipc/README.md +9 -0
  56. rocket_engine/adapters/ipc/__init__.py +111 -0
  57. rocket_engine/adapters/lvs_netgen/README.md +20 -0
  58. rocket_engine/adapters/lvs_netgen/__init__.py +889 -0
  59. rocket_engine/adapters/lvs_netgen/results.py +168 -0
  60. rocket_engine/adapters/mcp/README.md +19 -0
  61. rocket_engine/adapters/mcp/__init__.py +85 -0
  62. rocket_engine/adapters/mcp/api-schema.json +6107 -0
  63. rocket_engine/adapters/mcp/pull.py +246 -0
  64. rocket_engine/adapters/mcp/server.py +794 -0
  65. rocket_engine/adapters/meep/__init__.py +167 -0
  66. rocket_engine/adapters/meow/README.md +7 -0
  67. rocket_engine/adapters/meow/__init__.py +190 -0
  68. rocket_engine/adapters/meow/_driver.py +227 -0
  69. rocket_engine/adapters/meow/_envelope.py +108 -0
  70. rocket_engine/adapters/meow/adapter.py +491 -0
  71. rocket_engine/adapters/meow/results.py +218 -0
  72. rocket_engine/adapters/mutation/__init__.py +192 -0
  73. rocket_engine/adapters/ngspice/README.md +28 -0
  74. rocket_engine/adapters/ngspice/__init__.py +627 -0
  75. rocket_engine/adapters/ngspice/results.py +215 -0
  76. rocket_engine/adapters/postlayout/README.md +28 -0
  77. rocket_engine/adapters/postlayout/__init__.py +17 -0
  78. rocket_engine/adapters/postlayout/adapter.py +1062 -0
  79. rocket_engine/adapters/postlayout/results.py +330 -0
  80. rocket_engine/adapters/postlayout/translate.py +71 -0
  81. rocket_engine/adapters/profile.py +220 -0
  82. rocket_engine/adapters/sax/README.md +23 -0
  83. rocket_engine/adapters/sax/__init__.py +516 -0
  84. rocket_engine/adapters/sax/_driver.py +148 -0
  85. rocket_engine/adapters/sax/results.py +291 -0
  86. rocket_engine/adapters/stdio_console.py +1151 -0
  87. rocket_engine/adapters/syntax/README.md +5 -0
  88. rocket_engine/adapters/syntax/__init__.py +366 -0
  89. rocket_engine/adapters/syntax/results.py +181 -0
  90. rocket_engine/adapters/synth_yosys/README.md +7 -0
  91. rocket_engine/adapters/synth_yosys/__init__.py +655 -0
  92. rocket_engine/adapters/synth_yosys/amaranth.py +120 -0
  93. rocket_engine/adapters/synth_yosys/ghdl.py +128 -0
  94. rocket_engine/adapters/synth_yosys/results.py +173 -0
  95. rocket_engine/adapters/timing_sta/README.md +9 -0
  96. rocket_engine/adapters/timing_sta/__init__.py +726 -0
  97. rocket_engine/adapters/view_sync/__init__.py +185 -0
  98. rocket_engine/adapters/view_sync/results.py +189 -0
  99. rocket_engine/adapters/vlsir/__init__.py +142 -0
  100. rocket_engine/adapters/xschem/__init__.py +440 -0
  101. rocket_engine/adapters/xschem/deck.py +266 -0
  102. rocket_engine/clients/__init__.py +5 -0
  103. rocket_engine/clients/runtime.py +255 -0
  104. rocket_engine/composition.py +2279 -0
  105. rocket_engine/execution/README.md +30 -0
  106. rocket_engine/execution/__init__.py +150 -0
  107. rocket_engine/execution/apig/__init__.py +1 -0
  108. rocket_engine/execution/apig/pool.py +463 -0
  109. rocket_engine/execution/apig/submit.py +124 -0
  110. rocket_engine/execution/block_runs_scope.py +479 -0
  111. rocket_engine/execution/coalesce.py +414 -0
  112. rocket_engine/execution/console/__init__.py +1 -0
  113. rocket_engine/execution/console/apply_session.py +318 -0
  114. rocket_engine/execution/console/lifecycle.py +265 -0
  115. rocket_engine/execution/dispatch.py +205 -0
  116. rocket_engine/execution/errors.py +31 -0
  117. rocket_engine/execution/ports.py +797 -0
  118. rocket_engine/execution/pyexec/__init__.py +1 -0
  119. rocket_engine/execution/pyexec/apply_context.py +547 -0
  120. rocket_engine/execution/pyexec/approvals.py +173 -0
  121. rocket_engine/execution/pyexec/runner.py +404 -0
  122. rocket_engine/execution/pyexec/support.py +464 -0
  123. rocket_engine/execution/python_exec.py +897 -0
  124. rocket_engine/execution/queue.py +85 -0
  125. rocket_engine/execution/replay_safe.py +601 -0
  126. rocket_engine/execution/routing/__init__.py +1 -0
  127. rocket_engine/execution/routing/calls.py +364 -0
  128. rocket_engine/execution/routing/namespace.py +785 -0
  129. rocket_engine/execution/routing/ports.py +102 -0
  130. rocket_engine/execution/routing/tools.py +346 -0
  131. rocket_engine/execution/runner.py +311 -0
  132. rocket_engine/execution/runtime.py +1149 -0
  133. rocket_engine/execution/service.py +279 -0
  134. rocket_engine/execution/worker.py +464 -0
  135. rocket_engine/interfaces/README.md +26 -0
  136. rocket_engine/interfaces/__init__.py +58 -0
  137. rocket_engine/interfaces/apig_assets.py +407 -0
  138. rocket_engine/interfaces/celllink.py +461 -0
  139. rocket_engine/interfaces/documents.py +92 -0
  140. rocket_engine/interfaces/envelopes.py +131 -0
  141. rocket_engine/interfaces/errors.py +16 -0
  142. rocket_engine/interfaces/layout.py +410 -0
  143. rocket_engine/interfaces/layout_schema.yaml +278 -0
  144. rocket_engine/interfaces/paths.py +223 -0
  145. rocket_engine/interfaces/pdks.py +550 -0
  146. rocket_engine/interfaces/ports.py +72 -0
  147. rocket_engine/interfaces/references.py +115 -0
  148. rocket_engine/interfaces/refs.py +85 -0
  149. rocket_engine/interfaces/runparams.py +304 -0
  150. rocket_engine/interfaces/setup.py +552 -0
  151. rocket_engine/interfaces/setup_catalogue.yaml +243 -0
  152. rocket_engine/interfaces/setup_probes.py +66 -0
  153. rocket_engine/interfaces/vocabulary.py +58 -0
  154. rocket_engine/interfaces/yaml_dump.py +31 -0
  155. rocket_engine/library/README.md +22 -0
  156. rocket_engine/library/__init__.py +13 -0
  157. rocket_engine/library/apig_catalog.py +371 -0
  158. rocket_engine/library/catalog.py +185 -0
  159. rocket_engine/library/errors.py +16 -0
  160. rocket_engine/mutate/__init__.py +776 -0
  161. rocket_engine/mutate/operators.py +700 -0
  162. rocket_engine/ops/__init__.py +1 -0
  163. rocket_engine/ops/deferred.py +279 -0
  164. rocket_engine/ops/editors.py +404 -0
  165. rocket_engine/ops/evidence.py +194 -0
  166. rocket_engine/ops/gates.py +262 -0
  167. rocket_engine/ops/ports.py +76 -0
  168. rocket_engine/ops/queries.py +417 -0
  169. rocket_engine/ops/runs.py +553 -0
  170. rocket_engine/ops/submit.py +614 -0
  171. rocket_engine/project/README.md +27 -0
  172. rocket_engine/project/__init__.py +6 -0
  173. rocket_engine/project/canvas/__init__.py +55 -0
  174. rocket_engine/project/canvas/compile.py +109 -0
  175. rocket_engine/project/canvas/integrate.py +48 -0
  176. rocket_engine/project/canvas/schema.py +241 -0
  177. rocket_engine/project/canvas/templates.py +46 -0
  178. rocket_engine/project/canvas/validate.py +603 -0
  179. rocket_engine/project/edit/__init__.py +5 -0
  180. rocket_engine/project/edit/cas.py +87 -0
  181. rocket_engine/project/edit/transactions.py +282 -0
  182. rocket_engine/project/errors.py +16 -0
  183. rocket_engine/project/frontend13.py +685 -0
  184. rocket_engine/project/geometry/__init__.py +23 -0
  185. rocket_engine/project/geometry/ops.py +451 -0
  186. rocket_engine/project/geometry/snap.py +103 -0
  187. rocket_engine/project/ip/__init__.py +1 -0
  188. rocket_engine/project/ip/blocks.py +195 -0
  189. rocket_engine/project/ip/entry.schema.json +78 -0
  190. rocket_engine/project/ip/lifecycle.py +402 -0
  191. rocket_engine/project/ip/verbs.py +123 -0
  192. rocket_engine/project/layers/__init__.py +6 -0
  193. rocket_engine/project/layers/bindings.py +115 -0
  194. rocket_engine/project/layers/catalog.py +228 -0
  195. rocket_engine/project/layers/presets.py +43 -0
  196. rocket_engine/project/ledger/__init__.py +1 -0
  197. rocket_engine/project/ledger/docs.py +167 -0
  198. rocket_engine/project/ledger/entities.py +176 -0
  199. rocket_engine/project/ledger/events.py +29 -0
  200. rocket_engine/project/ledger/runs.py +257 -0
  201. rocket_engine/project/ledger/shared.py +174 -0
  202. rocket_engine/project/ledger/snapshots.py +385 -0
  203. rocket_engine/project/ledger/wires.py +552 -0
  204. rocket_engine/project/process/__init__.py +14 -0
  205. rocket_engine/project/process/model.py +193 -0
  206. rocket_engine/project/process/recipes.py +121 -0
  207. rocket_engine/project/schematic/__init__.py +1 -0
  208. rocket_engine/project/schematic/hierarchy.py +312 -0
  209. rocket_engine/project/schematic/instances.py +393 -0
  210. rocket_engine/project/schematic/sheets.py +198 -0
  211. rocket_engine/project/service.py +2941 -0
  212. rocket_engine/project/symbols/__init__.py +1 -0
  213. rocket_engine/project/symbols/cells.py +324 -0
  214. rocket_engine/project/symbols/linkage.py +356 -0
  215. rocket_engine/project/symbols/symbols.py +352 -0
  216. rocket_engine/project/templates/__init__.py +303 -0
  217. rocket_engine/project/templates/errors.py +25 -0
  218. rocket_engine/project/testbench/__init__.py +49 -0
  219. rocket_engine/project/testbench/blocks.py +619 -0
  220. rocket_engine/project/testbench/templates.py +134 -0
  221. rocket_engine/stores/README.md +12 -0
  222. rocket_engine/stores/__init__.py +6 -0
  223. rocket_engine/stores/ecs.py +1508 -0
  224. rocket_engine/stores/layers.py +1023 -0
  225. rocket_engine/stores/settings.py +1188 -0
  226. rocket_engine/stores/wireplace.py +1324 -0
  227. rocket_engine/workflows/README.md +18 -0
  228. rocket_engine/workflows/__init__.py +53 -0
  229. rocket_engine/workflows/capability_plan.py +102 -0
  230. rocket_engine/workflows/completion.py +228 -0
  231. rocket_engine/workflows/connectgraph.py +734 -0
  232. rocket_engine/workflows/connectgraph_emit.py +336 -0
  233. rocket_engine/workflows/gates.py +144 -0
  234. rocket_engine/workflows/photonic_wire.py +76 -0
  235. rocket_engine/workflows/stages.py +324 -0
  236. rocket_engine/workflows/viewsync.py +399 -0
  237. rocket_engine-0.0.2b1.dist-info/METADATA +200 -0
  238. rocket_engine-0.0.2b1.dist-info/RECORD +241 -0
  239. rocket_engine-0.0.2b1.dist-info/WHEEL +4 -0
  240. rocket_engine-0.0.2b1.dist-info/entry_points.txt +2 -0
  241. rocket_engine-0.0.2b1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,393 @@
1
+ """Symbol instances: placed schematic references with transforms.
2
+
3
+ Instances point at a sheet plus a symbol: a bare project symbol
4
+ name (resolved against project Symbol entities) or a scoped
5
+ upstream reference (``pdk:``, ``lib:``, ``pkg:``) that passes
6
+ through untouched — PDK/library bytes are never edited here.
7
+ """
8
+
9
+ from ...interfaces.errors import ContractError
10
+
11
+ #: Component name instances carry on their entity.
12
+ INSTANCE_COMPONENT = "Instance"
13
+
14
+ #: Allowed rotation steps in degrees.
15
+ ROTATIONS = (0, 90, 180, 270)
16
+
17
+ #: Prefixes treated as immutable upstream references.
18
+ SCOPED_PREFIXES = ("pdk:", "lib:", "pkg:")
19
+
20
+
21
+ def _is_int(value):
22
+ """True for real ints (bools excluded)."""
23
+ return isinstance(value, int) and not isinstance(value, bool)
24
+
25
+
26
+ def validate_instance(instance):
27
+ """Validate an Instance component mapping at edit time.
28
+
29
+ Args:
30
+ instance: Instance mapping with sheet, symbol, x, y,
31
+ rotation, mirror, properties, and ref keys.
32
+
33
+ Returns:
34
+ Normalized Instance copy.
35
+
36
+ Raises:
37
+ ContractError: invalid_instance for any shape violation.
38
+ """
39
+ if not isinstance(instance, dict):
40
+ raise ContractError(
41
+ "invalid_instance",
42
+ f"Instance must be a mapping, got {instance!r}.",
43
+ )
44
+ for key in instance:
45
+ if key not in ("sheet", "symbol", "x", "y", "rotation",
46
+ "mirror", "properties", "ref"):
47
+ raise ContractError(
48
+ "invalid_instance", f"Unknown Instance key: {key}."
49
+ )
50
+ sheet = instance.get("sheet")
51
+ if not isinstance(sheet, str) or not sheet:
52
+ raise ContractError(
53
+ "invalid_instance", "Instance sheet must be an entity id."
54
+ )
55
+ symbol = instance.get("symbol")
56
+ if not isinstance(symbol, str) or not symbol:
57
+ raise ContractError(
58
+ "invalid_instance", "Instance symbol must be a reference."
59
+ )
60
+ for axis in ("x", "y"):
61
+ if not _is_int(instance.get(axis)):
62
+ raise ContractError(
63
+ "invalid_instance",
64
+ f"Instance {axis} must be an int, got "
65
+ f"{instance.get(axis)!r}.",
66
+ )
67
+ if instance.get("rotation") not in ROTATIONS:
68
+ raise ContractError(
69
+ "invalid_instance",
70
+ f"Instance rotation must be one of {ROTATIONS}.",
71
+ )
72
+ if not isinstance(instance.get("mirror"), bool):
73
+ raise ContractError(
74
+ "invalid_instance", "Instance mirror must be a bool."
75
+ )
76
+ properties = instance.get("properties", {})
77
+ if not isinstance(properties, dict) or not all(
78
+ isinstance(key, str) and isinstance(val, str)
79
+ for key, val in properties.items()
80
+ ):
81
+ raise ContractError(
82
+ "invalid_instance",
83
+ "Instance properties must map text to text.",
84
+ )
85
+ ref = instance.get("ref", "")
86
+ if not isinstance(ref, str):
87
+ raise ContractError(
88
+ "invalid_instance", "Instance ref must be text."
89
+ )
90
+ return {
91
+ "sheet": sheet, "symbol": symbol,
92
+ "x": instance["x"], "y": instance["y"],
93
+ "rotation": instance["rotation"],
94
+ "mirror": instance["mirror"],
95
+ "properties": dict(properties), "ref": ref,
96
+ }
97
+
98
+
99
+ def _read_instance(store, instance_id):
100
+ """Return the normalized Instance for one entity id."""
101
+ try:
102
+ raw = store.get_component(instance_id, INSTANCE_COMPONENT)
103
+ except LookupError:
104
+ raise ContractError(
105
+ "unknown_instance",
106
+ f"No such instance entity: {instance_id!r}.",
107
+ ) from None
108
+ value = validate_instance(raw)
109
+ value["id"] = instance_id
110
+ return value
111
+
112
+
113
+ def _check_sheet(store, sheet_id):
114
+ """Fail loudly when a sheet entity is missing."""
115
+ try:
116
+ store.get_component(sheet_id, "Sheet")
117
+ except LookupError:
118
+ raise ContractError(
119
+ "unknown_sheet", f"No such sheet entity: {sheet_id!r}."
120
+ ) from None
121
+
122
+
123
+ def _check_symbol(store, symbol):
124
+ """Resolve one symbol ref: bare names must exist, scoped pass."""
125
+ if symbol.startswith(SCOPED_PREFIXES):
126
+ return
127
+ for entity in store.entities_with("Symbol"):
128
+ try:
129
+ found = store.get_component(entity, "Symbol")
130
+ except LookupError:
131
+ continue
132
+ if isinstance(found, dict) and found.get("name") == symbol:
133
+ return
134
+ raise ContractError(
135
+ "unknown_symbol", f"No such project symbol: {symbol!r}."
136
+ )
137
+
138
+
139
+ def place_instance(store, actor, sheet, symbol, x, y, rotation=0,
140
+ mirror=False, properties=None, ref=""):
141
+ """Place one symbol instance on a sheet.
142
+
143
+ Args:
144
+ store: Entity store serving the ENGINE-ECS seam.
145
+ actor: Attribution recorded on the revision and events.
146
+ sheet: Sheet entity id hosting the instance.
147
+ symbol: Bare project symbol name or a scoped upstream
148
+ reference (``pdk:``, ``lib:``, ``pkg:``).
149
+ x: X position in schematic units.
150
+ y: Y position in schematic units.
151
+ rotation: Rotation in degrees (0/90/180/270).
152
+ mirror: Mirror flag.
153
+ properties: Text-to-text instance properties.
154
+ ref: Annotation reference ("" means unannotated).
155
+
156
+ Returns:
157
+ Receipt with op, actor, revision, instance id, and events.
158
+
159
+ Raises:
160
+ ContractError: unknown_sheet for a missing sheet,
161
+ unknown_symbol for a missing project symbol,
162
+ invalid_instance for a malformed instance.
163
+ """
164
+ _check_sheet(store, sheet)
165
+ _check_symbol(store, symbol)
166
+ value = validate_instance({
167
+ "sheet": sheet, "symbol": symbol, "x": x, "y": y,
168
+ "rotation": rotation, "mirror": mirror,
169
+ "properties": properties or {}, "ref": ref,
170
+ })
171
+ made = store.create_entity(actor, f"place_instance {symbol}")
172
+ store.attach_component(
173
+ actor, made["id"], INSTANCE_COMPONENT, value,
174
+ f"place_instance {symbol}",
175
+ )
176
+ return {
177
+ "op": "place_instance", "actor": actor,
178
+ "revision": made["revision"], "instance": made["id"],
179
+ "events": [
180
+ {"event": "entity_created", "entity": made["id"]},
181
+ {"event": "component_changed", "entity": made["id"],
182
+ "component": INSTANCE_COMPONENT},
183
+ ],
184
+ }
185
+
186
+
187
+ def _rewrite(store, actor, instance_id, mutate, op):
188
+ """Apply one instance mutation and report the receipt."""
189
+ value = _read_instance(store, instance_id)
190
+ mutate(value)
191
+ clean = validate_instance({
192
+ key: value[key] for key in (
193
+ "sheet", "symbol", "x", "y", "rotation",
194
+ "mirror", "properties", "ref")
195
+ })
196
+ changed = store.set_component(
197
+ actor, instance_id, INSTANCE_COMPONENT, clean,
198
+ f"{op} {instance_id}",
199
+ )
200
+ receipt = {
201
+ "op": op, "actor": actor,
202
+ "revision": changed["revision"], "instance": instance_id,
203
+ "events": [],
204
+ }
205
+ receipt.update({
206
+ key: clean[key] for key in (
207
+ "x", "y", "rotation", "mirror", "properties", "ref")
208
+ })
209
+ return receipt
210
+
211
+
212
+ def move_instance(store, actor, instance_id, x, y):
213
+ """Move one instance to a new schematic position.
214
+
215
+ Args:
216
+ store: Entity store serving the ENGINE-ECS seam.
217
+ actor: Attribution recorded on the revision and events.
218
+ instance_id: Instance entity id to move.
219
+ x: New X position in schematic units.
220
+ y: New Y position in schematic units.
221
+
222
+ Returns:
223
+ Receipt with op, actor, revision, instance id, and
224
+ the new position.
225
+
226
+ Raises:
227
+ ContractError: unknown_instance for a missing instance,
228
+ invalid_instance for a malformed position.
229
+ """
230
+ def _move(value):
231
+ value["x"] = x
232
+ value["y"] = y
233
+
234
+ return _rewrite(store, actor, instance_id, _move, "move_instance")
235
+
236
+
237
+ def rotate_instance(store, actor, instance_id, rotation):
238
+ """Rotate one instance to an orthogonal orientation.
239
+
240
+ Args:
241
+ store: Entity store serving the ENGINE-ECS seam.
242
+ actor: Attribution recorded on the revision and events.
243
+ instance_id: Instance entity id to rotate.
244
+ rotation: New rotation in degrees (0/90/180/270).
245
+
246
+ Returns:
247
+ Receipt with op, actor, revision, instance id, and
248
+ the new rotation.
249
+
250
+ Raises:
251
+ ContractError: unknown_instance for a missing instance,
252
+ invalid_instance for a bad rotation.
253
+ """
254
+ def _rotate(value):
255
+ value["rotation"] = rotation
256
+
257
+ return _rewrite(store, actor, instance_id, _rotate, "rotate_instance")
258
+
259
+
260
+ def mirror_instance(store, actor, instance_id, mirror=True):
261
+ """Mirror (or unmirror) one instance.
262
+
263
+ Args:
264
+ store: Entity store serving the ENGINE-ECS seam.
265
+ actor: Attribution recorded on the revision and events.
266
+ instance_id: Instance entity id to mirror.
267
+ mirror: Mirror flag to set.
268
+
269
+ Returns:
270
+ Receipt with op, actor, revision, instance id, and
271
+ the new mirror flag.
272
+
273
+ Raises:
274
+ ContractError: unknown_instance for a missing instance,
275
+ invalid_instance for a non-bool flag.
276
+ """
277
+ def _mirror(value):
278
+ value["mirror"] = mirror
279
+
280
+ return _rewrite(store, actor, instance_id, _mirror, "mirror_instance")
281
+
282
+
283
+ def set_instance_properties(store, actor, instance_id, properties):
284
+ """Replace one instance's text properties.
285
+
286
+ Args:
287
+ store: Entity store serving the ENGINE-ECS seam.
288
+ actor: Attribution recorded on the revision and events.
289
+ instance_id: Instance entity id to update.
290
+ properties: New text-to-text property mapping.
291
+
292
+ Returns:
293
+ Receipt with op, actor, revision, instance id, and
294
+ the new properties.
295
+
296
+ Raises:
297
+ ContractError: unknown_instance for a missing instance,
298
+ invalid_instance for malformed properties.
299
+ """
300
+ def _props(value):
301
+ value["properties"] = properties
302
+
303
+ return _rewrite(
304
+ store, actor, instance_id, _props, "set_instance_properties"
305
+ )
306
+
307
+
308
+ def set_instance_ref(store, actor, instance_id, ref):
309
+ """Set one instance's annotation reference.
310
+
311
+ Args:
312
+ store: Entity store serving the ENGINE-ECS seam.
313
+ actor: Attribution recorded on the revision and events.
314
+ instance_id: Instance entity id to annotate.
315
+ ref: New reference text ("" clears the annotation).
316
+
317
+ Returns:
318
+ Receipt with op, actor, revision, instance id, and ref.
319
+
320
+ Raises:
321
+ ContractError: unknown_instance for a missing instance,
322
+ invalid_instance for a non-text ref.
323
+ """
324
+ def _ref(value):
325
+ value["ref"] = ref
326
+
327
+ return _rewrite(
328
+ store, actor, instance_id, _ref, "set_instance_ref"
329
+ )
330
+
331
+
332
+ def get_instance(store, instance_id):
333
+ """Read one instance by entity id.
334
+
335
+ Args:
336
+ store: Entity store serving the ENGINE-ECS seam.
337
+ instance_id: Instance entity id to read.
338
+
339
+ Returns:
340
+ Instance mapping with id, sheet, symbol, transform,
341
+ properties, and ref.
342
+
343
+ Raises:
344
+ ContractError: unknown_instance for a missing instance.
345
+ """
346
+ return _read_instance(store, instance_id)
347
+
348
+
349
+ def instances_on_sheet(store, sheet):
350
+ """List every instance placed on one sheet, id-sorted.
351
+
352
+ Args:
353
+ store: Entity store serving the ENGINE-ECS seam.
354
+ sheet: Sheet entity id to scan.
355
+
356
+ Returns:
357
+ Instance mappings with ids, id-sorted.
358
+ """
359
+ found = []
360
+ for entity in store.entities_with(INSTANCE_COMPONENT):
361
+ try:
362
+ value = _read_instance(store, entity)
363
+ except ContractError:
364
+ continue
365
+ if value["sheet"] == sheet:
366
+ found.append(value)
367
+ found.sort(key=lambda item: item["id"])
368
+ return found
369
+
370
+
371
+ def delete_instance(store, actor, instance_id):
372
+ """Delete one instance entity.
373
+
374
+ Args:
375
+ store: Entity store serving the ENGINE-ECS seam.
376
+ actor: Attribution recorded on the revision and events.
377
+ instance_id: Instance entity id to delete.
378
+
379
+ Returns:
380
+ Receipt with op, actor, revision, and instance id.
381
+
382
+ Raises:
383
+ ContractError: unknown_instance for a missing instance.
384
+ """
385
+ _read_instance(store, instance_id)
386
+ done = store.delete_entity(
387
+ actor, instance_id, f"delete_instance {instance_id}"
388
+ )
389
+ return {
390
+ "op": "delete_instance", "actor": actor,
391
+ "revision": done["revision"], "instance": instance_id,
392
+ "events": [{"event": "entity_deleted", "entity": instance_id}],
393
+ }
@@ -0,0 +1,198 @@
1
+ """Schematic sheets: durable sheet identities for hierarchical schematics."""
2
+
3
+ from ...interfaces.errors import ContractError
4
+
5
+ #: Component name sheets carry on their entity.
6
+ SHEET_COMPONENT = "Sheet"
7
+
8
+
9
+ def _need_name(value):
10
+ """Accept one sheet name (non-empty, no whitespace)."""
11
+ if not isinstance(value, str) or not value or value.split() != [value]:
12
+ raise ContractError(
13
+ "invalid_sheet", f"Sheet name must be blank-free text, got {value!r}."
14
+ )
15
+ return value
16
+
17
+
18
+ def validate_sheet(sheet):
19
+ """Validate a Sheet component mapping at edit time.
20
+
21
+ Args:
22
+ sheet: Sheet mapping with name and title keys.
23
+
24
+ Returns:
25
+ Normalized Sheet copy.
26
+
27
+ Raises:
28
+ ContractError: invalid_sheet for any shape violation.
29
+ """
30
+ if not isinstance(sheet, dict):
31
+ raise ContractError(
32
+ "invalid_sheet", f"Sheet must be a mapping, got {sheet!r}."
33
+ )
34
+ for key in sheet:
35
+ if key not in ("name", "title"):
36
+ raise ContractError(
37
+ "invalid_sheet", f"Unknown Sheet key: {key}."
38
+ )
39
+ title = sheet.get("title", "")
40
+ if not isinstance(title, str):
41
+ raise ContractError("invalid_sheet", "Sheet title must be text.")
42
+ return {"name": _need_name(sheet.get("name")), "title": title}
43
+
44
+
45
+ def _read_sheet(store, sheet_id):
46
+ """Return the normalized Sheet for one entity id."""
47
+ try:
48
+ raw = store.get_component(sheet_id, SHEET_COMPONENT)
49
+ except LookupError:
50
+ raise ContractError(
51
+ "unknown_sheet", f"No such sheet entity: {sheet_id!r}."
52
+ ) from None
53
+ sheet = validate_sheet(raw)
54
+ sheet["id"] = sheet_id
55
+ return sheet
56
+
57
+
58
+ def _sheet_entities(store):
59
+ """List (entity, sheet) pairs for every sheet, name-sorted."""
60
+ found = []
61
+ for entity in store.entities_with(SHEET_COMPONENT):
62
+ found.append((entity, _read_sheet(store, entity)))
63
+ found.sort(key=lambda pair: pair[1]["name"])
64
+ return found
65
+
66
+
67
+ def create_sheet(store, actor, name, title=""):
68
+ """Create one schematic sheet as a durable attributed revision.
69
+
70
+ Args:
71
+ store: Entity store serving the ENGINE-ECS seam.
72
+ actor: Attribution recorded on the revision and events.
73
+ name: Unique sheet name (blank-free text).
74
+ title: Human title shown on the sheet frame.
75
+
76
+ Returns:
77
+ Receipt with op, actor, revision, sheet id, and events.
78
+
79
+ Raises:
80
+ ContractError: invalid_sheet for a malformed sheet,
81
+ sheet_exists for a duplicate name.
82
+ """
83
+ value = validate_sheet({"name": name, "title": title})
84
+ for _, sheet in _sheet_entities(store):
85
+ if sheet["name"] == value["name"]:
86
+ raise ContractError(
87
+ "sheet_exists", f"Sheet already exists: {value['name']!r}."
88
+ )
89
+ made = store.create_entity(actor, f"create_sheet {value['name']}")
90
+ store.attach_component(
91
+ actor, made["id"], SHEET_COMPONENT, value,
92
+ f"create_sheet {value['name']}",
93
+ )
94
+ return {
95
+ "op": "create_sheet", "actor": actor,
96
+ "revision": made["revision"], "sheet": made["id"],
97
+ "events": [
98
+ {"event": "entity_created", "entity": made["id"]},
99
+ {"event": "component_changed", "entity": made["id"],
100
+ "component": SHEET_COMPONENT},
101
+ ],
102
+ }
103
+
104
+
105
+ def rename_sheet(store, actor, sheet_id, name):
106
+ """Rename one schematic sheet.
107
+
108
+ Args:
109
+ store: Entity store serving the ENGINE-ECS seam.
110
+ actor: Attribution recorded on the revision and events.
111
+ sheet_id: Sheet entity id to rename.
112
+ name: New unique sheet name.
113
+
114
+ Returns:
115
+ Receipt with op, actor, revision, sheet id, and name.
116
+
117
+ Raises:
118
+ ContractError: unknown_sheet for a missing sheet,
119
+ sheet_exists for a duplicate name.
120
+ """
121
+ sheet = _read_sheet(store, sheet_id)
122
+ value = validate_sheet({"name": name, "title": sheet["title"]})
123
+ for entity, other in _sheet_entities(store):
124
+ if entity != sheet_id and other["name"] == value["name"]:
125
+ raise ContractError(
126
+ "sheet_exists", f"Sheet already exists: {value['name']!r}."
127
+ )
128
+ changed = store.set_component(
129
+ actor, sheet_id, SHEET_COMPONENT, value,
130
+ f"rename_sheet {value['name']}",
131
+ )
132
+ return {
133
+ "op": "rename_sheet", "actor": actor,
134
+ "revision": changed["revision"], "sheet": sheet_id,
135
+ "name": value["name"], "events": [],
136
+ }
137
+
138
+
139
+ def get_sheet(store, sheet_id):
140
+ """Read one schematic sheet by entity id.
141
+
142
+ Args:
143
+ store: Entity store serving the ENGINE-ECS seam.
144
+ sheet_id: Sheet entity id to read.
145
+
146
+ Returns:
147
+ Sheet mapping with id, name, and title.
148
+
149
+ Raises:
150
+ ContractError: unknown_sheet for a missing sheet.
151
+ """
152
+ return _read_sheet(store, sheet_id)
153
+
154
+
155
+ def list_sheets(store):
156
+ """List every schematic sheet, name-sorted.
157
+
158
+ Args:
159
+ store: Entity store serving the ENGINE-ECS seam.
160
+
161
+ Returns:
162
+ Sheet mappings with id, name, and title.
163
+ """
164
+ return [sheet for _, sheet in _sheet_entities(store)]
165
+
166
+
167
+ def delete_sheet(store, actor, sheet_id):
168
+ """Delete one schematic sheet; refuse when instances remain.
169
+
170
+ Args:
171
+ store: Entity store serving the ENGINE-ECS seam.
172
+ actor: Attribution recorded on the revision and events.
173
+ sheet_id: Sheet entity id to delete.
174
+
175
+ Returns:
176
+ Receipt with op, actor, revision, and sheet id.
177
+
178
+ Raises:
179
+ ContractError: unknown_sheet for a missing sheet,
180
+ sheet_in_use when instances still sit on it.
181
+ """
182
+ _read_sheet(store, sheet_id)
183
+ for entity in store.entities_with("Instance"):
184
+ try:
185
+ instance = store.get_component(entity, "Instance")
186
+ except LookupError:
187
+ continue
188
+ if isinstance(instance, dict) and instance.get("sheet") == sheet_id:
189
+ raise ContractError(
190
+ "sheet_in_use",
191
+ f"Sheet {sheet_id!r} still hosts instance {entity!r}.",
192
+ )
193
+ done = store.delete_entity(actor, sheet_id, f"delete_sheet {sheet_id}")
194
+ return {
195
+ "op": "delete_sheet", "actor": actor,
196
+ "revision": done["revision"], "sheet": sheet_id,
197
+ "events": [{"event": "entity_deleted", "entity": sheet_id}],
198
+ }