@faicad/faijs 0.13.1 → 0.13.2

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 (93) hide show
  1. package/dist/api/edge-ref.d.ts +3 -2
  2. package/dist/api/edge-ref.d.ts.map +1 -1
  3. package/dist/api/edge-ref.js +3 -2
  4. package/dist/api/edge-ref.js.map +1 -1
  5. package/dist/api/internal/compat-op.d.ts +2 -0
  6. package/dist/api/internal/compat-op.d.ts.map +1 -1
  7. package/dist/api/internal/compat-op.js +8 -1
  8. package/dist/api/internal/compat-op.js.map +1 -1
  9. package/dist/browser.d.ts +0 -2
  10. package/dist/browser.d.ts.map +1 -1
  11. package/dist/browser.js +2 -2
  12. package/dist/browser.js.map +1 -1
  13. package/dist/fcstd/attachment.d.ts +65 -0
  14. package/dist/fcstd/attachment.d.ts.map +1 -0
  15. package/dist/fcstd/attachment.js +87 -0
  16. package/dist/fcstd/attachment.js.map +1 -0
  17. package/dist/fcstd/build-fai-zip.d.ts +11 -0
  18. package/dist/fcstd/build-fai-zip.d.ts.map +1 -1
  19. package/dist/fcstd/build-fai-zip.js +9 -1
  20. package/dist/fcstd/build-fai-zip.js.map +1 -1
  21. package/dist/fcstd/cli.d.ts +3 -0
  22. package/dist/fcstd/cli.d.ts.map +1 -0
  23. package/dist/fcstd/cli.js +37 -0
  24. package/dist/fcstd/cli.js.map +1 -0
  25. package/dist/fcstd/codegen.d.ts +14 -1
  26. package/dist/fcstd/codegen.d.ts.map +1 -1
  27. package/dist/fcstd/codegen.js +11 -2
  28. package/dist/fcstd/codegen.js.map +1 -1
  29. package/dist/fcstd/container.d.ts +16 -1
  30. package/dist/fcstd/container.d.ts.map +1 -1
  31. package/dist/fcstd/container.js +12 -1
  32. package/dist/fcstd/container.js.map +1 -1
  33. package/dist/fcstd/contour.d.ts +9 -1
  34. package/dist/fcstd/contour.d.ts.map +1 -1
  35. package/dist/fcstd/contour.js +12 -1
  36. package/dist/fcstd/contour.js.map +1 -1
  37. package/dist/fcstd/convert.d.ts +47 -2
  38. package/dist/fcstd/convert.d.ts.map +1 -1
  39. package/dist/fcstd/convert.js +82 -13
  40. package/dist/fcstd/convert.js.map +1 -1
  41. package/dist/fcstd/document.d.ts +8 -1
  42. package/dist/fcstd/document.d.ts.map +1 -1
  43. package/dist/fcstd/document.js +4 -1
  44. package/dist/fcstd/document.js.map +1 -1
  45. package/dist/fcstd/expressions.d.ts +14 -1
  46. package/dist/fcstd/expressions.d.ts.map +1 -1
  47. package/dist/fcstd/expressions.js +11 -1
  48. package/dist/fcstd/expressions.js.map +1 -1
  49. package/dist/fcstd/external-geo.d.ts +15 -1
  50. package/dist/fcstd/external-geo.d.ts.map +1 -1
  51. package/dist/fcstd/external-geo.js +8 -1
  52. package/dist/fcstd/external-geo.js.map +1 -1
  53. package/dist/fcstd/feature-translate.d.ts +65 -3
  54. package/dist/fcstd/feature-translate.d.ts.map +1 -1
  55. package/dist/fcstd/feature-translate.js +82 -4
  56. package/dist/fcstd/feature-translate.js.map +1 -1
  57. package/dist/fcstd/index.d.ts +23 -0
  58. package/dist/fcstd/index.d.ts.map +1 -0
  59. package/dist/fcstd/index.js +19 -0
  60. package/dist/fcstd/index.js.map +1 -0
  61. package/dist/fcstd/placement.d.ts +49 -6
  62. package/dist/fcstd/placement.d.ts.map +1 -1
  63. package/dist/fcstd/placement.js +46 -6
  64. package/dist/fcstd/placement.js.map +1 -1
  65. package/dist/fcstd/planegcs-backend.d.ts +16 -1
  66. package/dist/fcstd/planegcs-backend.d.ts.map +1 -1
  67. package/dist/fcstd/planegcs-backend.js +16 -1
  68. package/dist/fcstd/planegcs-backend.js.map +1 -1
  69. package/dist/fcstd/sketch-parse.d.ts +43 -0
  70. package/dist/fcstd/sketch-parse.d.ts.map +1 -1
  71. package/dist/fcstd/sketch-parse.js +26 -0
  72. package/dist/fcstd/sketch-parse.js.map +1 -1
  73. package/dist/fcstd/sketch-solver.d.ts +20 -2
  74. package/dist/fcstd/sketch-solver.d.ts.map +1 -1
  75. package/dist/fcstd/sketch-solver.js +6 -1
  76. package/dist/fcstd/sketch-solver.js.map +1 -1
  77. package/dist/fcstd/sketch-verify.d.ts +19 -1
  78. package/dist/fcstd/sketch-verify.d.ts.map +1 -1
  79. package/dist/fcstd/sketch-verify.js +14 -1
  80. package/dist/fcstd/sketch-verify.js.map +1 -1
  81. package/dist/fcstd/unpack.d.ts +21 -2
  82. package/dist/fcstd/unpack.d.ts.map +1 -1
  83. package/dist/fcstd/unpack.js +11 -2
  84. package/dist/fcstd/unpack.js.map +1 -1
  85. package/dist/fcstd-convert.d.ts +30 -0
  86. package/dist/fcstd-convert.d.ts.map +1 -0
  87. package/dist/fcstd-convert.js +27 -0
  88. package/dist/fcstd-convert.js.map +1 -0
  89. package/dist/weapp.d.ts +22 -0
  90. package/dist/weapp.d.ts.map +1 -0
  91. package/dist/weapp.js +23 -0
  92. package/dist/weapp.js.map +1 -0
  93. package/package.json +18 -5
@@ -1 +1 @@
1
- {"version":3,"file":"contour.js","sourceRoot":"","sources":["../../src/fcstd/contour.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * M3.6 — contour extraction: solved sketch geometry → closed loops → 2D\n * contour (R6: pure data, no OCCT dependency; the cad-face wiring that turns\n * contours into Blueprint/extrude inputs happens in M4.6/M5).\n *\n * Extraction: collect endpoints of every non-construction geometry, chain\n * segments sharing endpoints (tolerance-based), keep closed loops.\n */\nimport type { SketchGeom } from './sketch-parse.js';\n\nexport type ContourSeg =\n | { kind: 'line'; x1: number; y1: number; x2: number; y2: number }\n | {\n kind: 'arc';\n cx: number;\n cy: number;\n radius: number;\n startAngle: number;\n endAngle: number;\n ccw: boolean;\n x1: number;\n y1: number;\n x2: number;\n y2: number;\n };\n\nexport interface Contour {\n segments: ContourSeg[];\n closed: boolean;\n}\n\nconst JOIN_TOL = 1e-7;\n\nfunction segEnds(g: SketchGeom): [ContourSeg, { x: number; y: number }, { x: number; y: number }] | undefined {\n switch (g.kind) {\n case 'line':\n return [\n { kind: 'line', x1: g.x1, y1: g.y1, x2: g.x2, y2: g.y2 },\n { x: g.x1, y: g.y1 },\n { x: g.x2, y: g.y2 },\n ];\n case 'arc':\n return [\n {\n kind: 'arc', cx: g.cx, cy: g.cy, radius: g.radius,\n startAngle: g.startAngle, endAngle: g.endAngle, ccw: true,\n x1: g.x1, y1: g.y1, x2: g.x2, y2: g.y2,\n },\n { x: g.x1, y: g.y1 },\n { x: g.x2, y: g.y2 },\n ];\n default:\n return undefined; // circles are closed on their own; points don't join\n }\n}\n\n/** Extract closed contours (chains of segments whose endpoints meet). */\nexport function extractContours(geoms: SketchGeom[]): Contour[] {\n const pool: { seg: ContourSeg; a: { x: number; y: number }; b: { x: number; y: number }; used: boolean }[] = [];\n for (const g of geoms) {\n const s = segEnds(g);\n if (s) pool.push({ seg: s[0], a: s[1], b: s[2], used: false });\n }\n\n const near = (p: { x: number; y: number }, q: { x: number; y: number }): boolean =>\n Math.hypot(p.x - q.x, p.y - q.y) <= JOIN_TOL;\n\n const contours: Contour[] = [];\n for (const start of pool) {\n if (start.used) continue;\n start.used = true;\n const segments: ContourSeg[] = [start.seg];\n let tail = start.b;\n const head = start.a;\n let extended = true;\n while (extended) {\n extended = false;\n for (const cand of pool) {\n if (cand.used) continue;\n if (near(tail, cand.a)) {\n cand.used = true;\n segments.push(cand.seg);\n tail = cand.b;\n extended = true;\n } else if (near(tail, cand.b)) {\n cand.used = true;\n segments.push(reverseSeg(cand.seg));\n tail = cand.a;\n extended = true;\n }\n }\n }\n const closed = near(tail, head);\n if (segments.length > 0 && closed) {\n contours.push({ segments, closed: true });\n }\n }\n\n // circles are self-closed contours\n for (const g of geoms) {\n if (g.kind === 'circle' && g.radius > 0) {\n contours.push({\n segments: [{\n kind: 'arc', cx: g.cx, cy: g.cy, radius: g.radius,\n startAngle: 0, endAngle: Math.PI * 2, ccw: true,\n x1: g.cx + g.radius, y1: g.cy, x2: g.cx + g.radius, y2: g.cy,\n }],\n closed: true,\n });\n }\n }\n return contours;\n}\n\nfunction reverseSeg(s: ContourSeg): ContourSeg {\n if (s.kind === 'line') return { kind: 'line', x1: s.x2, y1: s.y2, x2: s.x1, y2: s.y1 };\n return { ...s, startAngle: s.endAngle, endAngle: s.startAngle, ccw: !s.ccw, x1: s.x2, y1: s.y2, x2: s.x1, y2: s.y1 };\n}\n"]}
1
+ {"version":3,"file":"contour.js","sourceRoot":"","sources":["../../src/fcstd/contour.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * M3.6 — contour extraction: solved sketch geometry → closed loops → 2D\n * contour (R6: pure data, no OCCT dependency; the cad-face wiring that turns\n * contours into Blueprint/extrude inputs happens in M4.6/M5).\n *\n * Extraction: collect endpoints of every non-construction geometry, chain\n * segments sharing endpoints (tolerance-based), keep closed loops.\n */\nimport type { SketchGeom } from './sketch-parse.js';\n\n/** One segment of a 2D contour: either a straight line or a circular arc (planar, sketch-local coordinates). */\nexport type ContourSeg =\n | { kind: 'line'; x1: number; y1: number; x2: number; y2: number }\n | {\n kind: 'arc';\n cx: number;\n cy: number;\n radius: number;\n startAngle: number;\n endAngle: number;\n ccw: boolean;\n x1: number;\n y1: number;\n x2: number;\n y2: number;\n };\n\n/** A chained run of segments; `closed` when the chain's ends meet (R6: pure data, no OCCT dependency). */\nexport interface Contour {\n /** chained segments in traversal order */\n segments: ContourSeg[];\n /** true when the chain's endpoints meet within JOIN_TOL */\n closed: boolean;\n}\n\nconst JOIN_TOL = 1e-7;\n\nfunction segEnds(g: SketchGeom): [ContourSeg, { x: number; y: number }, { x: number; y: number }] | undefined {\n switch (g.kind) {\n case 'line':\n return [\n { kind: 'line', x1: g.x1, y1: g.y1, x2: g.x2, y2: g.y2 },\n { x: g.x1, y: g.y1 },\n { x: g.x2, y: g.y2 },\n ];\n case 'arc':\n return [\n {\n kind: 'arc', cx: g.cx, cy: g.cy, radius: g.radius,\n startAngle: g.startAngle, endAngle: g.endAngle, ccw: true,\n x1: g.x1, y1: g.y1, x2: g.x2, y2: g.y2,\n },\n { x: g.x1, y: g.y1 },\n { x: g.x2, y: g.y2 },\n ];\n default:\n return undefined; // circles are closed on their own; points don't join\n }\n}\n\n/**\n * Extract closed contours (chains of segments whose endpoints meet).\n * @param geoms solved sketch geometry to chain (non-construction segments only)\n * @returns closed loops plus self-closed circles (open chains are dropped)\n */\nexport function extractContours(geoms: SketchGeom[]): Contour[] {\n const pool: { seg: ContourSeg; a: { x: number; y: number }; b: { x: number; y: number }; used: boolean }[] = [];\n for (const g of geoms) {\n const s = segEnds(g);\n if (s) pool.push({ seg: s[0], a: s[1], b: s[2], used: false });\n }\n\n const near = (p: { x: number; y: number }, q: { x: number; y: number }): boolean =>\n Math.hypot(p.x - q.x, p.y - q.y) <= JOIN_TOL;\n\n const contours: Contour[] = [];\n for (const start of pool) {\n if (start.used) continue;\n start.used = true;\n const segments: ContourSeg[] = [start.seg];\n let tail = start.b;\n const head = start.a;\n let extended = true;\n while (extended) {\n extended = false;\n // GOTCHA (ballend corpus, 2026-09-20): restart the scan from the pool\n // start after EVERY match. Scanning on with the drifted tail let a\n // later-indexed segment touching the new tail steal the chain before\n // the correct earlier-indexed continuation was reached, breaking a\n // perfectly closed loop (extractContours returned 0).\n for (const cand of pool) {\n if (cand.used) continue;\n if (near(tail, cand.a)) {\n cand.used = true;\n segments.push(cand.seg);\n tail = cand.b;\n extended = true;\n break;\n } else if (near(tail, cand.b)) {\n cand.used = true;\n segments.push(reverseSeg(cand.seg));\n tail = cand.a;\n extended = true;\n break;\n }\n }\n }\n const closed = near(tail, head);\n if (segments.length > 0 && closed) {\n contours.push({ segments, closed: true });\n }\n }\n\n // circles are self-closed contours\n for (const g of geoms) {\n if (g.kind === 'circle' && g.radius > 0) {\n contours.push({\n segments: [{\n kind: 'arc', cx: g.cx, cy: g.cy, radius: g.radius,\n startAngle: 0, endAngle: Math.PI * 2, ccw: true,\n x1: g.cx + g.radius, y1: g.cy, x2: g.cx + g.radius, y2: g.cy,\n }],\n closed: true,\n });\n }\n }\n return contours;\n}\n\nfunction reverseSeg(s: ContourSeg): ContourSeg {\n if (s.kind === 'line') return { kind: 'line', x1: s.x2, y1: s.y2, x2: s.x1, y2: s.y1 };\n return { ...s, startAngle: s.endAngle, endAngle: s.startAngle, ccw: !s.ccw, x1: s.x2, y1: s.y2, x2: s.x1, y2: s.y1 };\n}\n"]}
@@ -2,7 +2,27 @@
2
2
  export declare const SKETCH_T1 = 0.000001;
3
3
  /** Dispositions allowed in a conforming container (C4). */
4
4
  export declare const ALLOWED_DISPOSITIONS: Set<string>;
5
+ /**
6
+ * Optional knobs for `convertFcstdFile`.
7
+ */
8
+ export interface ConvertOptions {
9
+ /**
10
+ * Return the container bytes even when the mapping audit reports gaps.
11
+ *
12
+ * Diagnostics only. C4 requires that a gapped document produces **no
13
+ * product**, and the CLI never sets this — `ok` stays false and the exit
14
+ * code stays 2 regardless. It exists because everything the pipeline built
15
+ * before the audit is otherwise unobservable for exactly the documents that
16
+ * need triage: gaps and non-L0 sketches usually co-occur (a sketch the
17
+ * solver cannot handle usually starves the features built on it), so
18
+ * "which contour assets / mapping entries would this document have had"
19
+ * cannot be answered from `gaps[]` alone.
20
+ */
21
+ keepGappedContainer?: boolean;
22
+ }
23
+ /** Structured result of one FCStd → .fai.zip conversion; the caller decides exit codes and formatting. */
5
24
  export interface ConvertSummary {
25
+ /** input file path */
6
26
  file: string;
7
27
  /** conversion succeeded AND mapping final check passed */
8
28
  ok: boolean;
@@ -24,11 +44,36 @@ export interface ConvertSummary {
24
44
  l1: number;
25
45
  l2: number;
26
46
  };
27
- /** container bytes (undefined when ok=false: no zip produced) */
47
+ /** container bytes (undefined when ok=false, unless `keepGappedContainer`) */
28
48
  zip?: Uint8Array;
29
49
  /** human-readable failure when the pipeline itself failed */
30
50
  error?: string;
31
51
  elapsedMs: number;
32
52
  }
33
- export declare function convertFcstdFile(input: string): Promise<ConvertSummary>;
53
+ /**
54
+ * H7 companion (2026-09-20): FEM workbench objects carry SIMULATION semantics
55
+ * (analysis containers, meshes, solver settings, boundary conditions, result
56
+ * pipelines) — no modeling geometry. Native classes without a Proxy property
57
+ * used to fall into `type-not-whitelisted` and block whole files whose
58
+ * modeling part was a single box. Like structural types they are
59
+ * `preserved-only`; their regenerable field data (FemMesh / result Data
60
+ * properties) is deliberately NOT carried into the container.
61
+ *
62
+ * Remote task on record (user decision, 2026-09-20): faijs will port
63
+ * FreeCAD's FEM analysis capability in the future — separate feature; this
64
+ * classification only stops simulation objects from blocking conversion.
65
+ * Python subclasses (*Python with Proxy) never reach this set — H10's
66
+ * python-opaque handles them.
67
+ */
68
+ export declare const FEM_STRUCTURAL_TYPES: Set<string>;
69
+ /** True when the type is a FEM simulation object (structured non-modeling). */
70
+ export declare function isFemStructural(type: string): boolean;
71
+ /**
72
+ * Convert one FCStd file through the full pipeline (unpack → parse → sketch
73
+ * solving → codegen → container build) and return a structured summary.
74
+ * @param input path to the .FCStd file to convert
75
+ * @param opts optional knobs (see {@link ConvertOptions}); defaults keep the C4 contract
76
+ * @returns a summary with ok=false (and no zip, unless `keepGappedContainer`) on any pipeline failure or translation gap
77
+ */
78
+ export declare function convertFcstdFile(input: string, opts?: ConvertOptions): Promise<ConvertSummary>;
34
79
  //# sourceMappingURL=convert.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"convert.d.ts","sourceRoot":"","sources":["../../src/fcstd/convert.ts"],"names":[],"mappings":"AAyBA,2EAA2E;AAC3E,eAAO,MAAM,SAAS,WAAO,CAAC;AAE9B,2DAA2D;AAC3D,eAAO,MAAM,oBAAoB,aAA4D,CAAC;AAE9F,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,EAAE,EAAE,OAAO,CAAC;IACZ,0DAA0D;IAC1D,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACvD,MAAM,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1F,QAAQ,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IAChE,iEAAiE;IACjE,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAsCD,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAwJ7E"}
1
+ {"version":3,"file":"convert.d.ts","sourceRoot":"","sources":["../../src/fcstd/convert.ts"],"names":[],"mappings":"AA+BA,2EAA2E;AAC3E,eAAO,MAAM,SAAS,WAAO,CAAC;AAE9B,2DAA2D;AAC3D,eAAO,MAAM,oBAAoB,aAA4D,CAAC;AAE9F;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;;;;;;;;OAWG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,0GAA0G;AAC1G,MAAM,WAAW,cAAc;IAC7B,sBAAsB;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,EAAE,EAAE,OAAO,CAAC;IACZ,0DAA0D;IAC1D,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACvD,MAAM,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1F,QAAQ,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IAChE,8EAA8E;IAC9E,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAeD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,oBAAoB,aAgB/B,CAAC;AAEH,+EAA+E;AAC/E,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAErD;AA4BD;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,CA0KpG"}
@@ -1,8 +1,13 @@
1
1
  /**
2
- * fcstd-port B0 — reusable FCStd → .fai.zip conversion (extracted from
3
- * scripts/fcstd-to-fai-zip.ts so the batch CLI and the dev script share one
4
- * pipeline). Returns a structured summary; the caller decides exit codes and
5
- * output formatting (C4: batch driver must never absorb failures).
2
+ * B0 — THE FCStd → .fai.zip conversion pipeline (batch CLI and tests share it;
3
+ * `src/fcstd/cli.ts` is its only CLI). Returns a structured summary; the caller
4
+ * decides exit codes and output formatting (C4: batch driver must never absorb
5
+ * failures).
6
+ *
7
+ * 2026-09-20: the M5.4-era dev script `scripts/fcstd-to-fai-zip.ts` was a second
8
+ * implementation of this same pipeline (no C4 audit, no exit-code contract) and
9
+ * has been deleted. Whether the conversion entry is published or not, this file
10
+ * is the single implementation.
6
11
  *
7
12
  * Dispositions (plan §2, C4): translated | python-baked | preserved-only.
8
13
  * Any other baked reason = translation gap → result.ok = false, no zip
@@ -17,9 +22,9 @@ import { classifySketch } from './sketch-verify.js';
17
22
  import { resolveExternalGeometry } from './external-geo.js';
18
23
  import { extractContours } from './contour.js';
19
24
  import { generateModel } from './codegen.js';
20
- import { placementOf } from './placement.js';
25
+ import { effectivePlacement } from './attachment.js';
21
26
  import { buildFaiZip } from './build-fai-zip.js';
22
- import { isOk } from '../vendored/brepjs/core/result.js';
27
+ import { isOk } from '../api/result.js';
23
28
  import { zipSync, unzipSync, strToU8 } from 'fflate';
24
29
  /** V2 tolerance: solver must reproduce stored geometry (single source). */
25
30
  export const SKETCH_T1 = 1e-6;
@@ -33,7 +38,46 @@ const STRUCTURAL_TYPES = new Set([
33
38
  'PartDesign::Body', 'App::Origin', 'App::Plane', 'App::Line',
34
39
  'App::DocumentObjectGroup', 'App::Part', 'PartDesign::Plane', 'PartDesign::Line',
35
40
  'PartDesign::CoordinateSystem',
41
+ // H10 companion fix (plan §3.5): datum/annotation types, not modeling
42
+ // features — previously misclassified as translation gaps.
43
+ 'App::Point', 'App::Annotation',
44
+ ]);
45
+ /**
46
+ * H7 companion (2026-09-20): FEM workbench objects carry SIMULATION semantics
47
+ * (analysis containers, meshes, solver settings, boundary conditions, result
48
+ * pipelines) — no modeling geometry. Native classes without a Proxy property
49
+ * used to fall into `type-not-whitelisted` and block whole files whose
50
+ * modeling part was a single box. Like structural types they are
51
+ * `preserved-only`; their regenerable field data (FemMesh / result Data
52
+ * properties) is deliberately NOT carried into the container.
53
+ *
54
+ * Remote task on record (user decision, 2026-09-20): faijs will port
55
+ * FreeCAD's FEM analysis capability in the future — separate feature; this
56
+ * classification only stops simulation objects from blocking conversion.
57
+ * Python subclasses (*Python with Proxy) never reach this set — H10's
58
+ * python-opaque handles them.
59
+ */
60
+ export const FEM_STRUCTURAL_TYPES = new Set([
61
+ 'Fem::FemAnalysis',
62
+ 'Fem::FemMeshObject',
63
+ 'Fem::FemResultObject',
64
+ 'Fem::FemPostPipeline',
65
+ 'Fem::ConstraintFixed',
66
+ 'Fem::ConstraintForce',
67
+ 'Fem::ConstraintPressure',
68
+ 'Fem::ConstraintContact',
69
+ 'Fem::ConstraintBearing',
70
+ 'Fem::ConstraintDisplacement',
71
+ 'Fem::FemAnalysisPython',
72
+ 'Fem::FemMeshObjectPython',
73
+ 'Fem::FemResultObjectPython',
74
+ 'Fem::FemSolverObjectPython',
75
+ 'Fem::FemMeshShapeBaseObjectPython',
36
76
  ]);
77
+ /** True when the type is a FEM simulation object (structured non-modeling). */
78
+ export function isFemStructural(type) {
79
+ return FEM_STRUCTURAL_TYPES.has(type);
80
+ }
37
81
  /**
38
82
  * C4 final check: reclassify `baked` entries. A baked disposition is only
39
83
  * legitimate for Python-opaque objects (python-baked), structural/datum
@@ -48,9 +92,9 @@ function auditMapping(mapping) {
48
92
  if (o.reason === 'python-opaque') {
49
93
  o.disposition = 'python-baked';
50
94
  }
51
- else if (STRUCTURAL_TYPES.has(o.type)) {
95
+ else if (STRUCTURAL_TYPES.has(o.type) || FEM_STRUCTURAL_TYPES.has(o.type)) {
52
96
  o.disposition = 'preserved-only';
53
- o.reason = o.reason ?? 'structural';
97
+ o.reason = o.reason ?? (FEM_STRUCTURAL_TYPES.has(o.type) ? 'fem-simulation' : 'structural');
54
98
  }
55
99
  else {
56
100
  gaps.push({ name: o.name, type: o.type, reason: o.reason ?? 'unspecified' });
@@ -59,7 +103,14 @@ function auditMapping(mapping) {
59
103
  }
60
104
  return gaps;
61
105
  }
62
- export async function convertFcstdFile(input) {
106
+ /**
107
+ * Convert one FCStd file through the full pipeline (unpack → parse → sketch
108
+ * solving → codegen → container build) and return a structured summary.
109
+ * @param input path to the .FCStd file to convert
110
+ * @param opts optional knobs (see {@link ConvertOptions}); defaults keep the C4 contract
111
+ * @returns a summary with ok=false (and no zip, unless `keepGappedContainer`) on any pipeline failure or translation gap
112
+ */
113
+ export async function convertFcstdFile(input, opts) {
63
114
  const t0 = Date.now();
64
115
  const baseName = input.replace(/^.*[/\\]/, '').replace(/\.fcstd$/i, '');
65
116
  const fail = (error) => ({
@@ -134,13 +185,26 @@ export async function convertFcstdFile(input) {
134
185
  }
135
186
  }
136
187
  // M4/M5: translate + codegen
188
+ // H3: attachment-resolved placements — an attached sketch's stored Placement
189
+ // is recomputed by FreeCAD from Support ∘ AttachmentOffset; resolve the chain
190
+ // so non-XY-plane sketches land on their support frame (fall back to the
191
+ // stored value when the attachment is deactivated or unresolvable).
137
192
  const placements = new Map();
138
193
  for (const obj of doc.value.objects) {
139
- placements.set(obj.name, placementOf(obj));
194
+ placements.set(obj.name, effectivePlacement(obj, placements));
195
+ }
196
+ // H7: objects whose Shape is stored as a ZIP .brp member (pure-Shape
197
+ // carriers, e.g. Part::Feature) — collected from the archive members so the
198
+ // translator can record `shape-asset` instead of a translation gap.
199
+ const shapeCarriers = new Set();
200
+ for (const obj of doc.value.objects) {
201
+ const fileAttr = obj.properties.get('Shape')?.children[0]?.attributes['file'];
202
+ if (fileAttr && memberText(unpacked.value, fileAttr) !== undefined)
203
+ shapeCarriers.add(obj.name);
140
204
  }
141
205
  let gen;
142
206
  try {
143
- gen = generateModel(doc.value, sketchVerdict, sketchContours, baseName, placements);
207
+ gen = generateModel(doc.value, sketchVerdict, sketchContours, baseName, placements, shapeCarriers);
144
208
  }
145
209
  catch (e) {
146
210
  return fail(`codegen failed: ${e.message}`);
@@ -214,8 +278,13 @@ export async function convertFcstdFile(input) {
214
278
  for (const v of sketchVerdict.values())
215
279
  sketches[v.level.toLowerCase()]++;
216
280
  if (gaps.length > 0) {
217
- // C4: translation gaps → no container produced (exit contract: 2)
218
- return { file: input, ok: false, gaps, counts, sketches, elapsedMs: Date.now() - t0 };
281
+ // C4: translation gaps → no container produced (exit contract: 2).
282
+ // `keepGappedContainer` is a diagnostics-only escape hatch (see ConvertOptions).
283
+ if (!opts?.keepGappedContainer) {
284
+ return { file: input, ok: false, gaps, counts, sketches, elapsedMs: Date.now() - t0 };
285
+ }
286
+ const gappedZip = zipSync(members, { level: 6 });
287
+ return { file: input, ok: false, gaps, counts, sketches, zip: gappedZip, elapsedMs: Date.now() - t0 };
219
288
  }
220
289
  const zip = zipSync(members, { level: 6 });
221
290
  return { file: input, ok: true, gaps, counts, sketches, zip, elapsedMs: Date.now() - t0 };
@@ -1 +1 @@
1
- {"version":3,"file":"convert.js","sourceRoot":"","sources":["../../src/fcstd/convert.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * fcstd-port B0 — reusable FCStd → .fai.zip conversion (extracted from\n * scripts/fcstd-to-fai-zip.ts so the batch CLI and the dev script share one\n * pipeline). Returns a structured summary; the caller decides exit codes and\n * output formatting (C4: batch driver must never absorb failures).\n *\n * Dispositions (plan §2, C4): translated | python-baked | preserved-only.\n * Any other baked reason = translation gap → result.ok = false, no zip\n * written (final check `auditMapping`).\n */\nimport { readFileSync } from 'node:fs';\nimport { unpackFcstd, memberText } from './unpack.js';\nimport { parseDocumentXml } from './document.js';\nimport { parseSketchObject } from './sketch-parse.js';\nimport { createPlanegcsSolver } from './planegcs-backend.js';\nimport { classifySketch } from './sketch-verify.js';\nimport { resolveExternalGeometry } from './external-geo.js';\nimport { extractContours } from './contour.js';\nimport type { Contour } from './contour.js';\nimport { generateModel } from './codegen.js';\nimport { placementOf, type Placement } from './placement.js';\nimport { buildFaiZip } from './build-fai-zip.js';\nimport { isOk } from '../vendored/brepjs/core/result.js';\nimport { zipSync, unzipSync, strToU8 } from 'fflate';\n\n/** V2 tolerance: solver must reproduce stored geometry (single source). */\nexport const SKETCH_T1 = 1e-6;\n\n/** Dispositions allowed in a conforming container (C4). */\nexport const ALLOWED_DISPOSITIONS = new Set(['translated', 'python-baked', 'preserved-only']);\n\nexport interface ConvertSummary {\n file: string;\n /** conversion succeeded AND mapping final check passed */\n ok: boolean;\n /** translation gaps (non-Python baked) — empty when ok */\n gaps: { name: string; type: string; reason: string }[];\n counts: { translated: number; pythonBaked: number; preservedOnly: number; baked: number };\n sketches: { total: number; l0: number; l1: number; l2: number };\n /** container bytes (undefined when ok=false: no zip produced) */\n zip?: Uint8Array;\n /** human-readable failure when the pipeline itself failed */\n error?: string;\n elapsedMs: number;\n}\n\n/** Non-modeling structural/datum types: never translation gaps. They carry\n * no feature semantics (containers, datum planes/lines/origins, groups) —\n * their semantics are consumed by the translator (Body.Group ordering,\n * datum-plane UpToFace anchors), not emitted as cad calls. */\nconst STRUCTURAL_TYPES = new Set([\n 'PartDesign::Body', 'App::Origin', 'App::Plane', 'App::Line',\n 'App::DocumentObjectGroup', 'App::Part', 'PartDesign::Plane', 'PartDesign::Line',\n 'PartDesign::CoordinateSystem',\n]);\n\n/**\n * C4 final check: reclassify `baked` entries. A baked disposition is only\n * legitimate for Python-opaque objects (python-baked), structural/datum\n * containers (preserved-only), or preserved display members. Everything\n * else is a translation gap.\n */\nfunction auditMapping(\n mapping: { objects: { name: string; type: string; disposition: string; reason?: string }[] },\n): ConvertSummary['gaps'] {\n const gaps: ConvertSummary['gaps'] = [];\n for (const o of mapping.objects) {\n if (o.disposition === 'baked') {\n // python-opaque stays legitimate but is renamed for the ledger\n if (o.reason === 'python-opaque') {\n o.disposition = 'python-baked';\n } else if (STRUCTURAL_TYPES.has(o.type)) {\n o.disposition = 'preserved-only';\n o.reason = o.reason ?? 'structural';\n } else {\n gaps.push({ name: o.name, type: o.type, reason: o.reason ?? 'unspecified' });\n }\n }\n }\n return gaps;\n}\n\nexport async function convertFcstdFile(input: string): Promise<ConvertSummary> {\n const t0 = Date.now();\n const baseName = input.replace(/^.*[/\\\\]/, '').replace(/\\.fcstd$/i, '');\n const fail = (error: string): ConvertSummary => ({\n file: input, ok: false, gaps: [],\n counts: { translated: 0, pythonBaked: 0, preservedOnly: 0, baked: 0 },\n sketches: { total: 0, l0: 0, l1: 0, l2: 0 }, error, elapsedMs: Date.now() - t0,\n });\n\n let raw: Uint8Array;\n try {\n raw = new Uint8Array(readFileSync(input));\n } catch (e) {\n return fail(`read failed: ${(e as Error).message}`);\n }\n const unpacked = unpackFcstd(raw);\n if (!isOk(unpacked)) return fail(`unpack failed: ${JSON.stringify(unpacked.error)}`);\n const xml = memberText(unpacked.value, 'Document.xml');\n if (xml === undefined) return fail('Document.xml missing');\n const doc = parseDocumentXml(xml);\n if (!isOk(doc)) return fail(`parse failed: ${doc.error.message}`);\n\n // M3: solve every sketch (same pipeline as the dev script)\n const solver = await createPlanegcsSolver();\n const sketchVerdict = new Map<string, { level: 'L0' | 'L1' | 'L2'; reason?: string; loopCount?: number }>();\n const sketchContours = new Map<string, Contour[]>();\n for (const obj of doc.value.objects) {\n if (obj.type !== 'Sketcher::SketchObject') continue;\n const sk = parseSketchObject(obj.properties.get('Geometry'), obj.properties.get('Constraints'), false);\n const offPlane = sk.geoms.some((g) => {\n const zs = g.kind === 'point' ? [g.z]\n : g.kind === 'line' ? [g.z1, g.z2]\n : [g.cz];\n return zs.some((z) => Math.abs(z) > 1e-9);\n });\n const preBlocked =\n offPlane ? 'sketch-geometry-off-plane'\n : sk.geoms.some((g) => !Number.isFinite((g as { x?: number }).x ?? 0)) ? 'unsupported-geometry'\n : undefined;\n if (preBlocked) {\n sketchVerdict.set(obj.name, { level: 'L2', reason: preBlocked });\n continue;\n }\n let external: { geoId: number; polyline: [number, number][] }[] | undefined;\n if (sk.externalGeoIds.length > 0) {\n const ext = await resolveExternalGeometry(\n obj.properties.get('ExternalGeometry'), doc.value, unpacked.value, obj.properties.get('Placement'),\n );\n const usable = ext.links.filter((l) => l.polyline.length === 2);\n if (usable.length === 0) {\n sketchVerdict.set(obj.name, {\n level: 'L2',\n reason: `external-geometry-unresolved: ${ext.failures[0]?.reason ?? 'no links'}`,\n });\n continue;\n }\n external = usable.map((l, i) => ({ geoId: -3 - i, polyline: l.polyline }));\n }\n try {\n const r = await solver.solve(sk.geoms, sk.constraints, external);\n if (!isOk(r)) {\n sketchVerdict.set(obj.name, { level: 'L2', reason: 'solver-error' });\n continue;\n }\n const verdict = classifySketch(r.value, sk.geoms, SKETCH_T1);\n const contours = verdict.level === 'L0' ? extractContours(r.value.geoms) : [];\n sketchVerdict.set(obj.name, { ...verdict, loopCount: contours.length });\n if (verdict.level === 'L0') sketchContours.set(obj.name, contours);\n } catch (e) {\n sketchVerdict.set(obj.name, { level: 'L2', reason: `solver-throw: ${(e as Error).message.slice(0, 60)}` });\n }\n }\n\n // M4/M5: translate + codegen\n const placements = new Map<string, Placement>();\n for (const obj of doc.value.objects) {\n placements.set(obj.name, placementOf(obj));\n }\n let gen;\n try {\n gen = generateModel(doc.value, sketchVerdict, sketchContours, baseName, placements);\n } catch (e) {\n return fail(`codegen failed: ${(e as Error).message}`);\n }\n\n // M2: container with shadow, then inject model/ + updated mapping\n const built = buildFaiZip(unpacked.value, baseName + '.FCStd');\n if (built.error || !built.result) return fail(`container build failed: ${built.error ?? 'unknown'}`);\n const members: Record<string, Uint8Array> = {};\n for (const [k, v] of Object.entries(unzipSync(built.result.zip))) members[k] = v;\n members['model/main.fai.js'] = strToU8(gen.code);\n for (const f of gen.files) members[f.path] = strToU8(f.code);\n\n const mapping = built.result.mapping;\n for (const o of gen.objects) {\n const entry = mapping.objects.find((e) => e.name === o.name);\n if (!entry) continue;\n entry.disposition = o.disposition;\n if (o.reason) entry.reason = o.reason;\n if (o.sketch) {\n entry.sketch = {\n level: o.sketch.level === 'L0' ? 'solved' : o.sketch.level === 'L1' ? 'initial-value' : 'baked',\n reason: o.sketch.reason,\n gcs: o.sketch,\n };\n }\n if (o.disposition === 'translated' && !entry.artifacts.includes('model/main.fai.js')) {\n entry.artifacts.push('model/main.fai.js');\n }\n }\n\n // M11.4 (G9): L1/L2 sketches persist raw geometry as a contour asset\n for (const obj of doc.value.objects) {\n if (obj.type !== 'Sketcher::SketchObject') continue;\n const verdict = sketchVerdict.get(obj.name);\n if (!verdict || verdict.level === 'L0') continue;\n const sk = parseSketchObject(obj.properties.get('Geometry'), obj.properties.get('Constraints'), false);\n const asset = {\n sketch: obj.name,\n level: verdict.level,\n reason: verdict.reason,\n geoms: sk.geoms,\n constraints: sk.constraints.map((c) => ({ index: c.index, type: c.type, refs: c.refs, value: c.value, isDriving: c.isDriving })),\n };\n const path = `assets/${obj.name}.contour.json`;\n members[path] = strToU8(JSON.stringify(asset, null, 2));\n const entry = mapping.objects.find((e) => e.name === obj.name);\n if (entry && !entry.artifacts.includes(path)) entry.artifacts.push(path);\n }\n\n // C4 final check: reclassify python-opaque; everything else baked = gap\n const gaps = auditMapping(mapping);\n members['mapping.json'] = strToU8(JSON.stringify(mapping, null, 2));\n\n const counts = { translated: 0, pythonBaked: 0, preservedOnly: 0, baked: 0 };\n for (const o of mapping.objects) {\n const d = o.disposition as string; // auditMapping may rename baked -> python-baked\n if (d === 'translated') counts.translated++;\n else if (d === 'python-baked') counts.pythonBaked++;\n else if (d === 'preserved-only') counts.preservedOnly++;\n else counts.baked++;\n }\n const sketches = { total: sketchVerdict.size, l0: 0, l1: 0, l2: 0 };\n for (const v of sketchVerdict.values()) sketches[v.level.toLowerCase() as 'l0' | 'l1' | 'l2']++;\n\n if (gaps.length > 0) {\n // C4: translation gaps → no container produced (exit contract: 2)\n return { file: input, ok: false, gaps, counts, sketches, elapsedMs: Date.now() - t0 };\n }\n\n const zip = zipSync(members, { level: 6 });\n return { file: input, ok: true, gaps, counts, sketches, zip, elapsedMs: Date.now() - t0 };\n}\n"]}
1
+ {"version":3,"file":"convert.js","sourceRoot":"","sources":["../../src/fcstd/convert.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * B0 — THE FCStd → .fai.zip conversion pipeline (batch CLI and tests share it;\n * `src/fcstd/cli.ts` is its only CLI). Returns a structured summary; the caller\n * decides exit codes and output formatting (C4: batch driver must never absorb\n * failures).\n *\n * 2026-09-20: the M5.4-era dev script `scripts/fcstd-to-fai-zip.ts` was a second\n * implementation of this same pipeline (no C4 audit, no exit-code contract) and\n * has been deleted. Whether the conversion entry is published or not, this file\n * is the single implementation.\n *\n * Dispositions (plan §2, C4): translated | python-baked | preserved-only.\n * Any other baked reason = translation gap → result.ok = false, no zip\n * written (final check `auditMapping`).\n */\nimport { readFileSync } from 'node:fs';\nimport { unpackFcstd, memberText } from './unpack.js';\nimport { parseDocumentXml } from './document.js';\nimport { parseSketchObject } from './sketch-parse.js';\nimport { createPlanegcsSolver } from './planegcs-backend.js';\nimport { classifySketch } from './sketch-verify.js';\nimport { resolveExternalGeometry } from './external-geo.js';\nimport { extractContours } from './contour.js';\nimport type { Contour } from './contour.js';\nimport { generateModel } from './codegen.js';\nimport { placementOf, type Placement } from './placement.js';\nimport { effectivePlacement } from './attachment.js';\nimport { buildFaiZip } from './build-fai-zip.js';\nimport { isOk } from '../api/result.js';\nimport { zipSync, unzipSync, strToU8 } from 'fflate';\n\n/** V2 tolerance: solver must reproduce stored geometry (single source). */\nexport const SKETCH_T1 = 1e-6;\n\n/** Dispositions allowed in a conforming container (C4). */\nexport const ALLOWED_DISPOSITIONS = new Set(['translated', 'python-baked', 'preserved-only']);\n\n/**\n * Optional knobs for `convertFcstdFile`.\n */\nexport interface ConvertOptions {\n /**\n * Return the container bytes even when the mapping audit reports gaps.\n *\n * Diagnostics only. C4 requires that a gapped document produces **no\n * product**, and the CLI never sets this — `ok` stays false and the exit\n * code stays 2 regardless. It exists because everything the pipeline built\n * before the audit is otherwise unobservable for exactly the documents that\n * need triage: gaps and non-L0 sketches usually co-occur (a sketch the\n * solver cannot handle usually starves the features built on it), so\n * \"which contour assets / mapping entries would this document have had\"\n * cannot be answered from `gaps[]` alone.\n */\n keepGappedContainer?: boolean;\n}\n\n/** Structured result of one FCStd → .fai.zip conversion; the caller decides exit codes and formatting. */\nexport interface ConvertSummary {\n /** input file path */\n file: string;\n /** conversion succeeded AND mapping final check passed */\n ok: boolean;\n /** translation gaps (non-Python baked) — empty when ok */\n gaps: { name: string; type: string; reason: string }[];\n counts: { translated: number; pythonBaked: number; preservedOnly: number; baked: number };\n sketches: { total: number; l0: number; l1: number; l2: number };\n /** container bytes (undefined when ok=false, unless `keepGappedContainer`) */\n zip?: Uint8Array;\n /** human-readable failure when the pipeline itself failed */\n error?: string;\n elapsedMs: number;\n}\n\n/** Non-modeling structural/datum types: never translation gaps. They carry\n * no feature semantics (containers, datum planes/lines/origins, groups) —\n * their semantics are consumed by the translator (Body.Group ordering,\n * datum-plane UpToFace anchors), not emitted as cad calls. */\nconst STRUCTURAL_TYPES = new Set([\n 'PartDesign::Body', 'App::Origin', 'App::Plane', 'App::Line',\n 'App::DocumentObjectGroup', 'App::Part', 'PartDesign::Plane', 'PartDesign::Line',\n 'PartDesign::CoordinateSystem',\n // H10 companion fix (plan §3.5): datum/annotation types, not modeling\n // features — previously misclassified as translation gaps.\n 'App::Point', 'App::Annotation',\n]);\n\n/**\n * H7 companion (2026-09-20): FEM workbench objects carry SIMULATION semantics\n * (analysis containers, meshes, solver settings, boundary conditions, result\n * pipelines) — no modeling geometry. Native classes without a Proxy property\n * used to fall into `type-not-whitelisted` and block whole files whose\n * modeling part was a single box. Like structural types they are\n * `preserved-only`; their regenerable field data (FemMesh / result Data\n * properties) is deliberately NOT carried into the container.\n *\n * Remote task on record (user decision, 2026-09-20): faijs will port\n * FreeCAD's FEM analysis capability in the future — separate feature; this\n * classification only stops simulation objects from blocking conversion.\n * Python subclasses (*Python with Proxy) never reach this set — H10's\n * python-opaque handles them.\n */\nexport const FEM_STRUCTURAL_TYPES = new Set([\n 'Fem::FemAnalysis',\n 'Fem::FemMeshObject',\n 'Fem::FemResultObject',\n 'Fem::FemPostPipeline',\n 'Fem::ConstraintFixed',\n 'Fem::ConstraintForce',\n 'Fem::ConstraintPressure',\n 'Fem::ConstraintContact',\n 'Fem::ConstraintBearing',\n 'Fem::ConstraintDisplacement',\n 'Fem::FemAnalysisPython',\n 'Fem::FemMeshObjectPython',\n 'Fem::FemResultObjectPython',\n 'Fem::FemSolverObjectPython',\n 'Fem::FemMeshShapeBaseObjectPython',\n]);\n\n/** True when the type is a FEM simulation object (structured non-modeling). */\nexport function isFemStructural(type: string): boolean {\n return FEM_STRUCTURAL_TYPES.has(type);\n}\n\n/**\n * C4 final check: reclassify `baked` entries. A baked disposition is only\n * legitimate for Python-opaque objects (python-baked), structural/datum\n * containers (preserved-only), or preserved display members. Everything\n * else is a translation gap.\n */\nfunction auditMapping(\n mapping: { objects: { name: string; type: string; disposition: string; reason?: string }[] },\n): ConvertSummary['gaps'] {\n const gaps: ConvertSummary['gaps'] = [];\n for (const o of mapping.objects) {\n if (o.disposition === 'baked') {\n // python-opaque stays legitimate but is renamed for the ledger\n if (o.reason === 'python-opaque') {\n o.disposition = 'python-baked';\n } else if (STRUCTURAL_TYPES.has(o.type) || FEM_STRUCTURAL_TYPES.has(o.type)) {\n o.disposition = 'preserved-only';\n o.reason = o.reason ?? (FEM_STRUCTURAL_TYPES.has(o.type) ? 'fem-simulation' : 'structural');\n } else {\n gaps.push({ name: o.name, type: o.type, reason: o.reason ?? 'unspecified' });\n }\n }\n }\n return gaps;\n}\n\n/**\n * Convert one FCStd file through the full pipeline (unpack → parse → sketch\n * solving → codegen → container build) and return a structured summary.\n * @param input path to the .FCStd file to convert\n * @param opts optional knobs (see {@link ConvertOptions}); defaults keep the C4 contract\n * @returns a summary with ok=false (and no zip, unless `keepGappedContainer`) on any pipeline failure or translation gap\n */\nexport async function convertFcstdFile(input: string, opts?: ConvertOptions): Promise<ConvertSummary> {\n const t0 = Date.now();\n const baseName = input.replace(/^.*[/\\\\]/, '').replace(/\\.fcstd$/i, '');\n const fail = (error: string): ConvertSummary => ({\n file: input, ok: false, gaps: [],\n counts: { translated: 0, pythonBaked: 0, preservedOnly: 0, baked: 0 },\n sketches: { total: 0, l0: 0, l1: 0, l2: 0 }, error, elapsedMs: Date.now() - t0,\n });\n\n let raw: Uint8Array;\n try {\n raw = new Uint8Array(readFileSync(input));\n } catch (e) {\n return fail(`read failed: ${(e as Error).message}`);\n }\n const unpacked = unpackFcstd(raw);\n if (!isOk(unpacked)) return fail(`unpack failed: ${JSON.stringify(unpacked.error)}`);\n const xml = memberText(unpacked.value, 'Document.xml');\n if (xml === undefined) return fail('Document.xml missing');\n const doc = parseDocumentXml(xml);\n if (!isOk(doc)) return fail(`parse failed: ${doc.error.message}`);\n\n // M3: solve every sketch (same pipeline as the dev script)\n const solver = await createPlanegcsSolver();\n const sketchVerdict = new Map<string, { level: 'L0' | 'L1' | 'L2'; reason?: string; loopCount?: number }>();\n const sketchContours = new Map<string, Contour[]>();\n for (const obj of doc.value.objects) {\n if (obj.type !== 'Sketcher::SketchObject') continue;\n const sk = parseSketchObject(obj.properties.get('Geometry'), obj.properties.get('Constraints'), false);\n const offPlane = sk.geoms.some((g) => {\n const zs = g.kind === 'point' ? [g.z]\n : g.kind === 'line' ? [g.z1, g.z2]\n : [g.cz];\n return zs.some((z) => Math.abs(z) > 1e-9);\n });\n const preBlocked =\n offPlane ? 'sketch-geometry-off-plane'\n : sk.geoms.some((g) => !Number.isFinite((g as { x?: number }).x ?? 0)) ? 'unsupported-geometry'\n : undefined;\n if (preBlocked) {\n sketchVerdict.set(obj.name, { level: 'L2', reason: preBlocked });\n continue;\n }\n let external: { geoId: number; polyline: [number, number][] }[] | undefined;\n if (sk.externalGeoIds.length > 0) {\n const ext = await resolveExternalGeometry(\n obj.properties.get('ExternalGeometry'), doc.value, unpacked.value, obj.properties.get('Placement'),\n );\n const usable = ext.links.filter((l) => l.polyline.length === 2);\n if (usable.length === 0) {\n sketchVerdict.set(obj.name, {\n level: 'L2',\n reason: `external-geometry-unresolved: ${ext.failures[0]?.reason ?? 'no links'}`,\n });\n continue;\n }\n external = usable.map((l, i) => ({ geoId: -3 - i, polyline: l.polyline }));\n }\n try {\n const r = await solver.solve(sk.geoms, sk.constraints, external);\n if (!isOk(r)) {\n sketchVerdict.set(obj.name, { level: 'L2', reason: 'solver-error' });\n continue;\n }\n const verdict = classifySketch(r.value, sk.geoms, SKETCH_T1);\n const contours = verdict.level === 'L0' ? extractContours(r.value.geoms) : [];\n sketchVerdict.set(obj.name, { ...verdict, loopCount: contours.length });\n if (verdict.level === 'L0') sketchContours.set(obj.name, contours);\n } catch (e) {\n sketchVerdict.set(obj.name, { level: 'L2', reason: `solver-throw: ${(e as Error).message.slice(0, 60)}` });\n }\n }\n\n // M4/M5: translate + codegen\n // H3: attachment-resolved placements — an attached sketch's stored Placement\n // is recomputed by FreeCAD from Support ∘ AttachmentOffset; resolve the chain\n // so non-XY-plane sketches land on their support frame (fall back to the\n // stored value when the attachment is deactivated or unresolvable).\n const placements = new Map<string, Placement>();\n for (const obj of doc.value.objects) {\n placements.set(obj.name, effectivePlacement(obj, placements));\n }\n // H7: objects whose Shape is stored as a ZIP .brp member (pure-Shape\n // carriers, e.g. Part::Feature) — collected from the archive members so the\n // translator can record `shape-asset` instead of a translation gap.\n const shapeCarriers = new Set<string>();\n for (const obj of doc.value.objects) {\n const fileAttr = obj.properties.get('Shape')?.children[0]?.attributes['file'];\n if (fileAttr && memberText(unpacked.value, fileAttr) !== undefined) shapeCarriers.add(obj.name);\n }\n\n let gen;\n try {\n gen = generateModel(doc.value, sketchVerdict, sketchContours, baseName, placements, shapeCarriers);\n } catch (e) {\n return fail(`codegen failed: ${(e as Error).message}`);\n }\n\n // M2: container with shadow, then inject model/ + updated mapping\n const built = buildFaiZip(unpacked.value, baseName + '.FCStd');\n if (built.error || !built.result) return fail(`container build failed: ${built.error ?? 'unknown'}`);\n const members: Record<string, Uint8Array> = {};\n for (const [k, v] of Object.entries(unzipSync(built.result.zip))) members[k] = v;\n members['model/main.fai.js'] = strToU8(gen.code);\n for (const f of gen.files) members[f.path] = strToU8(f.code);\n\n const mapping = built.result.mapping;\n for (const o of gen.objects) {\n const entry = mapping.objects.find((e) => e.name === o.name);\n if (!entry) continue;\n entry.disposition = o.disposition;\n if (o.reason) entry.reason = o.reason;\n if (o.sketch) {\n entry.sketch = {\n level: o.sketch.level === 'L0' ? 'solved' : o.sketch.level === 'L1' ? 'initial-value' : 'baked',\n reason: o.sketch.reason,\n gcs: o.sketch,\n };\n }\n if (o.disposition === 'translated' && !entry.artifacts.includes('model/main.fai.js')) {\n entry.artifacts.push('model/main.fai.js');\n }\n }\n\n // M11.4 (G9): L1/L2 sketches persist raw geometry as a contour asset\n for (const obj of doc.value.objects) {\n if (obj.type !== 'Sketcher::SketchObject') continue;\n const verdict = sketchVerdict.get(obj.name);\n if (!verdict || verdict.level === 'L0') continue;\n const sk = parseSketchObject(obj.properties.get('Geometry'), obj.properties.get('Constraints'), false);\n const asset = {\n sketch: obj.name,\n level: verdict.level,\n reason: verdict.reason,\n geoms: sk.geoms,\n constraints: sk.constraints.map((c) => ({ index: c.index, type: c.type, refs: c.refs, value: c.value, isDriving: c.isDriving })),\n };\n const path = `assets/${obj.name}.contour.json`;\n members[path] = strToU8(JSON.stringify(asset, null, 2));\n const entry = mapping.objects.find((e) => e.name === obj.name);\n if (entry && !entry.artifacts.includes(path)) entry.artifacts.push(path);\n }\n\n // C4 final check: reclassify python-opaque; everything else baked = gap\n const gaps = auditMapping(mapping);\n members['mapping.json'] = strToU8(JSON.stringify(mapping, null, 2));\n\n const counts = { translated: 0, pythonBaked: 0, preservedOnly: 0, baked: 0 };\n for (const o of mapping.objects) {\n const d = o.disposition as string; // auditMapping may rename baked -> python-baked\n if (d === 'translated') counts.translated++;\n else if (d === 'python-baked') counts.pythonBaked++;\n else if (d === 'preserved-only') counts.preservedOnly++;\n else counts.baked++;\n }\n const sketches = { total: sketchVerdict.size, l0: 0, l1: 0, l2: 0 };\n for (const v of sketchVerdict.values()) sketches[v.level.toLowerCase() as 'l0' | 'l1' | 'l2']++;\n\n if (gaps.length > 0) {\n // C4: translation gaps → no container produced (exit contract: 2).\n // `keepGappedContainer` is a diagnostics-only escape hatch (see ConvertOptions).\n if (!opts?.keepGappedContainer) {\n return { file: input, ok: false, gaps, counts, sketches, elapsedMs: Date.now() - t0 };\n }\n const gappedZip = zipSync(members, { level: 6 });\n return { file: input, ok: false, gaps, counts, sketches, zip: gappedZip, elapsedMs: Date.now() - t0 };\n }\n\n const zip = zipSync(members, { level: 6 });\n return { file: input, ok: true, gaps, counts, sketches, zip, elapsedMs: Date.now() - t0 };\n}\n"]}
@@ -1,4 +1,5 @@
1
- import { type Result } from '../vendored/brepjs/core/result.js';
1
+ import { type Result } from '../api/result.js';
2
+ /** One parsed XML node from Document.xml: a property element (or nested value element) with its children. */
2
3
  export interface FcstdProperty {
3
4
  /** property name attribute ('' for non-property elements like <Geometry>) */
4
5
  name: string;
@@ -14,6 +15,7 @@ export interface FcstdProperty {
14
15
  children: FcstdProperty[];
15
16
  attributes: Record<string, string>;
16
17
  }
18
+ /** One object from <ObjectData>: name, resolved type and its property map. */
17
19
  export interface FcstdObject {
18
20
  /** object name, e.g. "Sketch" or "Pad" */
19
21
  name: string;
@@ -21,6 +23,7 @@ export interface FcstdObject {
21
23
  type: string;
22
24
  properties: Map<string, FcstdProperty>;
23
25
  }
26
+ /** Parsed Document.xml: objects, the <Objects> type index and document-level meta. */
24
27
  export interface FcstdDocument {
25
28
  objects: FcstdObject[];
26
29
  /** from <Objects>: name → type (index only, not authoritative) */
@@ -28,6 +31,7 @@ export interface FcstdDocument {
28
31
  /** document-level properties (Creator, LastModifiedDate, ...) */
29
32
  meta: Map<string, FcstdProperty>;
30
33
  }
34
+ /** Parse failure detail; currently only XML-level errors (`kind: 'xml'`). */
31
35
  export type ParseError = {
32
36
  kind: 'xml';
33
37
  message: string;
@@ -36,6 +40,9 @@ export type ParseError = {
36
40
  * Parse Document.xml into the object graph. Only `<ObjectData>` entries are
37
41
  * returned as objects; `<Objects>` feeds `typeIndex`; document meta from
38
42
  * `<Document ...>` attributes' sibling properties.
43
+ *
44
+ * @param xml raw Document.xml content
45
+ * @returns the parsed object graph, or an xml ParseError
39
46
  */
40
47
  export declare function parseDocumentXml(xml: string): Result<FcstdDocument, ParseError>;
41
48
  //# sourceMappingURL=document.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../../src/fcstd/document.ts"],"names":[],"mappings":"AAQA,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAEzE,MAAM,WAAW,aAAa;IAC5B,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,OAAO,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2EAA2E;IAC3E,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,WAAW;IAC1B,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;CACxC;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,kEAAkE;IAClE,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,iEAAiE;IACjE,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;CAClC;AAED,MAAM,MAAM,UAAU,GAAG;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AA6C1D;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,aAAa,EAAE,UAAU,CAAC,CAuE/E"}
1
+ {"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../../src/fcstd/document.ts"],"names":[],"mappings":"AAQA,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAExD,6GAA6G;AAC7G,MAAM,WAAW,aAAa;IAC5B,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,OAAO,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2EAA2E;IAC3E,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED,8EAA8E;AAC9E,MAAM,WAAW,WAAW;IAC1B,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;CACxC;AAED,sFAAsF;AACtF,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,kEAAkE;IAClE,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,iEAAiE;IACjE,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;CAClC;AAED,6EAA6E;AAC7E,MAAM,MAAM,UAAU,GAAG;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AA6C1D;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,aAAa,EAAE,UAAU,CAAC,CAuE/E"}
@@ -6,7 +6,7 @@
6
6
  * `_Property` elements are skipped.
7
7
  */
8
8
  import { DOMParser, XMLSerializer } from '@xmldom/xmldom';
9
- import { err, ok } from '../vendored/brepjs/core/result.js';
9
+ import { err, ok } from '../api/result.js';
10
10
  function attrs(el) {
11
11
  const out = {};
12
12
  for (let i = 0; i < el.attributes.length; i++) {
@@ -49,6 +49,9 @@ function parseProperty(propEl) {
49
49
  * Parse Document.xml into the object graph. Only `<ObjectData>` entries are
50
50
  * returned as objects; `<Objects>` feeds `typeIndex`; document meta from
51
51
  * `<Document ...>` attributes' sibling properties.
52
+ *
53
+ * @param xml raw Document.xml content
54
+ * @returns the parsed object graph, or an xml ParseError
52
55
  */
53
56
  export function parseDocumentXml(xml) {
54
57
  let doc;
@@ -1 +1 @@
1
- {"version":3,"file":"document.js","sourceRoot":"","sources":["../../src/fcstd/document.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * M1.2 — Document.xml → object graph.\n *\n * Contract per plan §5.5 / 前文 A §3.3: `<ObjectData>` is authoritative\n * (objects + properties); `<Objects>` is only a type index. Transient\n * `_Property` elements are skipped.\n */\nimport { DOMParser, XMLSerializer } from '@xmldom/xmldom';\nimport { err, ok, type Result } from '../vendored/brepjs/core/result.js';\n\nexport interface FcstdProperty {\n /** property name attribute ('' for non-property elements like <Geometry>) */\n name: string;\n /** e.g. \"App::PropertyString\", \"Part::PropertyPartShape\" */\n type: string;\n /** serialized element tag, e.g. \"Geometry\", \"Constrain\", \"LineSegment\" */\n tagName: string;\n /** raw nested XML content (first element child), serialized */\n valueXml?: string;\n /** for simple value properties, the text content */\n valueText?: string;\n /** named sub-elements (e.g. <Python .../>, <ExpressionEngine> children) */\n children: FcstdProperty[];\n attributes: Record<string, string>;\n}\n\nexport interface FcstdObject {\n /** object name, e.g. \"Sketch\" or \"Pad\" */\n name: string;\n /** e.g. \"Sketcher::SketchObject\", \"PartDesign::Pad\" */\n type: string;\n properties: Map<string, FcstdProperty>;\n}\n\nexport interface FcstdDocument {\n objects: FcstdObject[];\n /** from <Objects>: name → type (index only, not authoritative) */\n typeIndex: Map<string, string>;\n /** document-level properties (Creator, LastModifiedDate, ...) */\n meta: Map<string, FcstdProperty>;\n}\n\nexport type ParseError = { kind: 'xml'; message: string };\n\nfunction attrs(el: Element): Record<string, string> {\n const out: Record<string, string> = {};\n for (let i = 0; i < el.attributes.length; i++) {\n const a = el.attributes.item(i);\n if (a) out[a.name] = a.value;\n }\n return out;\n}\n\nfunction firstElementChild(el: Element): Element | undefined {\n for (let c = el.firstChild; c; c = c.nextSibling) {\n if (c.nodeType === 1) return c as Element;\n }\n return undefined;\n}\n\nfunction serialize(el: Element): string {\n // @xmldom/xmldom ships its own Node/Element types that conflict with lib: DOM;\n // the parser/serializer boundary is the only place the two universes meet.\n type SerNode = Parameters<XMLSerializer['serializeToString']>[0];\n return new XMLSerializer().serializeToString(el as unknown as SerNode);\n}\n\nfunction parseProperty(propEl: Element): FcstdProperty {\n const children: FcstdProperty[] = [];\n for (let c = propEl.firstChild; c; c = c.nextSibling) {\n if (c.nodeType === 1) {\n const child = c as Element;\n children.push(parseProperty(child));\n }\n }\n const valueEl = firstElementChild(propEl);\n return {\n name: propEl.getAttribute('name') ?? '',\n type: propEl.getAttribute('type') ?? '',\n tagName: propEl.tagName,\n attributes: attrs(propEl),\n valueXml: valueEl ? serialize(valueEl) : undefined,\n valueText: valueEl?.textContent ?? propEl.textContent ?? '',\n children,\n };\n}\n\n/**\n * Parse Document.xml into the object graph. Only `<ObjectData>` entries are\n * returned as objects; `<Objects>` feeds `typeIndex`; document meta from\n * `<Document ...>` attributes' sibling properties.\n */\nexport function parseDocumentXml(xml: string): Result<FcstdDocument, ParseError> {\n let doc: Document;\n try {\n // xmldom's Document type conflicts with lib: DOM; cast across the boundary.\n doc = new DOMParser().parseFromString(xml, 'application/xml') as unknown as Document;\n } catch (e) {\n return err({ kind: 'xml', message: `Document.xml parse failed: ${e instanceof Error ? e.message : String(e)}` });\n }\n const root = doc.documentElement;\n if (!root || root.tagName !== 'Document') {\n return err({ kind: 'xml', message: `root element is <${root?.tagName ?? 'none'}>, expected <Document>` });\n }\n\n const objects: FcstdObject[] = [];\n const typeIndex = new Map<string, string>();\n const meta = new Map<string, FcstdProperty>();\n\n for (let n = root.firstChild; n; n = n.nextSibling) {\n if (n.nodeType !== 1) continue;\n const el = n as Element;\n switch (el.tagName) {\n case 'Objects': {\n for (let c = el.firstChild; c; c = c.nextSibling) {\n if (c.nodeType !== 1) continue;\n const oe = c as Element;\n if (oe.tagName !== 'Object') continue;\n const name = oe.getAttribute('name');\n const type = oe.getAttribute('type');\n if (name && type) typeIndex.set(name, type);\n }\n break;\n }\n case 'ObjectData': {\n for (let c = el.firstChild; c; c = c.nextSibling) {\n if (c.nodeType !== 1) continue;\n const oe = c as Element;\n if (oe.tagName !== 'Object') continue;\n const name = oe.getAttribute('name') ?? '';\n // ObjectData <Object> carries only `name`; the type lives in the\n // <Objects> index (plan §3.3: Objects is the type index).\n const type = oe.getAttribute('type') ?? typeIndex.get(name) ?? '';\n const properties = new Map<string, FcstdProperty>();\n // properties live under a <Properties Count=\"N\"> wrapper element\n for (let p = oe.firstChild; p; p = p.nextSibling) {\n if (p.nodeType !== 1) continue;\n const pe = p as Element;\n if (pe.tagName === 'Properties') {\n for (let q = pe.firstChild; q; q = q.nextSibling) {\n if (q.nodeType !== 1) continue;\n const qe = q as Element;\n if (qe.tagName === '_Property') continue; // transient\n const prop = parseProperty(qe);\n if (prop.name) properties.set(prop.name, prop);\n }\n } else if (pe.tagName !== '_Property') {\n const prop = parseProperty(pe);\n if (prop.name) properties.set(prop.name, prop);\n }\n }\n objects.push({ name, type, properties });\n }\n break;\n }\n default: {\n // document meta properties (Creator, Uuid, LastModifiedDate, ...)\n const prop = parseProperty(el);\n if (prop.name) meta.set(prop.name, prop);\n }\n }\n }\n return ok({ objects, typeIndex, meta });\n}\n"]}
1
+ {"version":3,"file":"document.js","sourceRoot":"","sources":["../../src/fcstd/document.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * M1.2 — Document.xml → object graph.\n *\n * Contract per plan §5.5 / 前文 A §3.3: `<ObjectData>` is authoritative\n * (objects + properties); `<Objects>` is only a type index. Transient\n * `_Property` elements are skipped.\n */\nimport { DOMParser, XMLSerializer } from '@xmldom/xmldom';\nimport { err, ok, type Result } from '../api/result.js';\n\n/** One parsed XML node from Document.xml: a property element (or nested value element) with its children. */\nexport interface FcstdProperty {\n /** property name attribute ('' for non-property elements like <Geometry>) */\n name: string;\n /** e.g. \"App::PropertyString\", \"Part::PropertyPartShape\" */\n type: string;\n /** serialized element tag, e.g. \"Geometry\", \"Constrain\", \"LineSegment\" */\n tagName: string;\n /** raw nested XML content (first element child), serialized */\n valueXml?: string;\n /** for simple value properties, the text content */\n valueText?: string;\n /** named sub-elements (e.g. <Python .../>, <ExpressionEngine> children) */\n children: FcstdProperty[];\n attributes: Record<string, string>;\n}\n\n/** One object from <ObjectData>: name, resolved type and its property map. */\nexport interface FcstdObject {\n /** object name, e.g. \"Sketch\" or \"Pad\" */\n name: string;\n /** e.g. \"Sketcher::SketchObject\", \"PartDesign::Pad\" */\n type: string;\n properties: Map<string, FcstdProperty>;\n}\n\n/** Parsed Document.xml: objects, the <Objects> type index and document-level meta. */\nexport interface FcstdDocument {\n objects: FcstdObject[];\n /** from <Objects>: name → type (index only, not authoritative) */\n typeIndex: Map<string, string>;\n /** document-level properties (Creator, LastModifiedDate, ...) */\n meta: Map<string, FcstdProperty>;\n}\n\n/** Parse failure detail; currently only XML-level errors (`kind: 'xml'`). */\nexport type ParseError = { kind: 'xml'; message: string };\n\nfunction attrs(el: Element): Record<string, string> {\n const out: Record<string, string> = {};\n for (let i = 0; i < el.attributes.length; i++) {\n const a = el.attributes.item(i);\n if (a) out[a.name] = a.value;\n }\n return out;\n}\n\nfunction firstElementChild(el: Element): Element | undefined {\n for (let c = el.firstChild; c; c = c.nextSibling) {\n if (c.nodeType === 1) return c as Element;\n }\n return undefined;\n}\n\nfunction serialize(el: Element): string {\n // @xmldom/xmldom ships its own Node/Element types that conflict with lib: DOM;\n // the parser/serializer boundary is the only place the two universes meet.\n type SerNode = Parameters<XMLSerializer['serializeToString']>[0];\n return new XMLSerializer().serializeToString(el as unknown as SerNode);\n}\n\nfunction parseProperty(propEl: Element): FcstdProperty {\n const children: FcstdProperty[] = [];\n for (let c = propEl.firstChild; c; c = c.nextSibling) {\n if (c.nodeType === 1) {\n const child = c as Element;\n children.push(parseProperty(child));\n }\n }\n const valueEl = firstElementChild(propEl);\n return {\n name: propEl.getAttribute('name') ?? '',\n type: propEl.getAttribute('type') ?? '',\n tagName: propEl.tagName,\n attributes: attrs(propEl),\n valueXml: valueEl ? serialize(valueEl) : undefined,\n valueText: valueEl?.textContent ?? propEl.textContent ?? '',\n children,\n };\n}\n\n/**\n * Parse Document.xml into the object graph. Only `<ObjectData>` entries are\n * returned as objects; `<Objects>` feeds `typeIndex`; document meta from\n * `<Document ...>` attributes' sibling properties.\n *\n * @param xml raw Document.xml content\n * @returns the parsed object graph, or an xml ParseError\n */\nexport function parseDocumentXml(xml: string): Result<FcstdDocument, ParseError> {\n let doc: Document;\n try {\n // xmldom's Document type conflicts with lib: DOM; cast across the boundary.\n doc = new DOMParser().parseFromString(xml, 'application/xml') as unknown as Document;\n } catch (e) {\n return err({ kind: 'xml', message: `Document.xml parse failed: ${e instanceof Error ? e.message : String(e)}` });\n }\n const root = doc.documentElement;\n if (!root || root.tagName !== 'Document') {\n return err({ kind: 'xml', message: `root element is <${root?.tagName ?? 'none'}>, expected <Document>` });\n }\n\n const objects: FcstdObject[] = [];\n const typeIndex = new Map<string, string>();\n const meta = new Map<string, FcstdProperty>();\n\n for (let n = root.firstChild; n; n = n.nextSibling) {\n if (n.nodeType !== 1) continue;\n const el = n as Element;\n switch (el.tagName) {\n case 'Objects': {\n for (let c = el.firstChild; c; c = c.nextSibling) {\n if (c.nodeType !== 1) continue;\n const oe = c as Element;\n if (oe.tagName !== 'Object') continue;\n const name = oe.getAttribute('name');\n const type = oe.getAttribute('type');\n if (name && type) typeIndex.set(name, type);\n }\n break;\n }\n case 'ObjectData': {\n for (let c = el.firstChild; c; c = c.nextSibling) {\n if (c.nodeType !== 1) continue;\n const oe = c as Element;\n if (oe.tagName !== 'Object') continue;\n const name = oe.getAttribute('name') ?? '';\n // ObjectData <Object> carries only `name`; the type lives in the\n // <Objects> index (plan §3.3: Objects is the type index).\n const type = oe.getAttribute('type') ?? typeIndex.get(name) ?? '';\n const properties = new Map<string, FcstdProperty>();\n // properties live under a <Properties Count=\"N\"> wrapper element\n for (let p = oe.firstChild; p; p = p.nextSibling) {\n if (p.nodeType !== 1) continue;\n const pe = p as Element;\n if (pe.tagName === 'Properties') {\n for (let q = pe.firstChild; q; q = q.nextSibling) {\n if (q.nodeType !== 1) continue;\n const qe = q as Element;\n if (qe.tagName === '_Property') continue; // transient\n const prop = parseProperty(qe);\n if (prop.name) properties.set(prop.name, prop);\n }\n } else if (pe.tagName !== '_Property') {\n const prop = parseProperty(pe);\n if (prop.name) properties.set(prop.name, prop);\n }\n }\n objects.push({ name, type, properties });\n }\n break;\n }\n default: {\n // document meta properties (Creator, Uuid, LastModifiedDate, ...)\n const prop = parseProperty(el);\n if (prop.name) meta.set(prop.name, prop);\n }\n }\n }\n return ok({ objects, typeIndex, meta });\n}\n"]}
@@ -12,14 +12,20 @@
12
12
  * treat the property as unknown and degrade (no heuristic fallback, plan §12).
13
13
  */
14
14
  import type { FcstdProperty } from './document.js';
15
+ /** Evaluated expression result in mm (angles keep their own unit): a number, or undefined when unsupported. */
15
16
  export type ExprValue = number | undefined;
16
17
  /**
17
18
  * Evaluate a constant expression. Returns undefined when the expression is
18
19
  * not a bare constant (reference/arithmetic/function) — explicit unsupported,
19
20
  * no guessing.
21
+ *
22
+ * @param expr raw expression string (e.g. "10 mm")
23
+ * @returns the value in mm, or undefined when not a constant expression
20
24
  */
21
25
  export declare function evalConstantExpression(expr: string): ExprValue;
26
+ /** One <Expression path=... expression=...> binding with its optional constant value. */
22
27
  export interface ExpressionBinding {
28
+ /** property path the expression drives (e.g. "Length") */
23
29
  path: string;
24
30
  /** raw expression string */
25
31
  expression: string;
@@ -30,8 +36,15 @@ export interface ExpressionBinding {
30
36
  * Extract expression bindings from a parsed ExpressionEngine property.
31
37
  * `bindable` = every expression evaluated to a constant (then the property
32
38
  * values can be overridden); otherwise the engine is only partially readable.
39
+ *
40
+ * @param prop the parsed <ExpressionEngine> property (undefined → no bindings)
41
+ * @returns one binding per <Expression> child, values evaluated
33
42
  */
34
43
  export declare function parseExpressionEngine(prop: FcstdProperty | undefined): ExpressionBinding[];
35
- /** True when all bindings are constants → safe to override property values. */
44
+ /**
45
+ * True when all bindings are constants → safe to override property values.
46
+ * @param bindings bindings extracted by parseExpressionEngine
47
+ * @returns true when there is at least one binding and every value is defined
48
+ */
36
49
  export declare function allConstant(bindings: ExpressionBinding[]): boolean;
37
50
  //# sourceMappingURL=expressions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"expressions.d.ts","sourceRoot":"","sources":["../../src/fcstd/expressions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,SAAS,CAAC;AAa3C;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAY9D;AAGD,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,mEAAmE;IACnE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,aAAa,GAAG,SAAS,GAC9B,iBAAiB,EAAE,CAWrB;AAED,+EAA+E;AAC/E,wBAAgB,WAAW,CAAC,QAAQ,EAAE,iBAAiB,EAAE,GAAG,OAAO,CAElE"}
1
+ {"version":3,"file":"expressions.d.ts","sourceRoot":"","sources":["../../src/fcstd/expressions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,+GAA+G;AAC/G,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,SAAS,CAAC;AAa3C;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAY9D;AAGD,yFAAyF;AACzF,MAAM,WAAW,iBAAiB;IAChC,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,mEAAmE;IACnE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,aAAa,GAAG,SAAS,GAC9B,iBAAiB,EAAE,CAWrB;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,iBAAiB,EAAE,GAAG,OAAO,CAElE"}
@@ -12,6 +12,9 @@ const UNIT_TO_MM = {
12
12
  * Evaluate a constant expression. Returns undefined when the expression is
13
13
  * not a bare constant (reference/arithmetic/function) — explicit unsupported,
14
14
  * no guessing.
15
+ *
16
+ * @param expr raw expression string (e.g. "10 mm")
17
+ * @returns the value in mm, or undefined when not a constant expression
15
18
  */
16
19
  export function evalConstantExpression(expr) {
17
20
  const s = expr.trim();
@@ -34,6 +37,9 @@ export function evalConstantExpression(expr) {
34
37
  * Extract expression bindings from a parsed ExpressionEngine property.
35
38
  * `bindable` = every expression evaluated to a constant (then the property
36
39
  * values can be overridden); otherwise the engine is only partially readable.
40
+ *
41
+ * @param prop the parsed <ExpressionEngine> property (undefined → no bindings)
42
+ * @returns one binding per <Expression> child, values evaluated
37
43
  */
38
44
  export function parseExpressionEngine(prop) {
39
45
  const engineEl = prop?.children[0];
@@ -49,7 +55,11 @@ export function parseExpressionEngine(prop) {
49
55
  }
50
56
  return out;
51
57
  }
52
- /** True when all bindings are constants → safe to override property values. */
58
+ /**
59
+ * True when all bindings are constants → safe to override property values.
60
+ * @param bindings bindings extracted by parseExpressionEngine
61
+ * @returns true when there is at least one binding and every value is defined
62
+ */
53
63
  export function allConstant(bindings) {
54
64
  return bindings.length > 0 && bindings.every((b) => b.value !== undefined);
55
65
  }
@@ -1 +1 @@
1
- {"version":3,"file":"expressions.js","sourceRoot":"","sources":["../../src/fcstd/expressions.ts"],"names":[],"mappings":"AAiBA,MAAM,UAAU,GAA2B;IACzC,EAAE,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC;IACpB,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE;IACtB,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI;IACpB,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI;IAC/B,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK;IACtB,qEAAqE;IACrE,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC;IACzB,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAAY;IACjD,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IACtB,gBAAgB;IAChB,MAAM,CAAC,GAAG,+DAA+D,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClF,IAAI,CAAC,CAAC;QAAE,OAAO,SAAS,CAAC;IACzB,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC9C,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC;IACnB,IAAI,IAAI,KAAK,EAAE;QAAE,OAAO,KAAK,CAAC;IAC9B,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;IAC9C,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC,CAAC,6BAA6B;IACzE,OAAO,KAAK,GAAG,MAAM,CAAC;AACxB,CAAC;AAWD;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CACnC,IAA+B;IAE/B,MAAM,QAAQ,GAAG,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;IACnC,IAAI,CAAC,QAAQ;QAAE,OAAO,EAAE,CAAC;IACzB,MAAM,GAAG,GAAwB,EAAE,CAAC;IACpC,KAAK,MAAM,EAAE,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;QACnC,IAAI,EAAE,CAAC,OAAO,KAAK,YAAY;YAAE,SAAS;QAC1C,MAAM,IAAI,GAAG,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACzC,MAAM,UAAU,GAAG,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;QACrD,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,sBAAsB,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;IAC5E,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,WAAW,CAAC,QAA6B;IACvD,OAAO,QAAQ,CAAC,MAAM,GAAG,CAAC,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC;AAC7E,CAAC","sourcesContent":["/**\r\n * M6.2 — expression downgrade: <ExpressionEngine> → concrete values.\r\n *\r\n * Format (verified on TwoLengthsPadWithExpression.FCStd):\r\n * <ExpressionEngine count=\"N\">\r\n * <Expression path=\"Length\" expression=\"10 mm\" />\r\n * </ExpressionEngine>\r\n *\r\n * Scope: constant expressions only — number literals with optional unit\r\n * (mm/m/cm/in/deg/rad). Anything else (object references like Sketch.Constraints[3],\r\n * arithmetic with identifiers, functions) is NOT evaluated: the consumer must\r\n * treat the property as unknown and degrade (no heuristic fallback, plan §12).\r\n */\r\nimport type { FcstdProperty } from './document.js';\r\n\r\nexport type ExprValue = number | undefined;\r\n\r\nconst UNIT_TO_MM: Record<string, number> = {\r\n mm: 1, millimeter: 1,\r\n cm: 10, centimeter: 10,\r\n m: 1000, meter: 1000,\r\n in: 25.4, inch: 25.4, '\"': 25.4,\r\n ft: 304.8, foot: 304.8,\r\n // angles: value kept in the expression's own unit; callers interpret\r\n deg: 1, degree: 1, '°': 1,\r\n rad: 1, radian: 1,\r\n};\r\n\r\n/**\r\n * Evaluate a constant expression. Returns undefined when the expression is\r\n * not a bare constant (reference/arithmetic/function) — explicit unsupported,\r\n * no guessing.\r\n */\r\nexport function evalConstantExpression(expr: string): ExprValue {\r\n const s = expr.trim();\r\n // number [unit]\r\n const m = /^([-+]?(?:\\d+\\.?\\d*|\\.\\d+)(?:[eE][-+]?\\d+)?)\\s*([A-Za-z°\"]*)$/.exec(s);\r\n if (!m) return undefined;\r\n const value = Number(m[1]);\r\n if (!Number.isFinite(value)) return undefined;\r\n const unit = m[2]!;\r\n if (unit === '') return value;\r\n const factor = UNIT_TO_MM[unit.toLowerCase()];\r\n if (factor === undefined) return undefined; // unknown unit → unsupported\r\n return value * factor;\r\n}\r\n\r\n\r\nexport interface ExpressionBinding {\r\n path: string;\r\n /** raw expression string */\r\n expression: string;\r\n /** evaluated constant; undefined when not a constant expression */\r\n value?: number;\r\n}\r\n\r\n/**\r\n * Extract expression bindings from a parsed ExpressionEngine property.\r\n * `bindable` = every expression evaluated to a constant (then the property\r\n * values can be overridden); otherwise the engine is only partially readable.\r\n */\r\nexport function parseExpressionEngine(\r\n prop: FcstdProperty | undefined,\r\n): ExpressionBinding[] {\r\n const engineEl = prop?.children[0];\r\n if (!engineEl) return [];\r\n const out: ExpressionBinding[] = [];\r\n for (const el of engineEl.children) {\r\n if (el.tagName !== 'Expression') continue;\r\n const path = el.attributes['path'] ?? '';\r\n const expression = el.attributes['expression'] ?? '';\r\n out.push({ path, expression, value: evalConstantExpression(expression) });\r\n }\r\n return out;\r\n}\r\n\r\n/** True when all bindings are constants → safe to override property values. */\r\nexport function allConstant(bindings: ExpressionBinding[]): boolean {\r\n return bindings.length > 0 && bindings.every((b) => b.value !== undefined);\r\n}\r\n"]}
1
+ {"version":3,"file":"expressions.js","sourceRoot":"","sources":["../../src/fcstd/expressions.ts"],"names":[],"mappings":"AAkBA,MAAM,UAAU,GAA2B;IACzC,EAAE,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC;IACpB,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE;IACtB,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI;IACpB,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI;IAC/B,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK;IACtB,qEAAqE;IACrE,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC;IACzB,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAAY;IACjD,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IACtB,gBAAgB;IAChB,MAAM,CAAC,GAAG,+DAA+D,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClF,IAAI,CAAC,CAAC;QAAE,OAAO,SAAS,CAAC;IACzB,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC9C,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC;IACnB,IAAI,IAAI,KAAK,EAAE;QAAE,OAAO,KAAK,CAAC;IAC9B,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;IAC9C,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC,CAAC,6BAA6B;IACzE,OAAO,KAAK,GAAG,MAAM,CAAC;AACxB,CAAC;AAaD;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CACnC,IAA+B;IAE/B,MAAM,QAAQ,GAAG,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;IACnC,IAAI,CAAC,QAAQ;QAAE,OAAO,EAAE,CAAC;IACzB,MAAM,GAAG,GAAwB,EAAE,CAAC;IACpC,KAAK,MAAM,EAAE,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;QACnC,IAAI,EAAE,CAAC,OAAO,KAAK,YAAY;YAAE,SAAS;QAC1C,MAAM,IAAI,GAAG,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACzC,MAAM,UAAU,GAAG,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;QACrD,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,sBAAsB,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;IAC5E,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,QAA6B;IACvD,OAAO,QAAQ,CAAC,MAAM,GAAG,CAAC,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC;AAC7E,CAAC","sourcesContent":["/**\r\n * M6.2 — expression downgrade: <ExpressionEngine> → concrete values.\r\n *\r\n * Format (verified on TwoLengthsPadWithExpression.FCStd):\r\n * <ExpressionEngine count=\"N\">\r\n * <Expression path=\"Length\" expression=\"10 mm\" />\r\n * </ExpressionEngine>\r\n *\r\n * Scope: constant expressions only — number literals with optional unit\r\n * (mm/m/cm/in/deg/rad). Anything else (object references like Sketch.Constraints[3],\r\n * arithmetic with identifiers, functions) is NOT evaluated: the consumer must\r\n * treat the property as unknown and degrade (no heuristic fallback, plan §12).\r\n */\r\nimport type { FcstdProperty } from './document.js';\r\n\r\n/** Evaluated expression result in mm (angles keep their own unit): a number, or undefined when unsupported. */\nexport type ExprValue = number | undefined;\r\n\r\nconst UNIT_TO_MM: Record<string, number> = {\r\n mm: 1, millimeter: 1,\r\n cm: 10, centimeter: 10,\r\n m: 1000, meter: 1000,\r\n in: 25.4, inch: 25.4, '\"': 25.4,\r\n ft: 304.8, foot: 304.8,\r\n // angles: value kept in the expression's own unit; callers interpret\r\n deg: 1, degree: 1, '°': 1,\r\n rad: 1, radian: 1,\r\n};\r\n\r\n/**\r\n * Evaluate a constant expression. Returns undefined when the expression is\r\n * not a bare constant (reference/arithmetic/function) — explicit unsupported,\r\n * no guessing.\r\n *\r\n * @param expr raw expression string (e.g. \"10 mm\")\r\n * @returns the value in mm, or undefined when not a constant expression\r\n */\r\nexport function evalConstantExpression(expr: string): ExprValue {\r\n const s = expr.trim();\r\n // number [unit]\r\n const m = /^([-+]?(?:\\d+\\.?\\d*|\\.\\d+)(?:[eE][-+]?\\d+)?)\\s*([A-Za-z°\"]*)$/.exec(s);\r\n if (!m) return undefined;\r\n const value = Number(m[1]);\r\n if (!Number.isFinite(value)) return undefined;\r\n const unit = m[2]!;\r\n if (unit === '') return value;\r\n const factor = UNIT_TO_MM[unit.toLowerCase()];\r\n if (factor === undefined) return undefined; // unknown unit → unsupported\r\n return value * factor;\r\n}\r\n\r\n\r\n/** One <Expression path=... expression=...> binding with its optional constant value. */\r\nexport interface ExpressionBinding {\r\n /** property path the expression drives (e.g. \"Length\") */\r\n path: string;\r\n /** raw expression string */\r\n expression: string;\r\n /** evaluated constant; undefined when not a constant expression */\r\n value?: number;\r\n}\r\n\r\n/**\r\n * Extract expression bindings from a parsed ExpressionEngine property.\r\n * `bindable` = every expression evaluated to a constant (then the property\r\n * values can be overridden); otherwise the engine is only partially readable.\r\n *\r\n * @param prop the parsed <ExpressionEngine> property (undefined → no bindings)\r\n * @returns one binding per <Expression> child, values evaluated\r\n */\r\nexport function parseExpressionEngine(\r\n prop: FcstdProperty | undefined,\r\n): ExpressionBinding[] {\r\n const engineEl = prop?.children[0];\r\n if (!engineEl) return [];\r\n const out: ExpressionBinding[] = [];\r\n for (const el of engineEl.children) {\r\n if (el.tagName !== 'Expression') continue;\r\n const path = el.attributes['path'] ?? '';\r\n const expression = el.attributes['expression'] ?? '';\r\n out.push({ path, expression, value: evalConstantExpression(expression) });\r\n }\r\n return out;\r\n}\r\n\r\n/**\n * True when all bindings are constants → safe to override property values.\r\n * @param bindings bindings extracted by parseExpressionEngine\r\n * @returns true when there is at least one binding and every value is defined\r\n */\r\nexport function allConstant(bindings: ExpressionBinding[]): boolean {\r\n return bindings.length > 0 && bindings.every((b) => b.value !== undefined);\r\n}\r\n"]}