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,123 @@
|
|
|
1
|
+
"""IP editor verbs: per-PDK registry mutations behind ProjectService.
|
|
2
|
+
|
|
3
|
+
First REFACTOR-SERVICE slice: the ip_register/ip_instantiate/
|
|
4
|
+
ip_promote verbs live here as _IpMixin so src/project/service.py
|
|
5
|
+
stays under the architecture line limit. ProjectService binds
|
|
6
|
+
this mixin, so the verbs keep their bound path
|
|
7
|
+
(rocket_engine.project.service.ProjectService.<verb>); the API
|
|
8
|
+
contract generator reads the contract docstrings from this
|
|
9
|
+
module (see scripts/api_spec.py _SERVICE_DOC_HOME).
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from ...ops import editors as _editors
|
|
13
|
+
|
|
14
|
+
from . import lifecycle as _ip
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class _IpMixin:
|
|
18
|
+
"""IP registry verbs (per-PDK invariant, RULING-01)."""
|
|
19
|
+
|
|
20
|
+
def ip_register(self, pdk, name, version, symbol, cell,
|
|
21
|
+
request_id, command_text, pin_map=None,
|
|
22
|
+
scope="pdk", reason="",
|
|
23
|
+
expected_revision=None):
|
|
24
|
+
"""Register one prebuilt IP block revision under its PDK.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
pdk: Owning PDK id (the per-PDK invariant key).
|
|
28
|
+
name: Block name within the PDK registry.
|
|
29
|
+
version: Revision label ("" forbidden).
|
|
30
|
+
symbol: Linked schematic symbol reference.
|
|
31
|
+
cell: Linked footprint cell reference.
|
|
32
|
+
request_id: Client envelope id for idempotent replay.
|
|
33
|
+
command_text: YAML command text for identity comparison.
|
|
34
|
+
pin_map: Explicit [{symPin, cellPin}] entries.
|
|
35
|
+
scope: "pdk" (default) or "shared" with a reason.
|
|
36
|
+
reason: Recorded justification for shared scope.
|
|
37
|
+
expected_revision: Revision the writer read.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Mutation mapping with receipt, events, and cursor.
|
|
41
|
+
|
|
42
|
+
Raises:
|
|
43
|
+
ProjectError: request_conflict, revision_conflict,
|
|
44
|
+
invalid_pdk, ip_exists, invalid_ip, invalid_scope.
|
|
45
|
+
"""
|
|
46
|
+
return _editors.editor_mutation(self,
|
|
47
|
+
"ip_register", request_id, command_text,
|
|
48
|
+
expected_revision,
|
|
49
|
+
lambda store, _rid, _rt, _live: _ip.ip_register(
|
|
50
|
+
store, self._actor, pdk, name, version, symbol,
|
|
51
|
+
cell, pin_map=pin_map or [], scope=scope,
|
|
52
|
+
reason=reason),
|
|
53
|
+
lambda receipt: f"registered IP {pdk}/{name}@{version}",
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def ip_instantiate(self, pdk, name, sheet, x, y,
|
|
57
|
+
request_id, command_text, version=None,
|
|
58
|
+
rotation=0, mirror=False, properties=None,
|
|
59
|
+
expected_revision=None):
|
|
60
|
+
"""Place one registered IP block with linkage intact.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
pdk: Consuming PDK id (must own the block unless the
|
|
64
|
+
block opted into shared scope).
|
|
65
|
+
name: Block name within the PDK registry.
|
|
66
|
+
sheet: Sheet entity id hosting the instance.
|
|
67
|
+
x: X position in schematic units.
|
|
68
|
+
y: Y position in schematic units.
|
|
69
|
+
request_id: Client envelope id for idempotent replay.
|
|
70
|
+
command_text: YAML command text for identity comparison.
|
|
71
|
+
version: Revision label, or None for the latest.
|
|
72
|
+
rotation: Rotation in degrees (0/90/180/270).
|
|
73
|
+
mirror: Mirror flag.
|
|
74
|
+
properties: Text-to-text instance properties.
|
|
75
|
+
expected_revision: Revision the writer read.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
Mutation mapping with receipt, events, and cursor.
|
|
79
|
+
|
|
80
|
+
Raises:
|
|
81
|
+
ProjectError: request_conflict, revision_conflict,
|
|
82
|
+
unknown_ip, pdk_mismatch, unknown_sheet,
|
|
83
|
+
invalid_instance.
|
|
84
|
+
"""
|
|
85
|
+
return _editors.editor_mutation(self,
|
|
86
|
+
"ip_instantiate", request_id, command_text,
|
|
87
|
+
expected_revision,
|
|
88
|
+
lambda store, _rid, _rt, _live: _ip.ip_instantiate(
|
|
89
|
+
store, self._actor, pdk, name, sheet, x, y,
|
|
90
|
+
version=version, rotation=rotation, mirror=mirror,
|
|
91
|
+
properties=properties),
|
|
92
|
+
lambda receipt: (
|
|
93
|
+
f"instantiated IP {pdk}/{name}"
|
|
94
|
+
f"@{receipt.get('version')}"),
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
def ip_promote(self, pdk, name, version,
|
|
98
|
+
request_id, command_text,
|
|
99
|
+
expected_revision=None):
|
|
100
|
+
"""Promote one block version as the registry's recommended one.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
pdk: Owning PDK id scoping the registry.
|
|
104
|
+
name: Block name within the PDK registry.
|
|
105
|
+
version: Revision label to promote.
|
|
106
|
+
request_id: Client envelope id for idempotent replay.
|
|
107
|
+
command_text: YAML command text for identity comparison.
|
|
108
|
+
expected_revision: Revision the writer read.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Mutation mapping with receipt, events, and cursor.
|
|
112
|
+
|
|
113
|
+
Raises:
|
|
114
|
+
ProjectError: request_conflict, revision_conflict,
|
|
115
|
+
unknown_ip, invalid_pdk.
|
|
116
|
+
"""
|
|
117
|
+
return _editors.editor_mutation(self,
|
|
118
|
+
"ip_promote", request_id, command_text,
|
|
119
|
+
expected_revision,
|
|
120
|
+
lambda store, _rid, _rt, _live: _ip.ip_promote(
|
|
121
|
+
store, self._actor, pdk, name, version),
|
|
122
|
+
lambda receipt: f"promoted IP {pdk}/{name}@{version}",
|
|
123
|
+
)
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Technology-profile bindings with explicit capability states.
|
|
2
|
+
|
|
3
|
+
Bindings map project layer identities to process purposes and
|
|
4
|
+
report each mapping as supported, missing, or incompatible. A
|
|
5
|
+
missing binding never reinterprets geometry: drawing and saving
|
|
6
|
+
keep working and only process operations report the gap.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import copy
|
|
10
|
+
|
|
11
|
+
STATES = ("supported", "missing", "incompatible")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def bind_layers(catalog, profile_layers, source="profile",
|
|
15
|
+
version="unknown"):
|
|
16
|
+
"""Bind catalog layers against a profile vocabulary.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
catalog: LayerCatalog to bind.
|
|
20
|
+
profile_layers: Mapping of profile name to layer-name
|
|
21
|
+
lists, or a plain list of known layer names.
|
|
22
|
+
source: Provenance label for the profile source.
|
|
23
|
+
version: Profile version pinned into the report.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
Binding report with profile pin, catalog revision,
|
|
27
|
+
and one entry per layer (state, purpose, source).
|
|
28
|
+
"""
|
|
29
|
+
if isinstance(profile_layers, dict):
|
|
30
|
+
profile = catalog.profile or {}
|
|
31
|
+
known = profile_layers.get(profile.get("name", ""), [])
|
|
32
|
+
else:
|
|
33
|
+
known = list(profile_layers)
|
|
34
|
+
entries = []
|
|
35
|
+
for layer in catalog.list_layers():
|
|
36
|
+
if layer["name"] in known:
|
|
37
|
+
state = "supported"
|
|
38
|
+
elif layer["name"].startswith("custom"):
|
|
39
|
+
state = "missing"
|
|
40
|
+
else:
|
|
41
|
+
state = "missing"
|
|
42
|
+
entries.append({
|
|
43
|
+
"capability": state,
|
|
44
|
+
"layer": layer["lid"],
|
|
45
|
+
"name": layer["name"],
|
|
46
|
+
"purpose": layer["purpose"],
|
|
47
|
+
"source": source,
|
|
48
|
+
"state": state,
|
|
49
|
+
"version": version,
|
|
50
|
+
})
|
|
51
|
+
profile = catalog.profile or {}
|
|
52
|
+
return {
|
|
53
|
+
"catalog_revision": catalog.revision,
|
|
54
|
+
"entries": entries,
|
|
55
|
+
"profile": profile.get("name", "unresolved"),
|
|
56
|
+
"profile_version": profile.get("version", version),
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def check_operation(report, layer_lid, operation):
|
|
61
|
+
"""Check whether one process operation may consume a layer.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
report: Binding report from bind_layers.
|
|
65
|
+
layer_lid: Stable layer id the operation needs.
|
|
66
|
+
operation: Process operation name (export_gds, drc,
|
|
67
|
+
extraction, or a recipe step).
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
Verdict mapping with state (supported, unsupported,
|
|
71
|
+
or incomplete), layer, operation, and reason. Draw
|
|
72
|
+
and save paths ignore this verdict; only the named
|
|
73
|
+
process operation is gated by it.
|
|
74
|
+
|
|
75
|
+
Raises:
|
|
76
|
+
ValueError: For a layer with no binding entry.
|
|
77
|
+
"""
|
|
78
|
+
for entry in report["entries"]:
|
|
79
|
+
if entry["layer"] == layer_lid:
|
|
80
|
+
if entry["state"] == "supported":
|
|
81
|
+
return {
|
|
82
|
+
"layer": layer_lid,
|
|
83
|
+
"operation": operation,
|
|
84
|
+
"reason": "Binding covers the operation.",
|
|
85
|
+
"state": "supported",
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
"layer": layer_lid,
|
|
89
|
+
"operation": operation,
|
|
90
|
+
"reason": (
|
|
91
|
+
f"No {operation} mapping for layer "
|
|
92
|
+
f"{entry['name']!r}; geometry is intact."
|
|
93
|
+
),
|
|
94
|
+
"state": "unsupported",
|
|
95
|
+
}
|
|
96
|
+
raise ValueError(f"No binding entry for layer: {layer_lid!r}.")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def binding_for(report, layer_lid):
|
|
100
|
+
"""Return one layer entry from a binding report.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
report: Binding report from bind_layers.
|
|
104
|
+
layer_lid: Stable layer id to look up.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Deep copy of the binding entry.
|
|
108
|
+
|
|
109
|
+
Raises:
|
|
110
|
+
ValueError: For a layer with no binding entry.
|
|
111
|
+
"""
|
|
112
|
+
for entry in report["entries"]:
|
|
113
|
+
if entry["layer"] == layer_lid:
|
|
114
|
+
return copy.deepcopy(entry)
|
|
115
|
+
raise ValueError(f"No binding entry for layer: {layer_lid!r}.")
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"""Editable project layer catalog with stable identities.
|
|
2
|
+
|
|
3
|
+
Layers carry identity (lid) separate from display (name, color,
|
|
4
|
+
visibility): renames, saves, and reloads keep the lid, so saved
|
|
5
|
+
geometry addressed by lid is never reinterpreted when a profile
|
|
6
|
+
is missing or changed.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import copy
|
|
10
|
+
import itertools
|
|
11
|
+
|
|
12
|
+
_COUNTER = itertools.count(1)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _mint_lid(existing):
|
|
16
|
+
"""Mint one stable layer id.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
existing: Lids already in use.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
Fresh lid string not present in existing.
|
|
23
|
+
"""
|
|
24
|
+
while True:
|
|
25
|
+
candidate = f"lyr-{next(_COUNTER):04d}"
|
|
26
|
+
if candidate not in existing:
|
|
27
|
+
return candidate
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class LayerCatalog:
|
|
31
|
+
"""Editable layer vocabulary for one project.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
profile: Resolved technology profile mapping (name,
|
|
35
|
+
version) or None when no profile is selected.
|
|
36
|
+
revision: Starting catalog revision for a fresh
|
|
37
|
+
catalog.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(self, profile=None, revision=1):
|
|
41
|
+
self._profile = copy.deepcopy(profile)
|
|
42
|
+
self._revision = revision
|
|
43
|
+
self._layers = {}
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def setup_visible(self):
|
|
47
|
+
"""Whether missing-profile setup must be shown.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
True when no technology profile is resolved.
|
|
51
|
+
"""
|
|
52
|
+
return self._profile is None
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def revision(self):
|
|
56
|
+
"""Current catalog revision.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Integer revision bumped by every mutation.
|
|
60
|
+
"""
|
|
61
|
+
return self._revision
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def profile(self):
|
|
65
|
+
"""Resolved technology profile, if any.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Deep copy of the profile mapping or None.
|
|
69
|
+
"""
|
|
70
|
+
return copy.deepcopy(self._profile)
|
|
71
|
+
|
|
72
|
+
def add_layer(self, name, purpose="drawing", color="#ffffff",
|
|
73
|
+
order=0, visible=True, aliases=()):
|
|
74
|
+
"""Add one project layer with a stable identity.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
name: Display name for the layer.
|
|
78
|
+
purpose: Process purpose label (display only;
|
|
79
|
+
semantics come from bindings).
|
|
80
|
+
color: Display color for editors.
|
|
81
|
+
order: Stack order for editors.
|
|
82
|
+
visible: Display visibility flag.
|
|
83
|
+
aliases: Extra names resolving to this layer.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Deep copy of the created layer record.
|
|
87
|
+
|
|
88
|
+
Raises:
|
|
89
|
+
ValueError: If the name is empty or already used.
|
|
90
|
+
"""
|
|
91
|
+
if not name:
|
|
92
|
+
raise ValueError("Layer name must not be empty.")
|
|
93
|
+
if any(layer["name"] == name for layer in
|
|
94
|
+
self._layers.values()):
|
|
95
|
+
raise ValueError(f"Duplicate layer name: {name!r}.")
|
|
96
|
+
lid = _mint_lid(set(self._layers))
|
|
97
|
+
self._layers[lid] = {
|
|
98
|
+
"aliases": list(aliases),
|
|
99
|
+
"color": color,
|
|
100
|
+
"lid": lid,
|
|
101
|
+
"name": name,
|
|
102
|
+
"order": order,
|
|
103
|
+
"purpose": purpose,
|
|
104
|
+
"visible": bool(visible),
|
|
105
|
+
}
|
|
106
|
+
self._revision += 1
|
|
107
|
+
return copy.deepcopy(self._layers[lid])
|
|
108
|
+
|
|
109
|
+
def rename_layer(self, lid, name):
|
|
110
|
+
"""Rename one layer without changing its identity.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
lid: Stable layer id to rename.
|
|
114
|
+
name: New display name.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
Deep copy of the renamed layer record.
|
|
118
|
+
|
|
119
|
+
Raises:
|
|
120
|
+
ValueError: For an unknown lid, an empty name, or
|
|
121
|
+
a name already in use.
|
|
122
|
+
"""
|
|
123
|
+
if lid not in self._layers:
|
|
124
|
+
raise ValueError(f"Unknown layer: {lid!r}.")
|
|
125
|
+
if not name:
|
|
126
|
+
raise ValueError("Layer name must not be empty.")
|
|
127
|
+
self._layers[lid]["name"] = name
|
|
128
|
+
self._revision += 1
|
|
129
|
+
return copy.deepcopy(self._layers[lid])
|
|
130
|
+
|
|
131
|
+
def set_display(self, lid, color=None, visible=None, order=None):
|
|
132
|
+
"""Change display-only flags of one layer.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
lid: Stable layer id to update.
|
|
136
|
+
color: New display color, if given.
|
|
137
|
+
visible: New visibility flag, if given.
|
|
138
|
+
order: New stack order, if given.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
Deep copy of the updated layer record.
|
|
142
|
+
|
|
143
|
+
Raises:
|
|
144
|
+
ValueError: For an unknown lid.
|
|
145
|
+
"""
|
|
146
|
+
if lid not in self._layers:
|
|
147
|
+
raise ValueError(f"Unknown layer: {lid!r}.")
|
|
148
|
+
layer = self._layers[lid]
|
|
149
|
+
if color is not None:
|
|
150
|
+
layer["color"] = color
|
|
151
|
+
if visible is not None:
|
|
152
|
+
layer["visible"] = bool(visible)
|
|
153
|
+
if order is not None:
|
|
154
|
+
layer["order"] = order
|
|
155
|
+
self._revision += 1
|
|
156
|
+
return copy.deepcopy(layer)
|
|
157
|
+
|
|
158
|
+
def get_layer(self, lid):
|
|
159
|
+
"""Return one layer record by stable id.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
lid: Stable layer id to read.
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
Deep copy of the layer record.
|
|
166
|
+
|
|
167
|
+
Raises:
|
|
168
|
+
ValueError: For an unknown lid.
|
|
169
|
+
"""
|
|
170
|
+
if lid not in self._layers:
|
|
171
|
+
raise ValueError(f"Unknown layer: {lid!r}.")
|
|
172
|
+
return copy.deepcopy(self._layers[lid])
|
|
173
|
+
|
|
174
|
+
def list_layers(self):
|
|
175
|
+
"""List every layer ordered by stack order.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
List of deep-copied layer records.
|
|
179
|
+
"""
|
|
180
|
+
return sorted(
|
|
181
|
+
(copy.deepcopy(layer) for layer in
|
|
182
|
+
self._layers.values()),
|
|
183
|
+
key=lambda layer: (layer["order"], layer["lid"]),
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
def attach_profile(self, profile):
|
|
187
|
+
"""Resolve a technology profile without moving geometry.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
profile: Technology profile mapping with at least
|
|
191
|
+
name and version.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
Deep copy of the attached profile.
|
|
195
|
+
"""
|
|
196
|
+
self._profile = copy.deepcopy(profile)
|
|
197
|
+
self._revision += 1
|
|
198
|
+
return copy.deepcopy(self._profile)
|
|
199
|
+
|
|
200
|
+
def to_dict(self):
|
|
201
|
+
"""Serialize the catalog including its revision pin.
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
Plain mapping with profile, revision, and layers.
|
|
205
|
+
"""
|
|
206
|
+
return {
|
|
207
|
+
"layers": self.list_layers(),
|
|
208
|
+
"profile": copy.deepcopy(self._profile),
|
|
209
|
+
"revision": self._revision,
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
@classmethod
|
|
213
|
+
def from_dict(cls, data):
|
|
214
|
+
"""Rebuild a catalog from serialized state.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
data: Mapping produced by to_dict.
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
LayerCatalog with stable lids preserved.
|
|
221
|
+
"""
|
|
222
|
+
catalog = cls(
|
|
223
|
+
profile=data.get("profile"),
|
|
224
|
+
revision=data.get("revision", 1),
|
|
225
|
+
)
|
|
226
|
+
for layer in data.get("layers", ()):
|
|
227
|
+
catalog._layers[layer["lid"]] = copy.deepcopy(layer)
|
|
228
|
+
return catalog
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Built-in layer visibility presets (viewer aids, not semantics)."""
|
|
2
|
+
|
|
3
|
+
BUILTIN_PRESETS = ("All", "None", "All-metal", "Top", "Bottom")
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def builtin_presets():
|
|
7
|
+
"""List every built-in preset name.
|
|
8
|
+
|
|
9
|
+
Returns:
|
|
10
|
+
Tuple of preset names.
|
|
11
|
+
"""
|
|
12
|
+
return BUILTIN_PRESETS
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def apply_preset(name, layer_names):
|
|
16
|
+
"""Apply one built-in preset to a layer name list.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
name: Preset name from builtin_presets.
|
|
20
|
+
layer_names: Available layer names to filter.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Mapping with preset name and enabled layer names.
|
|
24
|
+
|
|
25
|
+
Raises:
|
|
26
|
+
ValueError: For an unknown preset name.
|
|
27
|
+
"""
|
|
28
|
+
if name not in BUILTIN_PRESETS:
|
|
29
|
+
raise ValueError(f"Unknown preset: {name!r}.")
|
|
30
|
+
names = list(layer_names)
|
|
31
|
+
if name == "All":
|
|
32
|
+
enabled = names
|
|
33
|
+
elif name == "None":
|
|
34
|
+
enabled = []
|
|
35
|
+
elif name == "All-metal":
|
|
36
|
+
enabled = [layer for layer in names
|
|
37
|
+
if "met" in layer.lower() or "metal" in
|
|
38
|
+
layer.lower()]
|
|
39
|
+
elif name == "Top":
|
|
40
|
+
enabled = names[-1:] if names else []
|
|
41
|
+
else:
|
|
42
|
+
enabled = names[:1] if names else []
|
|
43
|
+
return {"enabled": enabled, "preset": name}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Project ledger internals behind ProjectService."""
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""Document path resolution and byte access."""
|
|
2
|
+
|
|
3
|
+
from ...interfaces.paths import join_contained
|
|
4
|
+
from ..errors import ProjectError
|
|
5
|
+
|
|
6
|
+
from .shared import _RESERVED_DOCS, _SNAPSHOTS_DIRNAME, _atomic_write_text
|
|
7
|
+
|
|
8
|
+
class _DocMixin:
|
|
9
|
+
"""Document byte access for ProjectService."""
|
|
10
|
+
|
|
11
|
+
def _doc_path(self, canonical):
|
|
12
|
+
"""Resolve one canonical path under the project dir.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
canonical: Canonical document path.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
Resolved absolute path inside the project dir.
|
|
19
|
+
|
|
20
|
+
Raises:
|
|
21
|
+
ProjectError: path_escape when containment fails.
|
|
22
|
+
"""
|
|
23
|
+
try:
|
|
24
|
+
return join_contained(self._dir, canonical)
|
|
25
|
+
except Exception as exc:
|
|
26
|
+
code = getattr(exc, "code", None) or "path_escape"
|
|
27
|
+
raise ProjectError(code, str(exc)) from exc
|
|
28
|
+
|
|
29
|
+
def _read_doc_text(self, canonical, original):
|
|
30
|
+
"""Read one document as text, or None when missing.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
canonical: Canonical document path.
|
|
34
|
+
original: Caller-supplied path for error messages.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Document text, or None when the document is absent.
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
ProjectError: invalid_command when the path names a
|
|
41
|
+
directory; invalid_document when durable bytes are
|
|
42
|
+
not UTF-8 text.
|
|
43
|
+
"""
|
|
44
|
+
if self._dir is None:
|
|
45
|
+
return self._docs.get(canonical)
|
|
46
|
+
target = self._doc_path(canonical)
|
|
47
|
+
if not target.exists():
|
|
48
|
+
return None
|
|
49
|
+
if target.is_dir():
|
|
50
|
+
raise ProjectError(
|
|
51
|
+
"invalid_command",
|
|
52
|
+
f"Document path is a directory: {original!r}.",
|
|
53
|
+
)
|
|
54
|
+
try:
|
|
55
|
+
return target.read_text(encoding="utf-8")
|
|
56
|
+
except UnicodeDecodeError as exc:
|
|
57
|
+
raise ProjectError(
|
|
58
|
+
"invalid_document",
|
|
59
|
+
f"Document is not UTF-8 text: {original!r} ({exc}).",
|
|
60
|
+
) from exc
|
|
61
|
+
except OSError as exc:
|
|
62
|
+
raise ProjectError(
|
|
63
|
+
"invalid_ledger",
|
|
64
|
+
f"Cannot read document {original!r}: {exc}.",
|
|
65
|
+
) from exc
|
|
66
|
+
|
|
67
|
+
def _write_doc_text(self, canonical, content, original):
|
|
68
|
+
"""Write one document (parents created, atomic rename).
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
canonical: Canonical document path.
|
|
72
|
+
content: UTF-8 text to store.
|
|
73
|
+
original: Caller-supplied path for error messages.
|
|
74
|
+
|
|
75
|
+
Raises:
|
|
76
|
+
ProjectError: invalid_command when the path names a
|
|
77
|
+
directory; invalid_ledger when the durable write
|
|
78
|
+
fails.
|
|
79
|
+
"""
|
|
80
|
+
if self._dir is None:
|
|
81
|
+
self._docs[canonical] = content
|
|
82
|
+
return
|
|
83
|
+
target = self._doc_path(canonical)
|
|
84
|
+
if target.is_dir():
|
|
85
|
+
raise ProjectError(
|
|
86
|
+
"invalid_command",
|
|
87
|
+
f"Document path is a directory: {original!r}.",
|
|
88
|
+
)
|
|
89
|
+
try:
|
|
90
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
91
|
+
except OSError as exc:
|
|
92
|
+
raise ProjectError(
|
|
93
|
+
"invalid_ledger",
|
|
94
|
+
f"Cannot create document dir for {original!r}: {exc}.",
|
|
95
|
+
) from exc
|
|
96
|
+
try:
|
|
97
|
+
_atomic_write_text(target, content)
|
|
98
|
+
except OSError as exc:
|
|
99
|
+
raise ProjectError(
|
|
100
|
+
"invalid_ledger",
|
|
101
|
+
f"Cannot write document {original!r}: {exc}.",
|
|
102
|
+
) from exc
|
|
103
|
+
|
|
104
|
+
def _delete_doc_path(self, canonical, original):
|
|
105
|
+
"""Delete one existing document file.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
canonical: Canonical document path.
|
|
109
|
+
original: Caller-supplied path for error messages.
|
|
110
|
+
|
|
111
|
+
Raises:
|
|
112
|
+
ProjectError: invalid_command when the path names a
|
|
113
|
+
directory; invalid_ledger when the delete fails.
|
|
114
|
+
"""
|
|
115
|
+
if self._dir is None:
|
|
116
|
+
self._docs.pop(canonical, None)
|
|
117
|
+
return
|
|
118
|
+
target = self._doc_path(canonical)
|
|
119
|
+
if target.is_dir():
|
|
120
|
+
raise ProjectError(
|
|
121
|
+
"invalid_command",
|
|
122
|
+
f"Document path is a directory: {original!r}.",
|
|
123
|
+
)
|
|
124
|
+
try:
|
|
125
|
+
target.unlink()
|
|
126
|
+
except FileNotFoundError:
|
|
127
|
+
pass
|
|
128
|
+
except OSError as exc:
|
|
129
|
+
raise ProjectError(
|
|
130
|
+
"invalid_ledger",
|
|
131
|
+
f"Cannot delete document {original!r}: {exc}.",
|
|
132
|
+
) from exc
|
|
133
|
+
|
|
134
|
+
def _iter_doc_files(self):
|
|
135
|
+
"""Yield (canonical path, byte size) for every document.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Sorted (path, bytes) pairs, engine-owned entries
|
|
139
|
+
excluded.
|
|
140
|
+
"""
|
|
141
|
+
if self._dir is None:
|
|
142
|
+
return sorted(
|
|
143
|
+
(path, len(text.encode("utf-8")))
|
|
144
|
+
for path, text in self._docs.items()
|
|
145
|
+
)
|
|
146
|
+
if not self._dir.is_dir():
|
|
147
|
+
return []
|
|
148
|
+
found = []
|
|
149
|
+
for target in sorted(self._dir.rglob("*")):
|
|
150
|
+
if not target.is_file():
|
|
151
|
+
continue
|
|
152
|
+
try:
|
|
153
|
+
rel = target.relative_to(self._dir).as_posix()
|
|
154
|
+
except ValueError:
|
|
155
|
+
continue
|
|
156
|
+
if (
|
|
157
|
+
rel in _RESERVED_DOCS
|
|
158
|
+
or rel.startswith("runs/")
|
|
159
|
+
or rel.startswith(_SNAPSHOTS_DIRNAME + "/")
|
|
160
|
+
):
|
|
161
|
+
continue
|
|
162
|
+
try:
|
|
163
|
+
size = target.stat().st_size
|
|
164
|
+
except OSError:
|
|
165
|
+
continue
|
|
166
|
+
found.append((rel, size))
|
|
167
|
+
return sorted(found)
|