@faicad/sheetmetal 0.13.0

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 (113) hide show
  1. package/dist/allowanceFns.d.ts +42 -0
  2. package/dist/allowanceFns.d.ts.map +1 -0
  3. package/dist/allowanceFns.js +63 -0
  4. package/dist/allowanceFns.js.map +1 -0
  5. package/dist/api.d.ts +312 -0
  6. package/dist/api.d.ts.map +1 -0
  7. package/dist/api.js +367 -0
  8. package/dist/api.js.map +1 -0
  9. package/dist/authorFns.d.ts +118 -0
  10. package/dist/authorFns.d.ts.map +1 -0
  11. package/dist/authorFns.js +562 -0
  12. package/dist/authorFns.js.map +1 -0
  13. package/dist/bendTableFns.d.ts +87 -0
  14. package/dist/bendTableFns.d.ts.map +1 -0
  15. package/dist/bendTableFns.js +345 -0
  16. package/dist/bendTableFns.js.map +1 -0
  17. package/dist/compat.d.ts +90 -0
  18. package/dist/compat.d.ts.map +1 -0
  19. package/dist/compat.js +99 -0
  20. package/dist/compat.js.map +1 -0
  21. package/dist/contourFlangeFns.d.ts +122 -0
  22. package/dist/contourFlangeFns.d.ts.map +1 -0
  23. package/dist/contourFlangeFns.js +403 -0
  24. package/dist/contourFlangeFns.js.map +1 -0
  25. package/dist/cutoutFns.d.ts +95 -0
  26. package/dist/cutoutFns.d.ts.map +1 -0
  27. package/dist/cutoutFns.js +300 -0
  28. package/dist/cutoutFns.js.map +1 -0
  29. package/dist/dxfFns.d.ts +48 -0
  30. package/dist/dxfFns.d.ts.map +1 -0
  31. package/dist/dxfFns.js +236 -0
  32. package/dist/dxfFns.js.map +1 -0
  33. package/dist/facade.d.ts +449 -0
  34. package/dist/facade.d.ts.map +1 -0
  35. package/dist/facade.js +535 -0
  36. package/dist/facade.js.map +1 -0
  37. package/dist/featureTreeFns.d.ts +76 -0
  38. package/dist/featureTreeFns.d.ts.map +1 -0
  39. package/dist/featureTreeFns.js +190 -0
  40. package/dist/featureTreeFns.js.map +1 -0
  41. package/dist/foldFns.d.ts +83 -0
  42. package/dist/foldFns.d.ts.map +1 -0
  43. package/dist/foldFns.js +744 -0
  44. package/dist/foldFns.js.map +1 -0
  45. package/dist/foreignUnfoldFns.d.ts +62 -0
  46. package/dist/foreignUnfoldFns.d.ts.map +1 -0
  47. package/dist/foreignUnfoldFns.js +854 -0
  48. package/dist/foreignUnfoldFns.js.map +1 -0
  49. package/dist/formFns.d.ts +58 -0
  50. package/dist/formFns.d.ts.map +1 -0
  51. package/dist/formFns.js +300 -0
  52. package/dist/formFns.js.map +1 -0
  53. package/dist/hemFns.d.ts +17 -0
  54. package/dist/hemFns.d.ts.map +1 -0
  55. package/dist/hemFns.js +212 -0
  56. package/dist/hemFns.js.map +1 -0
  57. package/dist/index.d.ts +43 -0
  58. package/dist/index.d.ts.map +1 -0
  59. package/dist/index.js +32 -0
  60. package/dist/index.js.map +1 -0
  61. package/dist/internal.d.ts +27 -0
  62. package/dist/internal.d.ts.map +1 -0
  63. package/dist/internal.js +38 -0
  64. package/dist/internal.js.map +1 -0
  65. package/dist/jogFns.d.ts +18 -0
  66. package/dist/jogFns.d.ts.map +1 -0
  67. package/dist/jogFns.js +180 -0
  68. package/dist/jogFns.js.map +1 -0
  69. package/dist/loftedFlangeFns.d.ts +23 -0
  70. package/dist/loftedFlangeFns.d.ts.map +1 -0
  71. package/dist/loftedFlangeFns.js +306 -0
  72. package/dist/loftedFlangeFns.js.map +1 -0
  73. package/dist/materials.d.ts +16 -0
  74. package/dist/materials.d.ts.map +1 -0
  75. package/dist/materials.js +44 -0
  76. package/dist/materials.js.map +1 -0
  77. package/dist/miterFns.d.ts +31 -0
  78. package/dist/miterFns.d.ts.map +1 -0
  79. package/dist/miterFns.js +128 -0
  80. package/dist/miterFns.js.map +1 -0
  81. package/dist/nestFns.d.ts +116 -0
  82. package/dist/nestFns.d.ts.map +1 -0
  83. package/dist/nestFns.js +517 -0
  84. package/dist/nestFns.js.map +1 -0
  85. package/dist/polygonFns.d.ts +144 -0
  86. package/dist/polygonFns.d.ts.map +1 -0
  87. package/dist/polygonFns.js +324 -0
  88. package/dist/polygonFns.js.map +1 -0
  89. package/dist/reliefFns.d.ts +44 -0
  90. package/dist/reliefFns.d.ts.map +1 -0
  91. package/dist/reliefFns.js +367 -0
  92. package/dist/reliefFns.js.map +1 -0
  93. package/dist/reportFns.d.ts +33 -0
  94. package/dist/reportFns.d.ts.map +1 -0
  95. package/dist/reportFns.js +71 -0
  96. package/dist/reportFns.js.map +1 -0
  97. package/dist/tabFns.d.ts +43 -0
  98. package/dist/tabFns.d.ts.map +1 -0
  99. package/dist/tabFns.js +179 -0
  100. package/dist/tabFns.js.map +1 -0
  101. package/dist/types.d.ts +629 -0
  102. package/dist/types.d.ts.map +1 -0
  103. package/dist/types.js +2 -0
  104. package/dist/types.js.map +1 -0
  105. package/dist/unfoldFns.d.ts +120 -0
  106. package/dist/unfoldFns.d.ts.map +1 -0
  107. package/dist/unfoldFns.js +766 -0
  108. package/dist/unfoldFns.js.map +1 -0
  109. package/dist/validateFns.d.ts +18 -0
  110. package/dist/validateFns.d.ts.map +1 -0
  111. package/dist/validateFns.js +196 -0
  112. package/dist/validateFns.js.map +1 -0
  113. package/package.json +45 -0
@@ -0,0 +1,76 @@
1
+ import { type Result } from '@faicad/faijs/brepjs-compat';
2
+ import type { BendFeature, FlangeFeature, SheetMetalPart, SheetMetalWarning } from './types.js';
3
+ /** Id of the root flat — the part's base flat, present as the root of every graph. */
4
+ export declare const ROOT_FLAT_ID = "root";
5
+ /** A flat region in the feature graph (the part's base plus every flange face). */
6
+ export interface FlatNode {
7
+ id: string;
8
+ isRoot: boolean;
9
+ flange?: FlangeFeature | undefined;
10
+ }
11
+ /** A bend connecting two flats. `parent`/`child` are flat ids; `bend` is the recorded feature. */
12
+ export interface BendEdge {
13
+ bend: BendFeature;
14
+ parent: string;
15
+ child: string;
16
+ }
17
+ /** Graph view of an authored part: flats = nodes, bends = edges. */
18
+ export interface FeatureGraph {
19
+ nodes: Map<string, FlatNode>;
20
+ edges: BendEdge[];
21
+ }
22
+ /** A bend that survives in the spanning tree, paired with its source/target flat. */
23
+ export interface TreeBend {
24
+ bend: BendFeature;
25
+ parent: string;
26
+ child: string;
27
+ }
28
+ /** A non-tree edge that must be cut to flatten a closed profile. */
29
+ export interface SeamCut {
30
+ bend: BendFeature;
31
+ between: [string, string];
32
+ }
33
+ /** Result of a spanning-tree build over a feature graph: BFS-ordered tree bends, seam cuts, and the flat nodes. */
34
+ export interface FeatureTree {
35
+ rootId: string;
36
+ /** Tree edges in breadth-first order (parents precede their children). */
37
+ bends: TreeBend[];
38
+ seams: SeamCut[];
39
+ nodes: Map<string, FlatNode>;
40
+ warnings: SheetMetalWarning[];
41
+ }
42
+ /**
43
+ * Build the feature graph for an authored part. Each flange contributes a flat
44
+ * node `flange.id`; bends connect a parent flat to a child flat. A bend whose id
45
+ * matches a flange id (the flange's own fold) is wired parent→flange; any other
46
+ * bend is matched to a flange by id suffix `<flange>::<n>` or defaults to a
47
+ * root-anchored edge so authored parts always produce a connected graph.
48
+ *
49
+ * @param part - the authored sheet-metal part to graph.
50
+ * @returns a `Result<FeatureGraph>` — `ok` with `{ nodes, edges }` on success,
51
+ * or `err` with a `validationError` for a non-positive thickness, duplicate
52
+ * flange/bend ids, or bends that resolve to no flange flat.
53
+ */
54
+ export declare function buildFeatureGraph(part: SheetMetalPart): Result<FeatureGraph>;
55
+ /**
56
+ * Spanning tree over the feature graph, rooted at the base flat. Tree edges are
57
+ * returned BFS-ordered (every parent appears before its children); edges that
58
+ * would close a cycle (box/closed profiles) become seam cuts with a warning.
59
+ *
60
+ * @param graph - the feature graph built by {@link buildFeatureGraph}.
61
+ * @param rootId - the flat id to root the tree at; defaults to {@link ROOT_FLAT_ID}.
62
+ * @returns a `Result<FeatureTree>` — `ok` with the BFS-ordered tree bends, seam
63
+ * cuts, flat nodes, and warnings, or `err` with a `validationError` when the
64
+ * root is unknown or some flat is unreachable from the root.
65
+ */
66
+ export declare function buildFeatureTree(graph: FeatureGraph, rootId?: string): Result<FeatureTree>;
67
+ /**
68
+ * Convenience: graph + spanning tree in one call.
69
+ *
70
+ * @param part - the authored sheet-metal part to graph and tree.
71
+ * @param rootId - the flat id to root the tree at; defaults to {@link ROOT_FLAT_ID}.
72
+ * @returns a `Result<FeatureTree>` — `ok` with the feature tree, or the first
73
+ * `err` produced by {@link buildFeatureGraph} or {@link buildFeatureTree}.
74
+ */
75
+ export declare function featureTree(part: SheetMetalPart, rootId?: string): Result<FeatureTree>;
76
+ //# sourceMappingURL=featureTreeFns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"featureTreeFns.d.ts","sourceRoot":"","sources":["../src/featureTreeFns.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAA4B,MAAM,6BAA6B,CAAC;AACpF,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEhG,sFAAsF;AACtF,eAAO,MAAM,YAAY,SAAS,CAAC;AAEnC,mFAAmF;AACnF,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;CACpC;AAED,kGAAkG;AAClG,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,WAAW,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,oEAAoE;AACpE,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC7B,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB;AAED,qFAAqF;AACrF,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,WAAW,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,oEAAoE;AACpE,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC3B;AAED,mHAAmH;AACnH,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC7B,QAAQ,EAAE,iBAAiB,EAAE,CAAC;CAC/B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,CAAC,YAAY,CAAC,CAoE5E;AAsBD;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY,EAAE,MAAM,GAAE,MAAqB,GAAG,MAAM,CAAC,WAAW,CAAC,CAqExG;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,cAAc,EAAE,MAAM,GAAE,MAAqB,GAAG,MAAM,CAAC,WAAW,CAAC,CAIpG"}
@@ -0,0 +1,190 @@
1
+ import { ok, err, validationError } from '@faicad/faijs/brepjs-compat';
2
+ /** Id of the root flat — the part's base flat, present as the root of every graph. */
3
+ export const ROOT_FLAT_ID = 'root';
4
+ /**
5
+ * Build the feature graph for an authored part. Each flange contributes a flat
6
+ * node `flange.id`; bends connect a parent flat to a child flat. A bend whose id
7
+ * matches a flange id (the flange's own fold) is wired parent→flange; any other
8
+ * bend is matched to a flange by id suffix `<flange>::<n>` or defaults to a
9
+ * root-anchored edge so authored parts always produce a connected graph.
10
+ *
11
+ * @param part - the authored sheet-metal part to graph.
12
+ * @returns a `Result<FeatureGraph>` — `ok` with `{ nodes, edges }` on success,
13
+ * or `err` with a `validationError` for a non-positive thickness, duplicate
14
+ * flange/bend ids, or bends that resolve to no flange flat.
15
+ */
16
+ export function buildFeatureGraph(part) {
17
+ if (!Number.isFinite(part.thickness) || part.thickness <= 0) {
18
+ return err(validationError('INVALID_THICKNESS', `part.thickness must be positive, got ${part.thickness}`));
19
+ }
20
+ const nodes = new Map();
21
+ nodes.set(ROOT_FLAT_ID, { id: ROOT_FLAT_ID, isRoot: true });
22
+ const flangeById = new Map();
23
+ for (const flange of part.flanges) {
24
+ if (nodes.has(flange.id)) {
25
+ return err(validationError('DUPLICATE_FLAT', `duplicate flange/flat id '${flange.id}'`));
26
+ }
27
+ nodes.set(flange.id, { id: flange.id, isRoot: false, flange });
28
+ flangeById.set(flange.id, flange);
29
+ }
30
+ const edges = [];
31
+ const seenBendIds = new Set();
32
+ for (const bend of part.bends) {
33
+ if (seenBendIds.has(bend.id)) {
34
+ return err(validationError('DUPLICATE_BEND', `duplicate bend id '${bend.id}'`));
35
+ }
36
+ seenBendIds.add(bend.id);
37
+ // A contour-flange bend (`contour::<flange>::<n>`) is one arc of a multi-bend
38
+ // contour flange. The contour flange records its own developed strip in
39
+ // `part.contourFlanges` and lays out independently of the bend spanning tree
40
+ // (its development is non-rectilinear, like a miter), so its bends never enter
41
+ // the graph — they would resolve to no flange flat and break the tree.
42
+ if (bend.id.startsWith('contour::'))
43
+ continue;
44
+ // Hem and jog sub-bends (`hem::<id>::<n>` / `jog::<id>::<n>`) are the curl/step
45
+ // arcs of a recorded hem or jog. Like contour bends they record their own
46
+ // developed strip (in `part.hems` / `part.jogs`) and lay out non-rectilinearly,
47
+ // so they never enter the spanning tree.
48
+ if (bend.id.startsWith('hem::') || bend.id.startsWith('jog::'))
49
+ continue;
50
+ // A seam bend (`seam::<parent>::<child>`) is an explicit edge between two
51
+ // already-authored flats — the cycle-closing edge of a tube/box profile.
52
+ if (bend.id.startsWith('seam::')) {
53
+ // Format is `seam::<parent>::<child>`; flange ids are validated to exclude
54
+ // `::`, so the last delimiter splits parent from child unambiguously.
55
+ const withoutPrefix = bend.id.slice('seam::'.length);
56
+ const sep = withoutPrefix.lastIndexOf('::');
57
+ const parent = sep >= 0 ? withoutPrefix.slice(0, sep) : undefined;
58
+ const child = sep >= 0 ? withoutPrefix.slice(sep + 2) : undefined;
59
+ if (parent === undefined || child === undefined || !nodes.has(parent) || !nodes.has(child)) {
60
+ return err(validationError('UNRESOLVED_SEAM', `seam bend '${bend.id}' references an unknown flat`));
61
+ }
62
+ edges.push({ bend, parent, child });
63
+ continue;
64
+ }
65
+ const child = resolveChildFlat(bend.id, nodes);
66
+ if (child === undefined) {
67
+ return err(validationError('UNRESOLVED_BEND', `bend '${bend.id}' does not correspond to any flange flat`));
68
+ }
69
+ const flange = flangeById.get(child);
70
+ const parent = resolveParentFlat(flange, nodes);
71
+ edges.push({ bend, parent, child });
72
+ }
73
+ return ok({ nodes, edges });
74
+ }
75
+ function resolveChildFlat(bendId, nodes) {
76
+ if (nodes.has(bendId))
77
+ return bendId;
78
+ const sep = bendId.indexOf('::');
79
+ if (sep > 0) {
80
+ const base = bendId.slice(0, sep);
81
+ if (nodes.has(base))
82
+ return base;
83
+ }
84
+ return undefined;
85
+ }
86
+ function resolveParentFlat(flange, nodes) {
87
+ if (flange === undefined)
88
+ return ROOT_FLAT_ID;
89
+ const ref = flange.baseEdge;
90
+ // Chained flanges name their parent flat directly; `face-0` is the base flat.
91
+ if (ref.parentId !== undefined && ref.parentId !== 'face-0' && nodes.has(ref.parentId)) {
92
+ return ref.parentId;
93
+ }
94
+ return nodes.has(`face-${ref.faceIndex}`) ? `face-${ref.faceIndex}` : ROOT_FLAT_ID;
95
+ }
96
+ /**
97
+ * Spanning tree over the feature graph, rooted at the base flat. Tree edges are
98
+ * returned BFS-ordered (every parent appears before its children); edges that
99
+ * would close a cycle (box/closed profiles) become seam cuts with a warning.
100
+ *
101
+ * @param graph - the feature graph built by {@link buildFeatureGraph}.
102
+ * @param rootId - the flat id to root the tree at; defaults to {@link ROOT_FLAT_ID}.
103
+ * @returns a `Result<FeatureTree>` — `ok` with the BFS-ordered tree bends, seam
104
+ * cuts, flat nodes, and warnings, or `err` with a `validationError` when the
105
+ * root is unknown or some flat is unreachable from the root.
106
+ */
107
+ export function buildFeatureTree(graph, rootId = ROOT_FLAT_ID) {
108
+ if (!graph.nodes.has(rootId)) {
109
+ return err(validationError('UNKNOWN_ROOT', `root flat '${rootId}' not present in graph`));
110
+ }
111
+ // Authored seam bends (`seam::…`) are explicitly the cycle-closing edges of a
112
+ // closed profile. They must never enter the spanning tree, or the BFS can grab a
113
+ // seam as a tree edge and demote a real authored bend to the seam cut — placing
114
+ // its child flat off the wrong frame. Exclude them from BFS and record them as
115
+ // seam cuts directly, so authored seams are always the non-tree edges.
116
+ const seamEdges = [];
117
+ const treeCandidates = [];
118
+ for (const edge of graph.edges) {
119
+ if (edge.bend.id.startsWith('seam::'))
120
+ seamEdges.push(edge);
121
+ else
122
+ treeCandidates.push(edge);
123
+ }
124
+ const adjacency = new Map();
125
+ for (const id of graph.nodes.keys())
126
+ adjacency.set(id, []);
127
+ for (const edge of treeCandidates) {
128
+ adjacency.get(edge.parent)?.push(edge);
129
+ adjacency.get(edge.child)?.push(edge);
130
+ }
131
+ const visited = new Set([rootId]);
132
+ const treeEdges = new Set();
133
+ const bends = [];
134
+ const queue = [rootId];
135
+ while (queue.length > 0) {
136
+ const current = queue.shift();
137
+ if (current === undefined)
138
+ break;
139
+ for (const edge of adjacency.get(current) ?? []) {
140
+ if (treeEdges.has(edge))
141
+ continue;
142
+ const next = edge.parent === current ? edge.child : edge.parent;
143
+ if (visited.has(next))
144
+ continue;
145
+ visited.add(next);
146
+ treeEdges.add(edge);
147
+ bends.push({ bend: edge.bend, parent: current, child: next });
148
+ queue.push(next);
149
+ }
150
+ }
151
+ const warnings = [];
152
+ const seams = [];
153
+ const recordSeam = (edge) => {
154
+ seams.push({ bend: edge.bend, between: [edge.parent, edge.child] });
155
+ warnings.push({
156
+ code: 'SEAM_CUT',
157
+ message: `closed profile: bend '${edge.bend.id}' (${edge.parent}↔${edge.child}) becomes a seam cut`,
158
+ featureId: edge.bend.id,
159
+ });
160
+ };
161
+ for (const edge of seamEdges)
162
+ recordSeam(edge);
163
+ // A non-seam edge left out of the tree closes a cycle authored without a seam.
164
+ for (const edge of treeCandidates) {
165
+ if (treeEdges.has(edge))
166
+ continue;
167
+ recordSeam(edge);
168
+ }
169
+ for (const node of graph.nodes.values()) {
170
+ if (!visited.has(node.id)) {
171
+ return err(validationError('DISCONNECTED_FLAT', `flat '${node.id}' is not reachable from root '${rootId}'`));
172
+ }
173
+ }
174
+ return ok({ rootId, bends, seams, nodes: graph.nodes, warnings });
175
+ }
176
+ /**
177
+ * Convenience: graph + spanning tree in one call.
178
+ *
179
+ * @param part - the authored sheet-metal part to graph and tree.
180
+ * @param rootId - the flat id to root the tree at; defaults to {@link ROOT_FLAT_ID}.
181
+ * @returns a `Result<FeatureTree>` — `ok` with the feature tree, or the first
182
+ * `err` produced by {@link buildFeatureGraph} or {@link buildFeatureTree}.
183
+ */
184
+ export function featureTree(part, rootId = ROOT_FLAT_ID) {
185
+ const graph = buildFeatureGraph(part);
186
+ if (!graph.ok)
187
+ return graph;
188
+ return buildFeatureTree(graph.value, rootId);
189
+ }
190
+ //# sourceMappingURL=featureTreeFns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"featureTreeFns.js","sourceRoot":"","sources":["../src/featureTreeFns.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Result, ok, err, validationError } from '@faicad/faijs/brepjs-compat';\nimport type { BendFeature, FlangeFeature, SheetMetalPart, SheetMetalWarning } from './types.js';\n\n/** Id of the root flat — the part's base flat, present as the root of every graph. */\nexport const ROOT_FLAT_ID = 'root';\n\n/** A flat region in the feature graph (the part's base plus every flange face). */\nexport interface FlatNode {\n id: string;\n isRoot: boolean;\n flange?: FlangeFeature | undefined;\n}\n\n/** A bend connecting two flats. `parent`/`child` are flat ids; `bend` is the recorded feature. */\nexport interface BendEdge {\n bend: BendFeature;\n parent: string;\n child: string;\n}\n\n/** Graph view of an authored part: flats = nodes, bends = edges. */\nexport interface FeatureGraph {\n nodes: Map<string, FlatNode>;\n edges: BendEdge[];\n}\n\n/** A bend that survives in the spanning tree, paired with its source/target flat. */\nexport interface TreeBend {\n bend: BendFeature;\n parent: string;\n child: string;\n}\n\n/** A non-tree edge that must be cut to flatten a closed profile. */\nexport interface SeamCut {\n bend: BendFeature;\n between: [string, string];\n}\n\n/** Result of a spanning-tree build over a feature graph: BFS-ordered tree bends, seam cuts, and the flat nodes. */\nexport interface FeatureTree {\n rootId: string;\n /** Tree edges in breadth-first order (parents precede their children). */\n bends: TreeBend[];\n seams: SeamCut[];\n nodes: Map<string, FlatNode>;\n warnings: SheetMetalWarning[];\n}\n\n/**\n * Build the feature graph for an authored part. Each flange contributes a flat\n * node `flange.id`; bends connect a parent flat to a child flat. A bend whose id\n * matches a flange id (the flange's own fold) is wired parent→flange; any other\n * bend is matched to a flange by id suffix `<flange>::<n>` or defaults to a\n * root-anchored edge so authored parts always produce a connected graph.\n *\n * @param part - the authored sheet-metal part to graph.\n * @returns a `Result<FeatureGraph>` — `ok` with `{ nodes, edges }` on success,\n * or `err` with a `validationError` for a non-positive thickness, duplicate\n * flange/bend ids, or bends that resolve to no flange flat.\n */\nexport function buildFeatureGraph(part: SheetMetalPart): Result<FeatureGraph> {\n if (!Number.isFinite(part.thickness) || part.thickness <= 0) {\n return err(validationError('INVALID_THICKNESS', `part.thickness must be positive, got ${part.thickness}`));\n }\n\n const nodes = new Map<string, FlatNode>();\n nodes.set(ROOT_FLAT_ID, { id: ROOT_FLAT_ID, isRoot: true });\n\n const flangeById = new Map<string, FlangeFeature>();\n for (const flange of part.flanges) {\n if (nodes.has(flange.id)) {\n return err(validationError('DUPLICATE_FLAT', `duplicate flange/flat id '${flange.id}'`));\n }\n nodes.set(flange.id, { id: flange.id, isRoot: false, flange });\n flangeById.set(flange.id, flange);\n }\n\n const edges: BendEdge[] = [];\n const seenBendIds = new Set<string>();\n for (const bend of part.bends) {\n if (seenBendIds.has(bend.id)) {\n return err(validationError('DUPLICATE_BEND', `duplicate bend id '${bend.id}'`));\n }\n seenBendIds.add(bend.id);\n\n // A contour-flange bend (`contour::<flange>::<n>`) is one arc of a multi-bend\n // contour flange. The contour flange records its own developed strip in\n // `part.contourFlanges` and lays out independently of the bend spanning tree\n // (its development is non-rectilinear, like a miter), so its bends never enter\n // the graph — they would resolve to no flange flat and break the tree.\n if (bend.id.startsWith('contour::')) continue;\n\n // Hem and jog sub-bends (`hem::<id>::<n>` / `jog::<id>::<n>`) are the curl/step\n // arcs of a recorded hem or jog. Like contour bends they record their own\n // developed strip (in `part.hems` / `part.jogs`) and lay out non-rectilinearly,\n // so they never enter the spanning tree.\n if (bend.id.startsWith('hem::') || bend.id.startsWith('jog::')) continue;\n\n // A seam bend (`seam::<parent>::<child>`) is an explicit edge between two\n // already-authored flats — the cycle-closing edge of a tube/box profile.\n if (bend.id.startsWith('seam::')) {\n // Format is `seam::<parent>::<child>`; flange ids are validated to exclude\n // `::`, so the last delimiter splits parent from child unambiguously.\n const withoutPrefix = bend.id.slice('seam::'.length);\n const sep = withoutPrefix.lastIndexOf('::');\n const parent = sep >= 0 ? withoutPrefix.slice(0, sep) : undefined;\n const child = sep >= 0 ? withoutPrefix.slice(sep + 2) : undefined;\n if (parent === undefined || child === undefined || !nodes.has(parent) || !nodes.has(child)) {\n return err(\n validationError('UNRESOLVED_SEAM', `seam bend '${bend.id}' references an unknown flat`)\n );\n }\n edges.push({ bend, parent, child });\n continue;\n }\n\n const child = resolveChildFlat(bend.id, nodes);\n if (child === undefined) {\n return err(\n validationError('UNRESOLVED_BEND', `bend '${bend.id}' does not correspond to any flange flat`)\n );\n }\n const flange = flangeById.get(child);\n const parent = resolveParentFlat(flange, nodes);\n edges.push({ bend, parent, child });\n }\n\n return ok({ nodes, edges });\n}\n\nfunction resolveChildFlat(bendId: string, nodes: Map<string, FlatNode>): string | undefined {\n if (nodes.has(bendId)) return bendId;\n const sep = bendId.indexOf('::');\n if (sep > 0) {\n const base = bendId.slice(0, sep);\n if (nodes.has(base)) return base;\n }\n return undefined;\n}\n\nfunction resolveParentFlat(flange: FlangeFeature | undefined, nodes: Map<string, FlatNode>): string {\n if (flange === undefined) return ROOT_FLAT_ID;\n const ref = flange.baseEdge;\n // Chained flanges name their parent flat directly; `face-0` is the base flat.\n if (ref.parentId !== undefined && ref.parentId !== 'face-0' && nodes.has(ref.parentId)) {\n return ref.parentId;\n }\n return nodes.has(`face-${ref.faceIndex}`) ? `face-${ref.faceIndex}` : ROOT_FLAT_ID;\n}\n\n/**\n * Spanning tree over the feature graph, rooted at the base flat. Tree edges are\n * returned BFS-ordered (every parent appears before its children); edges that\n * would close a cycle (box/closed profiles) become seam cuts with a warning.\n *\n * @param graph - the feature graph built by {@link buildFeatureGraph}.\n * @param rootId - the flat id to root the tree at; defaults to {@link ROOT_FLAT_ID}.\n * @returns a `Result<FeatureTree>` — `ok` with the BFS-ordered tree bends, seam\n * cuts, flat nodes, and warnings, or `err` with a `validationError` when the\n * root is unknown or some flat is unreachable from the root.\n */\nexport function buildFeatureTree(graph: FeatureGraph, rootId: string = ROOT_FLAT_ID): Result<FeatureTree> {\n if (!graph.nodes.has(rootId)) {\n return err(validationError('UNKNOWN_ROOT', `root flat '${rootId}' not present in graph`));\n }\n\n // Authored seam bends (`seam::…`) are explicitly the cycle-closing edges of a\n // closed profile. They must never enter the spanning tree, or the BFS can grab a\n // seam as a tree edge and demote a real authored bend to the seam cut — placing\n // its child flat off the wrong frame. Exclude them from BFS and record them as\n // seam cuts directly, so authored seams are always the non-tree edges.\n const seamEdges: BendEdge[] = [];\n const treeCandidates: BendEdge[] = [];\n for (const edge of graph.edges) {\n if (edge.bend.id.startsWith('seam::')) seamEdges.push(edge);\n else treeCandidates.push(edge);\n }\n\n const adjacency = new Map<string, BendEdge[]>();\n for (const id of graph.nodes.keys()) adjacency.set(id, []);\n for (const edge of treeCandidates) {\n adjacency.get(edge.parent)?.push(edge);\n adjacency.get(edge.child)?.push(edge);\n }\n\n const visited = new Set<string>([rootId]);\n const treeEdges = new Set<BendEdge>();\n const bends: TreeBend[] = [];\n const queue: string[] = [rootId];\n\n while (queue.length > 0) {\n const current = queue.shift();\n if (current === undefined) break;\n for (const edge of adjacency.get(current) ?? []) {\n if (treeEdges.has(edge)) continue;\n const next = edge.parent === current ? edge.child : edge.parent;\n if (visited.has(next)) continue;\n visited.add(next);\n treeEdges.add(edge);\n bends.push({ bend: edge.bend, parent: current, child: next });\n queue.push(next);\n }\n }\n\n const warnings: SheetMetalWarning[] = [];\n const seams: SeamCut[] = [];\n const recordSeam = (edge: BendEdge): void => {\n seams.push({ bend: edge.bend, between: [edge.parent, edge.child] });\n warnings.push({\n code: 'SEAM_CUT',\n message: `closed profile: bend '${edge.bend.id}' (${edge.parent}↔${edge.child}) becomes a seam cut`,\n featureId: edge.bend.id,\n });\n };\n for (const edge of seamEdges) recordSeam(edge);\n // A non-seam edge left out of the tree closes a cycle authored without a seam.\n for (const edge of treeCandidates) {\n if (treeEdges.has(edge)) continue;\n recordSeam(edge);\n }\n\n for (const node of graph.nodes.values()) {\n if (!visited.has(node.id)) {\n return err(\n validationError('DISCONNECTED_FLAT', `flat '${node.id}' is not reachable from root '${rootId}'`)\n );\n }\n }\n\n return ok({ rootId, bends, seams, nodes: graph.nodes, warnings });\n}\n\n/**\n * Convenience: graph + spanning tree in one call.\n *\n * @param part - the authored sheet-metal part to graph and tree.\n * @param rootId - the flat id to root the tree at; defaults to {@link ROOT_FLAT_ID}.\n * @returns a `Result<FeatureTree>` — `ok` with the feature tree, or the first\n * `err` produced by {@link buildFeatureGraph} or {@link buildFeatureTree}.\n */\nexport function featureTree(part: SheetMetalPart, rootId: string = ROOT_FLAT_ID): Result<FeatureTree> {\n const graph = buildFeatureGraph(part);\n if (!graph.ok) return graph;\n return buildFeatureTree(graph.value, rootId);\n}\n"]}
@@ -0,0 +1,83 @@
1
+ import { type Result } from '@faicad/faijs/brepjs-compat';
2
+ import type { BendRule, FlatInput, FlatPattern, MaterialSpec, SheetMetalPart, SheetMetalWarning } from './types.js';
3
+ /** A folded part plus the warnings raised while folding (e.g. SEAM_CUT, MIN_RADIUS). */
4
+ export interface FoldResult {
5
+ part: SheetMetalPart;
6
+ warnings: SheetMetalWarning[];
7
+ }
8
+ /**
9
+ * Fold a flat pattern up into a 3D part. Each {@link FoldRegion} folds about its
10
+ * fold line off its parent region, exactly inverting {@link unfold}: folding by an
11
+ * angle is the same rigid construction the forward authoring path performs, so this
12
+ * reuses {@link authorPart}'s bend geometry wholesale rather than re-deriving it.
13
+ *
14
+ * The result carries a fully-populated {@link SheetMetalPart} (valid solid + the
15
+ * BendFeature/FlangeFeature tree consistent with `authorPart`), so
16
+ * `unfold(fold(input))` round-trips. SEAM_CUT-style and min-radius warnings ride
17
+ * inside the Ok payload.
18
+ *
19
+ * @param input - the flat pattern input to fold.
20
+ * @returns the folded sheet metal part, or an error.
21
+ */
22
+ export declare function fold(input: FlatInput): Result<SheetMetalPart>;
23
+ /**
24
+ * {@link fold} that also surfaces the fold warnings alongside the part.
25
+ * @param input - the flat pattern input to fold.
26
+ * @returns the fold result with warnings, or an error.
27
+ */
28
+ export declare function foldWithWarnings(input: FlatInput): Result<FoldResult>;
29
+ /** How to resolve the bend rule for a bend line read out of a 2D flat pattern. */
30
+ export interface PatternToFlatInputOptions {
31
+ thickness: number;
32
+ /**
33
+ * Rule for bend line `i` (0-based, in {@link FlatPattern.bendLines} order). A flat
34
+ * pattern is pure geometry and cannot encode K-factor/inner-radius, so the rule is
35
+ * a required *input*; only the regions/sides/offsets/spans/lengths/angles are
36
+ * recovered from the 2D geometry.
37
+ */
38
+ ruleFor: (bendIndex: number) => BendRule;
39
+ material?: MaterialSpec | undefined;
40
+ }
41
+ /**
42
+ * Reconstruct a {@link FlatInput} region-tree from the *2D flat-pattern geometry*
43
+ * alone — `pattern.outline` (a closed 2D wire) and `pattern.bendLines` (each a 2D
44
+ * segment + fold angle/direction). Nothing is read from a feature tree or a 3D
45
+ * solid: the regions, sides, offsets, spans and flat lengths are all recovered by
46
+ * reading real 2D coordinates back out of the wire/edges via the public morph
47
+ * geometry readers (`getEdges`, `curveStartPoint`, `curveEndPoint`).
48
+ *
49
+ * This is the non-circular round-trip bridge: feeding `unfold(part).pattern` through
50
+ * here and into {@link fold} exercises unfold's 2D placement, this parser, and the
51
+ * forward fold geometry — a bug in any of them breaks the round-trip.
52
+ *
53
+ * Algorithm (rectilinear families — PR1 develops only axis-aligned rectangles):
54
+ * every bend line is an axis-aligned segment lying on the shared edge between a
55
+ * parent region and a child's developed strip. The developed strip width is
56
+ * `developedLength(angle, thickness, ruleFor(i))` (the supplied rule). BFS outward
57
+ * from the base region (the one anchored at the origin): a bend line on a known
58
+ * region's edge spawns a child region whose far edge is the next outward bend line
59
+ * (a grandchild) or the outline boundary; `length = far-extent − dev`, `span` =
60
+ * bend-line length, `offset`/`side` are the bend line's position on the parent edge.
61
+ *
62
+ * @param pattern - the flat pattern to convert.
63
+ * @param opts - options (thickness, ruleFor, material).
64
+ * @returns the flat input, or an error.
65
+ */
66
+ export declare function patternToFlatInput(pattern: FlatPattern, opts: PatternToFlatInputOptions): Result<FlatInput>;
67
+ /**
68
+ * Convert an authored {@link SheetMetalPart} into the {@link FlatInput} that folds
69
+ * back into it, going strictly through the 2D flat pattern: `unfold(part)` produces
70
+ * the developed wire + bend lines, and {@link patternToFlatInput} recovers the
71
+ * region tree from that 2D geometry. Only the bend *rule* is read off the part (by
72
+ * matching bend id), which a flat pattern legitimately cannot encode; every
73
+ * geometric attribute is parsed from the wire/edges. This makes the round-trip
74
+ * oracle non-circular — a bug in unfold's 2D placement or in the parser breaks it.
75
+ *
76
+ * Seam bends (closed profiles) are left unfolded by `unfold`, so the recovered part
77
+ * is the open spanning-tree shape, not the re-closed box.
78
+ *
79
+ * @param part - the sheet metal part to recover a flat input from.
80
+ * @returns the flat input, or an error.
81
+ */
82
+ export declare function partToFlatInput(part: SheetMetalPart): Result<FlatInput>;
83
+ //# sourceMappingURL=foldFns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"foldFns.d.ts","sourceRoot":"","sources":["../src/foldFns.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,MAAM,EAQZ,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EACV,QAAQ,EAER,SAAS,EACT,WAAW,EAIX,YAAY,EACZ,cAAc,EACd,iBAAiB,EAElB,MAAM,YAAY,CAAC;AAWpB,wFAAwF;AACxF,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,cAAc,CAAC;IACrB,QAAQ,EAAE,iBAAiB,EAAE,CAAC;CAC/B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,IAAI,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAAC,cAAc,CAAC,CAI7D;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAAC,UAAU,CAAC,CAyErE;AAiBD,kFAAkF;AAClF,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,OAAO,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,QAAQ,CAAC;IACzC,QAAQ,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC;CACrC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,WAAW,EACpB,IAAI,EAAE,yBAAyB,GAC9B,MAAM,CAAC,SAAS,CAAC,CA4BnB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,CAAC,SAAS,CAAC,CAwBvE"}