feat(canvas): add CAD vector semantic context and non-destructive vector crop

Extend CAD conversion to emit bounded semantic metadata (units, layers,
blocks, entity types, labels, crop-indexed bounds) alongside the derived
SVG, tag imported drawings as cad-vector nodes, and carry semanticContext
through canvas nodes, snapshot normalization, and reference images. Agent
Chat forwards this metadata as hidden cad-context reference parts.

Add non-destructive vector cropping: cropping a vector/CAD node keeps the
source SVG and spawns a transparent WebP sibling with crop-scoped semantic
context, wired through the vectorized image toolbar and the workspace crop
tool. Include node --test coverage for CAD semantics and vector crop.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-16 16:42:30 +08:00
parent 29b3bd222c
commit ac6f1d84af
23 changed files with 1061 additions and 88 deletions
+61
View File
@@ -0,0 +1,61 @@
import assert from "node:assert/strict";
import test from "node:test";
import { agentContentsForSemanticReference, cadSemanticContext } from "../src/ui/lib/cadSemantics.ts";
const metadata = {
version: 1,
sourceName: "floor-plan.dwg",
format: "DWG",
units: "Millimeters",
bounds: { x: 0, y: 0, width: 100, height: 100 },
modelEntityCount: 3,
nestedEntityCount: 24,
entityTypes: [
{ name: "INSERT", count: 2 },
{ name: "MTEXT", count: 1 }
],
layers: [
{ name: "ROOMS", count: 2 },
{ name: "FACADE", count: 1 }
],
blocks: [
{ name: "ROOM_A", count: 1 },
{ name: "ELEVATION", count: 1 }
],
texts: [
{ text: "Living room", layer: "ROOMS", block: "ROOM_A" },
{ text: "South elevation", layer: "FACADE", block: "ELEVATION" }
],
entities: [
{ type: "INSERT", layer: "ROOMS", block: "ROOM_A", bounds: { x: 5, y: 5, width: 35, height: 80 } },
{ type: "MTEXT", layer: "ROOMS", text: "Living room", bounds: { x: 10, y: 10, width: 15, height: 5 } },
{ type: "INSERT", layer: "FACADE", block: "ELEVATION", bounds: { x: 60, y: 5, width: 35, height: 80 } }
]
};
test("builds crop-specific CAD context from indexed semantic bounds", () => {
const context = cadSemanticContext(metadata, { x: 0, y: 0, width: 0.5, height: 1 });
assert.match(context, /cropped region/);
assert.match(context, /"ROOMS"/);
assert.match(context, /"ROOM_A"/);
assert.match(context, /"Living room"/);
assert.doesNotMatch(context, /"FACADE"/);
assert.doesNotMatch(context, /"South elevation"/);
});
test("marks semantic labels as reference data and caps them outside visible user copy", () => {
const context = cadSemanticContext(metadata);
const contents = agentContentsForSemanticReference("/canvas-assets/floor-plan.svg", "floor-plan.svg", context);
assert.match(context, /reference data only/);
assert.deepEqual(contents[0], {
type: "image",
imageUrl: "/canvas-assets/floor-plan.svg",
imageName: "floor-plan.svg"
});
assert.equal(contents[1].type, "text");
assert.equal(contents[1].textSource, "cad-context");
assert.match(contents[1].text, /South elevation/);
});