Skip to content

Add declarative spatial rules and yaw-aware spatial geometry - #45

Merged
haphantran merged 4 commits into
mainfrom
feature/spatial-rules
Oct 13, 2026
Merged

haphantran merged 4 commits into
mainfrom
feature/spatial-rules

Conversation

@haphantran

@haphantran haphantran commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

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 in frontend/src/services/spatial/) models each placed element as its rotated footprint (widthMm x heightMm, turned by rotationZ, counter-clockwise from above, as in the 3D editor and OpenUSD) extruded over [z, z + depthMm]. The JS/OCL spatial helpers keep their names and signatures but now use a separating-axis overlap test and exact convex-polygon clearance; boundsOf/bounds3DOf return 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 spatialRules with five kinds over placed instances of a source and a target class (subclasses included), in plan or volume mode:

Kind Holds when Issues
noOverlap no two distinct elements intersect one per offending pair, on both elements
minClearance(d) gap between distinct elements >= d one per offending pair
maxDistance(d) each source has a target centre within d one per unsatisfied source
within(margin) each source inside a target footprint shrunk by margin one per unsatisfied source
adjacentTo(tol) each source has a distinct target with gap <= tol one per unsatisfied source
  • One pure evaluator (shared/spatial-rules.ts plus 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.
  • Pair issues carry the second element in a new optional ValidationIssue.relatedElementIds, so both elements are marked.
  • Persistence: new metamodels.spatialRules JSONB 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 the EPackage annotation spatialdsl/spatialRules (frontend Ecore export/import and the Sirius project export).
  • UI: a Spatial Rules section in the metamodel editor's Constraints tab with an add/edit dialog.
  • Example: the Smart Warehouse metamodel gains an abstract FixedEquipment supertype 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: new docs/user-guide/spatial-rules.md with 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

  • Backend: typecheck, lint, 696 tests pass. Frontend: typecheck, 387 tests pass.
  • Live on a local stack: the Smart Warehouse starter imports with its rules; moving a conveyor onto another gives one error in the validation panel and red markers on both conveyors in 2D; the headless pipeline reports the same single issue; a checkpoint restore brings back the rules; a rule added through the dialog persists.
  • The backend Docker image builds: the parity test loads the frontend mirror and the warehouse fixtures at run time, because the image compiles src/ (tests included) without frontend/.
  • Note: some backend route tests (routes/*.test.ts) fail intermittently under full-suite parallel load. This happens on main at 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.

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 '*'.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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.
@haphantran
haphantran merged commit be3b259 into main Oct 13, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants