Repository navigation
Add declarative spatial rules and yaw-aware spatial geometry - #45
Merged
Merged
Conversation
Spatial geometry now honours rotationZ. A shared oriented-box module (shared/spatial-geometry.ts, mirrored in frontend/src/services/spatial) builds each placed element's rotated footprint extruded over its vertical interval. The JS/OCL spatial helpers keep their names and signatures but use separating-axis overlap tests and exact convex-polygon clearance, so rotated elements no longer get axis-aligned answers. Metamodels gain spatialRules: noOverlap, minClearance, maxDistance, within, and adjacentTo over placed instances of a source and a target metaclass (subclasses included), in plan or volume mode. A pure evaluator (shared/spatial-rules.ts plus the frontend mirror) runs in browser validation, which marks both elements of an offending pair in 2D and 3D, and in the backend validation engine used by the headless pipeline. A parity test keeps the two copies identical and checks that they report the same issues. Rules are persisted in a new metamodels.spatialRules column, validated on create and update, carried through checkpoints and metamodel evolution (a rule on a removed class blocks the evolution), removed with a deleted class, and exported to Ecore as the EPackage annotation spatialdsl/spatialRules. The metamodel editor gains a Spatial Rules section with an add/edit dialog. The Smart Warehouse example declares a FixedEquipment supertype and four rules that its bundled model satisfies, and its unbounded controller references now use '*'.
This was referenced Oct 12, 2026
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Partial updates, evolution, and Ecore imports can still introduce invalid or dangling spatial rules.
3 open findings
What changed in this PR
Adds yaw-aware spatial geometry and declarative spatial rules across validation, persistence, editing, interoperability, and documentation.
Changes:
- Implements rotated geometry and five spatial-rule kinds.
- Integrates rules into frontend/backend validation and metamodel lifecycle.
- Adds editor UI, examples, documentation, and tests.
| File | Description |
|---|---|
shared/types/index.ts |
Defines shared spatial-rule types. |
shared/spatial-rules.ts |
Implements shared rule validation and evaluation. |
shared/spatial-geometry.ts |
Implements yaw-aware geometry. |
README.md |
Advertises spatial rules. |
frontend/src/services/spatial/spatial-rules.ts |
Mirrors the shared evaluator. |
frontend/src/services/spatial/spatial-geometry.ts |
Mirrors shared geometry. |
frontend/src/services/spatial/index.ts |
Exports spatial modules. |
frontend/src/services/model/model-validation.service.ts |
Runs spatial-rule validation. |
frontend/src/services/metamodel/metamodel.service.ts |
Validates imported rules. |
frontend/src/services/metamodel/metamodel-api-sync.service.ts |
Synchronizes rules with the API. |
frontend/src/services/metamodel/metaclass-crud.service.ts |
Removes rules for deleted classes. |
frontend/src/services/metamodel/ecore.service.ts |
Imports and exports rule annotations. |
frontend/src/services/diagram/view-validation.service.ts |
Marks all related elements. |
frontend/src/services/constraint/spatial.helpers.ts |
Makes constraint geometry yaw-aware. |
frontend/src/models/types.ts |
Adds frontend rule types. |
frontend/src/examples/data/smart-warehouse-metamodel.json |
Adds example rules and hierarchy. |
frontend/src/components/metamodel/SpatialRulesEditor.tsx |
Adds rule editing UI. |
frontend/src/components/metamodel/components/sidebar/MetamodelSidebar.tsx |
Embeds the rule editor. |
frontend/src/__tests__/services/view-validation.test.ts |
Tests related-element markers. |
frontend/src/__tests__/services/spatial-rules.test.ts |
Tests rule semantics. |
frontend/src/__tests__/services/spatial-helpers.test.ts |
Tests rotated geometry helpers. |
frontend/src/__tests__/services/ecore-xmi-interop.test.ts |
Tests Ecore rule round-trips. |
frontend/src/__tests__/components/SpatialRulesEditor.test.tsx |
Tests rule editor behavior. |
docs/user-guide/spatial-rules.md |
Documents spatial rules. |
docs/user-guide/metamodels.md |
Links metamodel guidance. |
docs/reference/data-model.md |
Documents persisted rule data. |
docs/reference/api.md |
Documents API rule fields. |
docs/README.md |
Adds documentation navigation. |
backend/src/services/sirius-interop.service.ts |
Exports rules to Sirius Ecore. |
backend/src/services/pipeline/model-validation.engine.ts |
Runs rules in headless validation. |
backend/src/services/pipeline/headless-pipeline.service.ts |
Maps persisted rules. |
backend/src/services/metamodel.service.ts |
Persists and validates rules. |
backend/src/services/lifecycle/project-checkpoint.service.ts |
Checkpoints spatial rules. |
backend/src/services/lifecycle/metamodel-evolution.service.ts |
Handles rules during evolution. |
backend/src/__tests__/services/spatial-rules-parity.test.ts |
Tests frontend/backend parity. |
backend/src/__tests__/services/sirius-interop.service.test.ts |
Tests Sirius annotation export. |
backend/src/__tests__/services/sharing.service.test.ts |
Updates metamodel fixtures. |
backend/src/__tests__/services/project-checkpoint.service.test.ts |
Tests checkpoint round-trips. |
backend/src/__tests__/services/pipeline-engines.test.ts |
Tests headless rule evaluation. |
backend/src/__tests__/services/metamodel.service.test.ts |
Tests rule persistence and validation. |
backend/src/__tests__/services/metamodel-evolution.service.test.ts |
Tests evolution blockers and hashes. |
backend/prisma/schema.prisma |
Adds the spatial-rules column. |
backend/prisma/migrations/20261011000000_add_metamodel_spatial_rules/migration.sql |
Migrates the database schema. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+378
to
+385
| asArray<SpatialRule>(next.spatialRules).forEach(rule => { | ||
| [rule?.sourceClassId, rule?.targetClassId].forEach(classId => { | ||
| if (typeof classId !== 'string' || nextClassNames.has(classId)) return; | ||
| const removedName = currentClassNames.get(classId); | ||
| blockers.push(removedName | ||
| ? `Spatial rule ${rule.name} references removed class ${removedName}; update or delete the rule before applying this evolution` | ||
| : `Spatial rule ${rule?.name || rule?.id} references unknown class ${classId}`); | ||
| }); |
Comment on lines
213
to
+221
| if (data.classes !== undefined) { | ||
| this.assertValidClassesVerticalPlacement(data.classes); | ||
| } | ||
| if (data.spatialRules !== undefined) { | ||
| const classes = data.classes !== undefined | ||
| ? data.classes | ||
| : (await prisma.metamodel.findUnique({ where: { id }, select: { classes: true } }))?.classes; | ||
| this.assertValidSpatialRules(data.spatialRules, classes); | ||
| } |
Comment on lines
+154
to
+165
| rules.push({ | ||
| id: uuidv4(), | ||
| name, | ||
| kind: parsed.kind, | ||
| sourceClassId, | ||
| targetClassId, | ||
| ...(parsed.distanceMm !== undefined && { distanceMm: parsed.distanceMm }), | ||
| ...(parsed.mode !== undefined && { mode: parsed.mode }), | ||
| severity: parsed.severity || 'error', | ||
| ...(parsed.message !== undefined && { message: parsed.message }), | ||
| ...(parsed.enabled !== undefined && { enabled: parsed.enabled }), | ||
| }); |
The warehouse example said a violation could be provoked in the 2D view, but 2D moves only change the 3D pose that rules check when an element's 2D and 3D plan positions coincide, which is not the case for the bundled examples. Say so, and point to the 3D view and its Position & Rotation fields instead.
The backend Dockerfile compiles src/, tests included, with only backend/ and shared/ in the image. The spatial rules parity test imported the frontend mirror and the warehouse fixtures statically, so the image build failed. Load those files at run time instead, as the Sirius fixture test does.
Metamodel evolution now runs the full rule validation (kind, distance, mode, severity, duplicate IDs) before a preview can be applied, a classes-only update checks the stored rules against the new classes, and Ecore import skips annotation rules that are not valid rules with a warning instead of importing them.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
Spatial rules become part of the language definition, and the spatial geometry they build on now honours rotation.
Yaw-aware geometry (T1).
shared/spatial-geometry.ts(mirrored infrontend/src/services/spatial/) models each placed element as its rotated footprint (widthMm x heightMm, turned byrotationZ, counter-clockwise from above, as in the 3D editor and OpenUSD) extruded over[z, z + depthMm]. The JS/OCLspatialhelpers keep their names and signatures but now use a separating-axis overlap test and exact convex-polygon clearance;boundsOf/bounds3DOfreturn the envelope of the rotated footprint. Example: two 2000 x 500 mm conveyors where one is turned 90 degrees were judged with axis-aligned boxes before; now they are not.Declarative spatial rules (T2). Metamodels gain
spatialRuleswith five kinds over placed instances of a source and a target class (subclasses included), inplanorvolumemode:noOverlapminClearance(d)maxDistance(d)within(margin)adjacentTo(tol)shared/spatial-rules.tsplus the frontend mirror) runs in browser validation (2D and 3D markers, view validation panel) and in the backend validation engine used by the headless pipeline. A parity test keeps both copies byte-identical below their imports and checks identical issues on seeded random models.ValidationIssue.relatedElementIds, so both elements are marked.metamodels.spatialRulesJSONB column (migration), validated on create/update, kept in checkpoints (omitted when empty so existing checkpoint hashes still verify), handled by metamodel evolution (a rule on a removed class is a blocker), removed with a deleted class, and exported to Ecore as theEPackageannotationspatialdsl/spatialRules(frontend Ecore export/import and the Sirius project export).FixedEquipmentsupertype and four rules that the bundled model satisfies. Its two unbounded controller references now use'*'instead of-1, which both validators had reported as cardinality errors.docs/user-guide/spatial-rules.mdwith the exact semantics, linked from the docs hub, metamodel guide, data model, and API reference. It also states that rules read the 3D pose: the bundled examples draw schematic 2D layouts, so violations are provoked in the 3D view or with the Position & Rotation fields.Verification
src/(tests included) withoutfrontend/.routes/*.test.ts) fail intermittently under full-suite parallel load. This happens onmainat the same rate (2 of 6 local runs) and is unrelated to this change.Follow-ups in other PRs
The evaluation scripts (#49), the Smart Home example (#50), and the documentation PR (#48) build on this branch.