@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.
- package/dist/allowanceFns.d.ts +42 -0
- package/dist/allowanceFns.d.ts.map +1 -0
- package/dist/allowanceFns.js +63 -0
- package/dist/allowanceFns.js.map +1 -0
- package/dist/api.d.ts +312 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +367 -0
- package/dist/api.js.map +1 -0
- package/dist/authorFns.d.ts +118 -0
- package/dist/authorFns.d.ts.map +1 -0
- package/dist/authorFns.js +562 -0
- package/dist/authorFns.js.map +1 -0
- package/dist/bendTableFns.d.ts +87 -0
- package/dist/bendTableFns.d.ts.map +1 -0
- package/dist/bendTableFns.js +345 -0
- package/dist/bendTableFns.js.map +1 -0
- package/dist/compat.d.ts +90 -0
- package/dist/compat.d.ts.map +1 -0
- package/dist/compat.js +99 -0
- package/dist/compat.js.map +1 -0
- package/dist/contourFlangeFns.d.ts +122 -0
- package/dist/contourFlangeFns.d.ts.map +1 -0
- package/dist/contourFlangeFns.js +403 -0
- package/dist/contourFlangeFns.js.map +1 -0
- package/dist/cutoutFns.d.ts +95 -0
- package/dist/cutoutFns.d.ts.map +1 -0
- package/dist/cutoutFns.js +300 -0
- package/dist/cutoutFns.js.map +1 -0
- package/dist/dxfFns.d.ts +48 -0
- package/dist/dxfFns.d.ts.map +1 -0
- package/dist/dxfFns.js +236 -0
- package/dist/dxfFns.js.map +1 -0
- package/dist/facade.d.ts +449 -0
- package/dist/facade.d.ts.map +1 -0
- package/dist/facade.js +535 -0
- package/dist/facade.js.map +1 -0
- package/dist/featureTreeFns.d.ts +76 -0
- package/dist/featureTreeFns.d.ts.map +1 -0
- package/dist/featureTreeFns.js +190 -0
- package/dist/featureTreeFns.js.map +1 -0
- package/dist/foldFns.d.ts +83 -0
- package/dist/foldFns.d.ts.map +1 -0
- package/dist/foldFns.js +744 -0
- package/dist/foldFns.js.map +1 -0
- package/dist/foreignUnfoldFns.d.ts +62 -0
- package/dist/foreignUnfoldFns.d.ts.map +1 -0
- package/dist/foreignUnfoldFns.js +854 -0
- package/dist/foreignUnfoldFns.js.map +1 -0
- package/dist/formFns.d.ts +58 -0
- package/dist/formFns.d.ts.map +1 -0
- package/dist/formFns.js +300 -0
- package/dist/formFns.js.map +1 -0
- package/dist/hemFns.d.ts +17 -0
- package/dist/hemFns.d.ts.map +1 -0
- package/dist/hemFns.js +212 -0
- package/dist/hemFns.js.map +1 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/dist/internal.d.ts +27 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +38 -0
- package/dist/internal.js.map +1 -0
- package/dist/jogFns.d.ts +18 -0
- package/dist/jogFns.d.ts.map +1 -0
- package/dist/jogFns.js +180 -0
- package/dist/jogFns.js.map +1 -0
- package/dist/loftedFlangeFns.d.ts +23 -0
- package/dist/loftedFlangeFns.d.ts.map +1 -0
- package/dist/loftedFlangeFns.js +306 -0
- package/dist/loftedFlangeFns.js.map +1 -0
- package/dist/materials.d.ts +16 -0
- package/dist/materials.d.ts.map +1 -0
- package/dist/materials.js +44 -0
- package/dist/materials.js.map +1 -0
- package/dist/miterFns.d.ts +31 -0
- package/dist/miterFns.d.ts.map +1 -0
- package/dist/miterFns.js +128 -0
- package/dist/miterFns.js.map +1 -0
- package/dist/nestFns.d.ts +116 -0
- package/dist/nestFns.d.ts.map +1 -0
- package/dist/nestFns.js +517 -0
- package/dist/nestFns.js.map +1 -0
- package/dist/polygonFns.d.ts +144 -0
- package/dist/polygonFns.d.ts.map +1 -0
- package/dist/polygonFns.js +324 -0
- package/dist/polygonFns.js.map +1 -0
- package/dist/reliefFns.d.ts +44 -0
- package/dist/reliefFns.d.ts.map +1 -0
- package/dist/reliefFns.js +367 -0
- package/dist/reliefFns.js.map +1 -0
- package/dist/reportFns.d.ts +33 -0
- package/dist/reportFns.d.ts.map +1 -0
- package/dist/reportFns.js +71 -0
- package/dist/reportFns.js.map +1 -0
- package/dist/tabFns.d.ts +43 -0
- package/dist/tabFns.d.ts.map +1 -0
- package/dist/tabFns.js +179 -0
- package/dist/tabFns.js.map +1 -0
- package/dist/types.d.ts +629 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/unfoldFns.d.ts +120 -0
- package/dist/unfoldFns.d.ts.map +1 -0
- package/dist/unfoldFns.js +766 -0
- package/dist/unfoldFns.js.map +1 -0
- package/dist/validateFns.d.ts +18 -0
- package/dist/validateFns.d.ts.map +1 -0
- package/dist/validateFns.js +196 -0
- package/dist/validateFns.js.map +1 -0
- package/package.json +45 -0
package/dist/hemFns.js
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { ok, err, validationError } from '@faicad/faijs/brepjs-compat';
|
|
2
|
+
import { normalizeSolid } from './internal.js';
|
|
3
|
+
import { developedLength } from './allowanceFns.js';
|
|
4
|
+
import { worldFrames } from './authorFns.js';
|
|
5
|
+
import { ROOT_FLAT_ID } from './featureTreeFns.js';
|
|
6
|
+
import { buildLineLeg, buildArcBend, regionEdge, initialSegmentFrame, } from './contourFlangeFns.js';
|
|
7
|
+
/**
|
|
8
|
+
* A small clearance baked into a CLOSED hem's curl radius. A true closed hem folds
|
|
9
|
+
* the return flat against the parent with zero gap; modelling that exactly makes the
|
|
10
|
+
* curl's inner cylinder coincide with the parent face, so the boolean fuse produces
|
|
11
|
+
* a self-touching (non-manifold) solid. Inflating the inner radius by this HAIR
|
|
12
|
+
* leaves a sub-thickness air gap that keeps the fused solid valid while still
|
|
13
|
+
* reading as a closed hem. Documented so a reader understands the gap is intentional.
|
|
14
|
+
*/
|
|
15
|
+
const CLOSED_HEM_HAIR = 0.05;
|
|
16
|
+
/** Sub-arc cap (deg): the bend-patch wedge degenerates at ≥180°, so curls split. */
|
|
17
|
+
const MAX_SUBARC_DEG = 120;
|
|
18
|
+
/**
|
|
19
|
+
* Author a hem: fold a region edge back ~180°+ onto its parent flat, then run a
|
|
20
|
+
* short return leg. Built on the contour-flange segment chainer — the curl is one
|
|
21
|
+
* or more ≤120° sub-arcs (each a recorded `hem::<id>::<n>` bend) followed by a flat
|
|
22
|
+
* return — so the development is EXACT: Σ curl bend allowances (via the table-aware
|
|
23
|
+
* {@link developedLength}) + the return length, laid out straight past the edge.
|
|
24
|
+
* Four `type`s set the curl/gap geometry (see {@link HemSpec}). Construction stays
|
|
25
|
+
* on the public, OCCT-WASM-safe API; the result is guarded to a valid single solid.
|
|
26
|
+
*
|
|
27
|
+
* @param part - the sheet metal part to attach a hem to.
|
|
28
|
+
* @param spec - the hem specification.
|
|
29
|
+
* @returns the updated part with the hem feature recorded, or an error.
|
|
30
|
+
*/
|
|
31
|
+
export function hem(part, spec) {
|
|
32
|
+
if (part.solid === undefined) {
|
|
33
|
+
return err(validationError('NO_SOLID', `part has no solid to attach hem '${hemId(spec)}'`));
|
|
34
|
+
}
|
|
35
|
+
const id = hemId(spec);
|
|
36
|
+
if (id.includes('::')) {
|
|
37
|
+
return err(validationError('INVALID_HEM_ID', `hem id must not contain '::', got '${id}'`));
|
|
38
|
+
}
|
|
39
|
+
for (const existing of part.hems ?? []) {
|
|
40
|
+
if (existing.id === id) {
|
|
41
|
+
return err(validationError('DUPLICATE_HEM', `duplicate hem id '${id}'`));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const thickness = part.thickness;
|
|
45
|
+
const planResult = planHem(spec, thickness);
|
|
46
|
+
if (!planResult.ok)
|
|
47
|
+
return planResult;
|
|
48
|
+
const plan = planResult.value;
|
|
49
|
+
const framesResult = worldFrames(part);
|
|
50
|
+
if (!framesResult.ok)
|
|
51
|
+
return framesResult;
|
|
52
|
+
const regionId = resolveRegion(spec.region);
|
|
53
|
+
const regionFrame = framesResult.value.get(regionId);
|
|
54
|
+
if (regionFrame === undefined) {
|
|
55
|
+
return err(validationError('UNKNOWN_REGION', `hem '${id}' references unknown region '${spec.region}'`));
|
|
56
|
+
}
|
|
57
|
+
const edge = regionEdge(regionFrame, spec.side);
|
|
58
|
+
const offset = spec.offset ?? 0;
|
|
59
|
+
const span = spec.width ?? edge.length;
|
|
60
|
+
if (!Number.isFinite(offset) || offset < -1e-9) {
|
|
61
|
+
return err(validationError('INVALID_OFFSET', `hem '${id}' offset must be non-negative`));
|
|
62
|
+
}
|
|
63
|
+
if (!Number.isFinite(span) || span <= 0) {
|
|
64
|
+
return err(validationError('INVALID_HEM_WIDTH', `hem '${id}' width must be positive`));
|
|
65
|
+
}
|
|
66
|
+
if (offset + span > edge.length + 1e-6) {
|
|
67
|
+
return err(validationError('HEM_OUT_OF_BOUNDS', `hem '${id}' [${offset}, ${offset + span}] exceeds region edge length ${edge.length}`));
|
|
68
|
+
}
|
|
69
|
+
const rule = spec.rule ?? { innerRadius: plan.radius, kFactor: 0.44 };
|
|
70
|
+
const subAngles = splitAngle(plan.curlDeg);
|
|
71
|
+
// The curl is split into ≤120° sub-arcs only because the bend-patch wedge
|
|
72
|
+
// degenerates at ≥180°; the DEVELOPMENT is one physical curl. Resolve the full
|
|
73
|
+
// curl angle's allowance as a single table/formula query, then apportion it to
|
|
74
|
+
// the sub-arcs by angle. Querying each sub-arc separately would over-count a
|
|
75
|
+
// table sparse in angle (each sub-angle clamps up to the nearest tabulated row).
|
|
76
|
+
const fullDevResult = developedLength(plan.curlDeg, thickness, { ...rule, innerRadius: plan.radius });
|
|
77
|
+
if (!fullDevResult.ok)
|
|
78
|
+
return fullDevResult;
|
|
79
|
+
const devPerDeg = plan.curlDeg > 0 ? fullDevResult.value / plan.curlDeg : 0;
|
|
80
|
+
let frame = initialSegmentFrame(edge, regionFrame.n, offset);
|
|
81
|
+
let solid = part.solid;
|
|
82
|
+
const bends = [];
|
|
83
|
+
const segments = [];
|
|
84
|
+
const subBends = [];
|
|
85
|
+
let devTotal = 0;
|
|
86
|
+
for (let i = 0; i < subAngles.length; i += 1) {
|
|
87
|
+
const subDeg = subAngles[i];
|
|
88
|
+
if (subDeg === undefined)
|
|
89
|
+
continue;
|
|
90
|
+
const built = buildArcBend(solid, frame, span, thickness, {
|
|
91
|
+
kind: 'arc',
|
|
92
|
+
radius: plan.radius,
|
|
93
|
+
angleDeg: subDeg,
|
|
94
|
+
direction: 'up',
|
|
95
|
+
});
|
|
96
|
+
if (!built.ok)
|
|
97
|
+
return built;
|
|
98
|
+
solid = built.value.solid;
|
|
99
|
+
frame = built.value.frame;
|
|
100
|
+
const dev = devPerDeg * subDeg;
|
|
101
|
+
const bendId = `hem::${id}::${i}`;
|
|
102
|
+
subBends.push(bendId);
|
|
103
|
+
bends.push({
|
|
104
|
+
id: bendId,
|
|
105
|
+
axisOrigin: [built.value.axisOrigin[0], built.value.axisOrigin[1], built.value.axisOrigin[2]],
|
|
106
|
+
axisDir: [edge.dir[0], edge.dir[1], edge.dir[2]],
|
|
107
|
+
angleDeg: subDeg,
|
|
108
|
+
direction: 'up',
|
|
109
|
+
rule: { ...rule, innerRadius: plan.radius },
|
|
110
|
+
});
|
|
111
|
+
segments.push({ kind: 'arc', dev, angleDeg: subDeg, direction: 'up', bendId });
|
|
112
|
+
devTotal += dev;
|
|
113
|
+
}
|
|
114
|
+
if (plan.returnLength > 0) {
|
|
115
|
+
const built = buildLineLeg(solid, frame, span, thickness, plan.returnLength);
|
|
116
|
+
if (!built.ok)
|
|
117
|
+
return built;
|
|
118
|
+
solid = built.value.solid;
|
|
119
|
+
segments.push({ kind: 'line', dev: plan.returnLength });
|
|
120
|
+
devTotal += plan.returnLength;
|
|
121
|
+
}
|
|
122
|
+
const feature = {
|
|
123
|
+
id,
|
|
124
|
+
type: spec.type,
|
|
125
|
+
region: regionId,
|
|
126
|
+
side: spec.side,
|
|
127
|
+
offset,
|
|
128
|
+
span,
|
|
129
|
+
returnLength: plan.returnLength,
|
|
130
|
+
developedLength: devTotal,
|
|
131
|
+
subBends,
|
|
132
|
+
segments,
|
|
133
|
+
};
|
|
134
|
+
return ok({
|
|
135
|
+
...part,
|
|
136
|
+
solid: normalizeSolid(solid),
|
|
137
|
+
bends: [...part.bends, ...bends],
|
|
138
|
+
hems: [...(part.hems ?? []), feature],
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
/** The hem's id: an explicit `spec.id`, else a deterministic region/side/type key. */
|
|
142
|
+
function hemId(spec) {
|
|
143
|
+
return spec.id ?? `hem-${spec.region}-${spec.side}-${spec.type}`;
|
|
144
|
+
}
|
|
145
|
+
function resolveRegion(region) {
|
|
146
|
+
return region === 'base' || region === 'face-0' ? ROOT_FLAT_ID : region;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Resolve a {@link HemSpec} into its curl angle, radius and return length. The
|
|
150
|
+
* radius defaults to one thickness (the tightest practical hem). A closed hem
|
|
151
|
+
* inflates the radius by {@link CLOSED_HEM_HAIR} so the fused solid stays valid;
|
|
152
|
+
* an open hem widens the radius to span the requested gap; teardrop/rolled set a
|
|
153
|
+
* larger curl angle.
|
|
154
|
+
*/
|
|
155
|
+
function planHem(spec, thickness) {
|
|
156
|
+
if (!Number.isFinite(thickness) || thickness <= 0) {
|
|
157
|
+
return err(validationError('INVALID_THICKNESS', `hem thickness must be positive, got ${thickness}`));
|
|
158
|
+
}
|
|
159
|
+
if (spec.radius !== undefined && (!Number.isFinite(spec.radius) || spec.radius < 0)) {
|
|
160
|
+
return err(validationError('INVALID_HEM_RADIUS', `hem radius must be non-negative, got ${spec.radius}`));
|
|
161
|
+
}
|
|
162
|
+
// Inner-radius default differs by type: a closed hem folds essentially flat
|
|
163
|
+
// (radius ≈ 0, just the HAIR clearance) so it is actually closed; teardrop/rolled
|
|
164
|
+
// default to one thickness. Open derives its radius from the requested gap below.
|
|
165
|
+
const baseRadius = spec.radius ?? thickness;
|
|
166
|
+
switch (spec.type) {
|
|
167
|
+
case 'closed': {
|
|
168
|
+
const returnLength = spec.length ?? 0;
|
|
169
|
+
if (!Number.isFinite(returnLength) || returnLength <= 0) {
|
|
170
|
+
return err(validationError('INVALID_HEM_LENGTH', `closed hem requires a positive return length, got ${spec.length}`));
|
|
171
|
+
}
|
|
172
|
+
if (returnLength < thickness) {
|
|
173
|
+
return err(validationError('HEM_LENGTH_TOO_SHORT', `closed hem return length ${returnLength} must be ≥ thickness ${thickness}`));
|
|
174
|
+
}
|
|
175
|
+
// Default to a tight (≈0) radius so the return folds flat against the parent
|
|
176
|
+
// (an actually-closed hem); the HAIR keeps the coincident-face fuse valid.
|
|
177
|
+
return ok({ curlDeg: 180, radius: (spec.radius ?? 0) + CLOSED_HEM_HAIR, returnLength });
|
|
178
|
+
}
|
|
179
|
+
case 'open': {
|
|
180
|
+
const returnLength = spec.length ?? 0;
|
|
181
|
+
if (!Number.isFinite(returnLength) || returnLength <= 0) {
|
|
182
|
+
return err(validationError('INVALID_HEM_LENGTH', `open hem requires a positive return length, got ${spec.length}`));
|
|
183
|
+
}
|
|
184
|
+
// `gap` is the physical clear distance between the return and the parent. A
|
|
185
|
+
// 180° fold at inner radius r lands the return 2r away, so the radius is gap/2.
|
|
186
|
+
const gap = spec.gap ?? thickness;
|
|
187
|
+
if (!Number.isFinite(gap) || gap <= 0) {
|
|
188
|
+
return err(validationError('INVALID_HEM_GAP', `open hem gap must be positive, got ${gap}`));
|
|
189
|
+
}
|
|
190
|
+
return ok({ curlDeg: 180, radius: gap / 2, returnLength });
|
|
191
|
+
}
|
|
192
|
+
case 'teardrop': {
|
|
193
|
+
const returnLength = spec.length ?? 0;
|
|
194
|
+
if (!Number.isFinite(returnLength) || returnLength <= 0) {
|
|
195
|
+
return err(validationError('INVALID_HEM_LENGTH', `teardrop hem requires a positive return length, got ${spec.length}`));
|
|
196
|
+
}
|
|
197
|
+
return ok({ curlDeg: 210, radius: baseRadius, returnLength });
|
|
198
|
+
}
|
|
199
|
+
case 'rolled':
|
|
200
|
+
// A full circular roll (curled / safe edge): no flat return.
|
|
201
|
+
return ok({ curlDeg: 270, radius: baseRadius, returnLength: 0 });
|
|
202
|
+
default:
|
|
203
|
+
return spec.type;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/** Split a curl angle into ≤{@link MAX_SUBARC_DEG} equal sub-arcs (the wedge cap). */
|
|
207
|
+
function splitAngle(totalDeg) {
|
|
208
|
+
const n = Math.max(1, Math.ceil(totalDeg / MAX_SUBARC_DEG));
|
|
209
|
+
const per = totalDeg / n;
|
|
210
|
+
return Array.from({ length: n }, () => per);
|
|
211
|
+
}
|
|
212
|
+
//# sourceMappingURL=hemFns.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hemFns.js","sourceRoot":"","sources":["../src/hemFns.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Result, type Solid, ok, err, validationError } from '@faicad/faijs/brepjs-compat';\nimport type {\n BendFeature,\n BendRule,\n HemFeature,\n HemSpec,\n SheetMetalPart,\n} from './types.js';\nimport { normalizeSolid } from './internal.js';\nimport { developedLength } from './allowanceFns.js';\nimport { worldFrames } from './authorFns.js';\nimport { ROOT_FLAT_ID } from './featureTreeFns.js';\nimport {\n type SegmentFrame,\n buildLineLeg,\n buildArcBend,\n regionEdge,\n initialSegmentFrame,\n} from './contourFlangeFns.js';\n\n/**\n * A small clearance baked into a CLOSED hem's curl radius. A true closed hem folds\n * the return flat against the parent with zero gap; modelling that exactly makes the\n * curl's inner cylinder coincide with the parent face, so the boolean fuse produces\n * a self-touching (non-manifold) solid. Inflating the inner radius by this HAIR\n * leaves a sub-thickness air gap that keeps the fused solid valid while still\n * reading as a closed hem. Documented so a reader understands the gap is intentional.\n */\nconst CLOSED_HEM_HAIR = 0.05;\n\n/** Sub-arc cap (deg): the bend-patch wedge degenerates at ≥180°, so curls split. */\nconst MAX_SUBARC_DEG = 120;\n\ninterface HemPlan {\n /** Total curl angle in degrees (≈180 for closed/open, >180 teardrop, ~270 rolled). */\n curlDeg: number;\n /** Inner bend radius used for the curl. */\n radius: number;\n /** Flat return-leg length past the curl (0 for rolled). */\n returnLength: number;\n}\n\n/**\n * Author a hem: fold a region edge back ~180°+ onto its parent flat, then run a\n * short return leg. Built on the contour-flange segment chainer — the curl is one\n * or more ≤120° sub-arcs (each a recorded `hem::<id>::<n>` bend) followed by a flat\n * return — so the development is EXACT: Σ curl bend allowances (via the table-aware\n * {@link developedLength}) + the return length, laid out straight past the edge.\n * Four `type`s set the curl/gap geometry (see {@link HemSpec}). Construction stays\n * on the public, OCCT-WASM-safe API; the result is guarded to a valid single solid.\n *\n * @param part - the sheet metal part to attach a hem to.\n * @param spec - the hem specification.\n * @returns the updated part with the hem feature recorded, or an error.\n */\nexport function hem(part: SheetMetalPart, spec: HemSpec): Result<SheetMetalPart> {\n if (part.solid === undefined) {\n return err(validationError('NO_SOLID', `part has no solid to attach hem '${hemId(spec)}'`));\n }\n const id = hemId(spec);\n if (id.includes('::')) {\n return err(validationError('INVALID_HEM_ID', `hem id must not contain '::', got '${id}'`));\n }\n for (const existing of part.hems ?? []) {\n if (existing.id === id) {\n return err(validationError('DUPLICATE_HEM', `duplicate hem id '${id}'`));\n }\n }\n\n const thickness = part.thickness;\n const planResult = planHem(spec, thickness);\n if (!planResult.ok) return planResult;\n const plan = planResult.value;\n\n const framesResult = worldFrames(part);\n if (!framesResult.ok) return framesResult;\n const regionId = resolveRegion(spec.region);\n const regionFrame = framesResult.value.get(regionId);\n if (regionFrame === undefined) {\n return err(validationError('UNKNOWN_REGION', `hem '${id}' references unknown region '${spec.region}'`));\n }\n\n const edge = regionEdge(regionFrame, spec.side);\n const offset = spec.offset ?? 0;\n const span = spec.width ?? edge.length;\n if (!Number.isFinite(offset) || offset < -1e-9) {\n return err(validationError('INVALID_OFFSET', `hem '${id}' offset must be non-negative`));\n }\n if (!Number.isFinite(span) || span <= 0) {\n return err(validationError('INVALID_HEM_WIDTH', `hem '${id}' width must be positive`));\n }\n if (offset + span > edge.length + 1e-6) {\n return err(\n validationError('HEM_OUT_OF_BOUNDS', `hem '${id}' [${offset}, ${offset + span}] exceeds region edge length ${edge.length}`)\n );\n }\n\n const rule: BendRule = spec.rule ?? { innerRadius: plan.radius, kFactor: 0.44 };\n const subAngles = splitAngle(plan.curlDeg);\n\n // The curl is split into ≤120° sub-arcs only because the bend-patch wedge\n // degenerates at ≥180°; the DEVELOPMENT is one physical curl. Resolve the full\n // curl angle's allowance as a single table/formula query, then apportion it to\n // the sub-arcs by angle. Querying each sub-arc separately would over-count a\n // table sparse in angle (each sub-angle clamps up to the nearest tabulated row).\n const fullDevResult = developedLength(plan.curlDeg, thickness, { ...rule, innerRadius: plan.radius });\n if (!fullDevResult.ok) return fullDevResult;\n const devPerDeg = plan.curlDeg > 0 ? fullDevResult.value / plan.curlDeg : 0;\n\n let frame: SegmentFrame = initialSegmentFrame(edge, regionFrame.n, offset);\n let solid: Solid = part.solid;\n const bends: BendFeature[] = [];\n const segments: HemFeature['segments'] = [];\n const subBends: string[] = [];\n let devTotal = 0;\n\n for (let i = 0; i < subAngles.length; i += 1) {\n const subDeg = subAngles[i];\n if (subDeg === undefined) continue;\n const built = buildArcBend(solid, frame, span, thickness, {\n kind: 'arc',\n radius: plan.radius,\n angleDeg: subDeg,\n direction: 'up',\n });\n if (!built.ok) return built;\n solid = built.value.solid;\n frame = built.value.frame;\n\n const dev = devPerDeg * subDeg;\n\n const bendId = `hem::${id}::${i}`;\n subBends.push(bendId);\n bends.push({\n id: bendId,\n axisOrigin: [built.value.axisOrigin[0], built.value.axisOrigin[1], built.value.axisOrigin[2]],\n axisDir: [edge.dir[0], edge.dir[1], edge.dir[2]],\n angleDeg: subDeg,\n direction: 'up',\n rule: { ...rule, innerRadius: plan.radius },\n });\n segments.push({ kind: 'arc', dev, angleDeg: subDeg, direction: 'up', bendId });\n devTotal += dev;\n }\n\n if (plan.returnLength > 0) {\n const built = buildLineLeg(solid, frame, span, thickness, plan.returnLength);\n if (!built.ok) return built;\n solid = built.value.solid;\n segments.push({ kind: 'line', dev: plan.returnLength });\n devTotal += plan.returnLength;\n }\n\n const feature: HemFeature = {\n id,\n type: spec.type,\n region: regionId,\n side: spec.side,\n offset,\n span,\n returnLength: plan.returnLength,\n developedLength: devTotal,\n subBends,\n segments,\n };\n\n return ok({\n ...part,\n solid: normalizeSolid(solid),\n bends: [...part.bends, ...bends],\n hems: [...(part.hems ?? []), feature],\n });\n}\n\n/** The hem's id: an explicit `spec.id`, else a deterministic region/side/type key. */\nfunction hemId(spec: HemSpec): string {\n return spec.id ?? `hem-${spec.region}-${spec.side}-${spec.type}`;\n}\n\nfunction resolveRegion(region: string): string {\n return region === 'base' || region === 'face-0' ? ROOT_FLAT_ID : region;\n}\n\n/**\n * Resolve a {@link HemSpec} into its curl angle, radius and return length. The\n * radius defaults to one thickness (the tightest practical hem). A closed hem\n * inflates the radius by {@link CLOSED_HEM_HAIR} so the fused solid stays valid;\n * an open hem widens the radius to span the requested gap; teardrop/rolled set a\n * larger curl angle.\n */\nfunction planHem(spec: HemSpec, thickness: number): Result<HemPlan> {\n if (!Number.isFinite(thickness) || thickness <= 0) {\n return err(validationError('INVALID_THICKNESS', `hem thickness must be positive, got ${thickness}`));\n }\n if (spec.radius !== undefined && (!Number.isFinite(spec.radius) || spec.radius < 0)) {\n return err(validationError('INVALID_HEM_RADIUS', `hem radius must be non-negative, got ${spec.radius}`));\n }\n // Inner-radius default differs by type: a closed hem folds essentially flat\n // (radius ≈ 0, just the HAIR clearance) so it is actually closed; teardrop/rolled\n // default to one thickness. Open derives its radius from the requested gap below.\n const baseRadius = spec.radius ?? thickness;\n\n switch (spec.type) {\n case 'closed': {\n const returnLength = spec.length ?? 0;\n if (!Number.isFinite(returnLength) || returnLength <= 0) {\n return err(\n validationError('INVALID_HEM_LENGTH', `closed hem requires a positive return length, got ${spec.length}`)\n );\n }\n if (returnLength < thickness) {\n return err(\n validationError('HEM_LENGTH_TOO_SHORT', `closed hem return length ${returnLength} must be ≥ thickness ${thickness}`)\n );\n }\n // Default to a tight (≈0) radius so the return folds flat against the parent\n // (an actually-closed hem); the HAIR keeps the coincident-face fuse valid.\n return ok({ curlDeg: 180, radius: (spec.radius ?? 0) + CLOSED_HEM_HAIR, returnLength });\n }\n case 'open': {\n const returnLength = spec.length ?? 0;\n if (!Number.isFinite(returnLength) || returnLength <= 0) {\n return err(\n validationError('INVALID_HEM_LENGTH', `open hem requires a positive return length, got ${spec.length}`)\n );\n }\n // `gap` is the physical clear distance between the return and the parent. A\n // 180° fold at inner radius r lands the return 2r away, so the radius is gap/2.\n const gap = spec.gap ?? thickness;\n if (!Number.isFinite(gap) || gap <= 0) {\n return err(validationError('INVALID_HEM_GAP', `open hem gap must be positive, got ${gap}`));\n }\n return ok({ curlDeg: 180, radius: gap / 2, returnLength });\n }\n case 'teardrop': {\n const returnLength = spec.length ?? 0;\n if (!Number.isFinite(returnLength) || returnLength <= 0) {\n return err(\n validationError('INVALID_HEM_LENGTH', `teardrop hem requires a positive return length, got ${spec.length}`)\n );\n }\n return ok({ curlDeg: 210, radius: baseRadius, returnLength });\n }\n case 'rolled':\n // A full circular roll (curled / safe edge): no flat return.\n return ok({ curlDeg: 270, radius: baseRadius, returnLength: 0 });\n default:\n return spec.type satisfies never;\n }\n}\n\n/** Split a curl angle into ≤{@link MAX_SUBARC_DEG} equal sub-arcs (the wedge cap). */\nfunction splitAngle(totalDeg: number): number[] {\n const n = Math.max(1, Math.ceil(totalDeg / MAX_SUBARC_DEG));\n const per = totalDeg / n;\n return Array.from({ length: n }, () => per);\n}\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @faicad/sheetmetal — sheet-metal CAD domain (L5 third-party library)
|
|
3
|
+
*
|
|
4
|
+
* P9 第二/三批:3D author/unfold 全功能面(compat shim D12 桥接 compat
|
|
5
|
+
* L1/L2)——只依赖 L3 faijs API 面 + compat 桥接点。导出面对齐上游
|
|
6
|
+
* morph-sheetmetal index.ts(与上游逐项一致)。
|
|
7
|
+
*/
|
|
8
|
+
export { DEFAULT_K_FACTOR, STEEL_GAUGES, ALUMINUM_GAUGES, MATERIALS, getMaterial, } from './materials.js';
|
|
9
|
+
export { bendAllowance, developedLength, neutralRadius } from './allowanceFns.js';
|
|
10
|
+
export { registerBendTable, getBendTable, resolveBendAllowance } from './bendTableFns.js';
|
|
11
|
+
export type { BendTable, BendTableRow } from './bendTableFns.js';
|
|
12
|
+
export { ROOT_FLAT_ID, buildFeatureGraph, buildFeatureTree, featureTree, } from './featureTreeFns.js';
|
|
13
|
+
export type { FlatNode, BendEdge, FeatureGraph, TreeBend, SeamCut, FeatureTree, } from './featureTreeFns.js';
|
|
14
|
+
export { unfold } from './unfoldFns.js';
|
|
15
|
+
export { unfoldForeignSolid, fitCylinder } from './foreignUnfoldFns.js';
|
|
16
|
+
export type { FittedCylinder } from './foreignUnfoldFns.js';
|
|
17
|
+
export { fold, foldWithWarnings, patternToFlatInput, partToFlatInput } from './foldFns.js';
|
|
18
|
+
export type { FoldResult, PatternToFlatInputOptions } from './foldFns.js';
|
|
19
|
+
export { authorPart, baseEdgeRef } from './authorFns.js';
|
|
20
|
+
export type { AuthorSpec, BaseFlatSpec, FlangeSpec, FlangeSide, SeamSpec, } from './authorFns.js';
|
|
21
|
+
export { miterCut, autoMiterCorner } from './miterFns.js';
|
|
22
|
+
export type { MiterPlane } from './miterFns.js';
|
|
23
|
+
export { addBendRelief, autoBendReliefs, cornerRelief } from './reliefFns.js';
|
|
24
|
+
export { addCutout, addHole, addSlot, addPolygonCutout } from './cutoutFns.js';
|
|
25
|
+
export { addTab, tabAndSlot } from './tabFns.js';
|
|
26
|
+
export type { SlotPlacement } from './tabFns.js';
|
|
27
|
+
export { addForm, louver, emboss } from './formFns.js';
|
|
28
|
+
export { authorContourFlange } from './contourFlangeFns.js';
|
|
29
|
+
export { authorLoftedFlange } from './loftedFlangeFns.js';
|
|
30
|
+
export { hem } from './hemFns.js';
|
|
31
|
+
export { jog } from './jogFns.js';
|
|
32
|
+
export { flatPatternToDXF, multiPatternToDXF } from './dxfFns.js';
|
|
33
|
+
export type { DxfOptions, PlacedPattern, Transform2 } from './dxfFns.js';
|
|
34
|
+
export { patternBbox } from './nestFns.js';
|
|
35
|
+
export type { NestOptions, NestResult, NestSheet, Placement, SheetSpec, Bbox, } from './nestFns.js';
|
|
36
|
+
export { wireToPolygon, flatPatternToPolylines, polygonsOverlap, polygonsOverlapWithClearance, segmentsIntersect, pointInPolygon, transformPolygon, polygonBounds, signedArea, } from './polygonFns.js';
|
|
37
|
+
export type { Polygon, Pt2, FlatPatternPolylines, FlatPatternBendLine, } from './polygonFns.js';
|
|
38
|
+
export { buildReport, reportFromUnfold, reportToJSON } from './reportFns.js';
|
|
39
|
+
export { validatePart } from './validateFns.js';
|
|
40
|
+
export { author, unfold as unfoldPart, unfoldSolid, fold as foldPart, miter, miterCorner, bendRelief, autoReliefs, relieveCorner, toDXF, report, reportFrom, reportJSON, validate, allowance, developed, addBendTable, bendTable, resolveAllowance, contourFlange, loftedFlange, nest, nestToDXF, solidOf, } from './api.js';
|
|
41
|
+
export { sheetMetal, fromPart, foldFlat, fromSolid, SheetMetalError, SheetMetalBuilder, SheetMetalPartHandle, ForeignSolidHandle, } from './facade.js';
|
|
42
|
+
export type { EdgeRef, FlatSide, BendRule, MaterialSpec, MiterSpec, BendFeature, FlangeFeature, CornerMiter, ReliefSpec, ReliefFeature, CutoutSpec, CutoutFeature, TabSpec, TabFeature, FormSpec, FormFeature, ProfileSegment, ContourFlangeSpec, ContourFlangeFeature, LoftedFlangeSpec, LoftedFlangeFeature, HemSpec, HemFeature, JogSpec, JogFeature, SheetMetalPart, FlatPattern, FlatInput, FoldRegion, BendReport, SheetMetalWarning, UnfoldResult, } from './types.js';
|
|
43
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @faicad/sheetmetal — sheet-metal CAD domain (L5 third-party library)
|
|
3
|
+
*
|
|
4
|
+
* P9 第二/三批:3D author/unfold 全功能面(compat shim D12 桥接 compat
|
|
5
|
+
* L1/L2)——只依赖 L3 faijs API 面 + compat 桥接点。导出面对齐上游
|
|
6
|
+
* morph-sheetmetal index.ts(与上游逐项一致)。
|
|
7
|
+
*/
|
|
8
|
+
export { DEFAULT_K_FACTOR, STEEL_GAUGES, ALUMINUM_GAUGES, MATERIALS, getMaterial, } from './materials.js';
|
|
9
|
+
export { bendAllowance, developedLength, neutralRadius } from './allowanceFns.js';
|
|
10
|
+
export { registerBendTable, getBendTable, resolveBendAllowance } from './bendTableFns.js';
|
|
11
|
+
export { ROOT_FLAT_ID, buildFeatureGraph, buildFeatureTree, featureTree, } from './featureTreeFns.js';
|
|
12
|
+
export { unfold } from './unfoldFns.js';
|
|
13
|
+
export { unfoldForeignSolid, fitCylinder } from './foreignUnfoldFns.js';
|
|
14
|
+
export { fold, foldWithWarnings, patternToFlatInput, partToFlatInput } from './foldFns.js';
|
|
15
|
+
export { authorPart, baseEdgeRef } from './authorFns.js';
|
|
16
|
+
export { miterCut, autoMiterCorner } from './miterFns.js';
|
|
17
|
+
export { addBendRelief, autoBendReliefs, cornerRelief } from './reliefFns.js';
|
|
18
|
+
export { addCutout, addHole, addSlot, addPolygonCutout } from './cutoutFns.js';
|
|
19
|
+
export { addTab, tabAndSlot } from './tabFns.js';
|
|
20
|
+
export { addForm, louver, emboss } from './formFns.js';
|
|
21
|
+
export { authorContourFlange } from './contourFlangeFns.js';
|
|
22
|
+
export { authorLoftedFlange } from './loftedFlangeFns.js';
|
|
23
|
+
export { hem } from './hemFns.js';
|
|
24
|
+
export { jog } from './jogFns.js';
|
|
25
|
+
export { flatPatternToDXF, multiPatternToDXF } from './dxfFns.js';
|
|
26
|
+
export { patternBbox } from './nestFns.js';
|
|
27
|
+
export { wireToPolygon, flatPatternToPolylines, polygonsOverlap, polygonsOverlapWithClearance, segmentsIntersect, pointInPolygon, transformPolygon, polygonBounds, signedArea, } from './polygonFns.js';
|
|
28
|
+
export { buildReport, reportFromUnfold, reportToJSON } from './reportFns.js';
|
|
29
|
+
export { validatePart } from './validateFns.js';
|
|
30
|
+
export { author, unfold as unfoldPart, unfoldSolid, fold as foldPart, miter, miterCorner, bendRelief, autoReliefs, relieveCorner, toDXF, report, reportFrom, reportJSON, validate, allowance, developed, addBendTable, bendTable, resolveAllowance, contourFlange, loftedFlange, nest, nestToDXF, solidOf, } from './api.js';
|
|
31
|
+
export { sheetMetal, fromPart, foldFlat, fromSolid, SheetMetalError, SheetMetalBuilder, SheetMetalPartHandle, ForeignSolidHandle, } from './facade.js';
|
|
32
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,eAAe,EACf,SAAS,EACT,WAAW,GACZ,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElF,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAG1F,OAAO,EACL,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,EAChB,WAAW,GACZ,MAAM,qBAAqB,CAAC;AAU7B,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAExC,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAGxE,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAG3F,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AASzD,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAG1D,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9E,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAE/E,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGjD,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAElC,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAGlE,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAU3C,OAAO,EACL,aAAa,EACb,sBAAsB,EACtB,eAAe,EACf,4BAA4B,EAC5B,iBAAiB,EACjB,cAAc,EACd,gBAAgB,EAChB,aAAa,EACb,UAAU,GACX,MAAM,iBAAiB,CAAC;AAQzB,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE7E,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEhD,OAAO,EACL,MAAM,EACN,MAAM,IAAI,UAAU,EACpB,WAAW,EACX,IAAI,IAAI,QAAQ,EAChB,KAAK,EACL,WAAW,EACX,UAAU,EACV,WAAW,EACX,aAAa,EACb,KAAK,EACL,MAAM,EACN,UAAU,EACV,UAAU,EACV,QAAQ,EACR,SAAS,EACT,SAAS,EACT,YAAY,EACZ,SAAS,EACT,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,IAAI,EACJ,SAAS,EACT,OAAO,GACR,MAAM,UAAU,CAAC;AAElB,OAAO,EACL,UAAU,EACV,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,eAAe,EACf,iBAAiB,EACjB,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,aAAa,CAAC","sourcesContent":["/**\n * @faicad/sheetmetal — sheet-metal CAD domain (L5 third-party library)\n *\n * P9 第二/三批:3D author/unfold 全功能面(compat shim D12 桥接 compat\n * L1/L2)——只依赖 L3 faijs API 面 + compat 桥接点。导出面对齐上游\n * morph-sheetmetal index.ts(与上游逐项一致)。\n */\n\nexport {\n DEFAULT_K_FACTOR,\n STEEL_GAUGES,\n ALUMINUM_GAUGES,\n MATERIALS,\n getMaterial,\n} from './materials.js';\n\nexport { bendAllowance, developedLength, neutralRadius } from './allowanceFns.js';\n\nexport { registerBendTable, getBendTable, resolveBendAllowance } from './bendTableFns.js';\nexport type { BendTable, BendTableRow } from './bendTableFns.js';\n\nexport {\n ROOT_FLAT_ID,\n buildFeatureGraph,\n buildFeatureTree,\n featureTree,\n} from './featureTreeFns.js';\nexport type {\n FlatNode,\n BendEdge,\n FeatureGraph,\n TreeBend,\n SeamCut,\n FeatureTree,\n} from './featureTreeFns.js';\n\nexport { unfold } from './unfoldFns.js';\n\nexport { unfoldForeignSolid, fitCylinder } from './foreignUnfoldFns.js';\nexport type { FittedCylinder } from './foreignUnfoldFns.js';\n\nexport { fold, foldWithWarnings, patternToFlatInput, partToFlatInput } from './foldFns.js';\nexport type { FoldResult, PatternToFlatInputOptions } from './foldFns.js';\n\nexport { authorPart, baseEdgeRef } from './authorFns.js';\nexport type {\n AuthorSpec,\n BaseFlatSpec,\n FlangeSpec,\n FlangeSide,\n SeamSpec,\n} from './authorFns.js';\n\nexport { miterCut, autoMiterCorner } from './miterFns.js';\nexport type { MiterPlane } from './miterFns.js';\n\nexport { addBendRelief, autoBendReliefs, cornerRelief } from './reliefFns.js';\n\nexport { addCutout, addHole, addSlot, addPolygonCutout } from './cutoutFns.js';\n\nexport { addTab, tabAndSlot } from './tabFns.js';\nexport type { SlotPlacement } from './tabFns.js';\n\nexport { addForm, louver, emboss } from './formFns.js';\n\nexport { authorContourFlange } from './contourFlangeFns.js';\nexport { authorLoftedFlange } from './loftedFlangeFns.js';\n\nexport { hem } from './hemFns.js';\nexport { jog } from './jogFns.js';\n\nexport { flatPatternToDXF, multiPatternToDXF } from './dxfFns.js';\nexport type { DxfOptions, PlacedPattern, Transform2 } from './dxfFns.js';\n\nexport { patternBbox } from './nestFns.js';\nexport type {\n NestOptions,\n NestResult,\n NestSheet,\n Placement,\n SheetSpec,\n Bbox,\n} from './nestFns.js';\n\nexport {\n wireToPolygon,\n flatPatternToPolylines,\n polygonsOverlap,\n polygonsOverlapWithClearance,\n segmentsIntersect,\n pointInPolygon,\n transformPolygon,\n polygonBounds,\n signedArea,\n} from './polygonFns.js';\nexport type {\n Polygon,\n Pt2,\n FlatPatternPolylines,\n FlatPatternBendLine,\n} from './polygonFns.js';\n\nexport { buildReport, reportFromUnfold, reportToJSON } from './reportFns.js';\n\nexport { validatePart } from './validateFns.js';\n\nexport {\n author,\n unfold as unfoldPart,\n unfoldSolid,\n fold as foldPart,\n miter,\n miterCorner,\n bendRelief,\n autoReliefs,\n relieveCorner,\n toDXF,\n report,\n reportFrom,\n reportJSON,\n validate,\n allowance,\n developed,\n addBendTable,\n bendTable,\n resolveAllowance,\n contourFlange,\n loftedFlange,\n nest,\n nestToDXF,\n solidOf,\n} from './api.js';\n\nexport {\n sheetMetal,\n fromPart,\n foldFlat,\n fromSolid,\n SheetMetalError,\n SheetMetalBuilder,\n SheetMetalPartHandle,\n ForeignSolidHandle,\n} from './facade.js';\n\nexport type {\n EdgeRef,\n FlatSide,\n BendRule,\n MaterialSpec,\n MiterSpec,\n BendFeature,\n FlangeFeature,\n CornerMiter,\n ReliefSpec,\n ReliefFeature,\n CutoutSpec,\n CutoutFeature,\n TabSpec,\n TabFeature,\n FormSpec,\n FormFeature,\n ProfileSegment,\n ContourFlangeSpec,\n ContourFlangeFeature,\n LoftedFlangeSpec,\n LoftedFlangeFeature,\n HemSpec,\n HemFeature,\n JogSpec,\n JogFeature,\n SheetMetalPart,\n FlatPattern,\n FlatInput,\n FoldRegion,\n BendReport,\n SheetMetalWarning,\n UnfoldResult,\n} from './types.js';\n"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type Solid, type Vec3 } from '@faicad/faijs/brepjs-compat';
|
|
2
|
+
import type { FlatSide } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* OCCT boolean fuse returns a compound wrapping the unioned solid. Extract the
|
|
5
|
+
* single solid so a part's cached shape is a true `Solid` (clean STEP/GLB
|
|
6
|
+
* export); fall back to the compound if the union produced multiple bodies.
|
|
7
|
+
*
|
|
8
|
+
* @param shape - The shape returned by the fuse boolean operation (a `Solid` or
|
|
9
|
+
* a compound wrapping one).
|
|
10
|
+
* @returns The single `Solid` when the compound wraps exactly one solid,
|
|
11
|
+
* otherwise the input `shape` unchanged.
|
|
12
|
+
*/
|
|
13
|
+
export declare function normalizeSolid(shape: Solid): Solid;
|
|
14
|
+
/**
|
|
15
|
+
* Fallback develop side for a hand-built flange whose `baseEdge.side` is omitted:
|
|
16
|
+
* derive it from the recorded bend axis. An axis along +Y (|axisDir.y|>0.5) is an
|
|
17
|
+
* east flange that develops in +X (`xmax`); an axis along +X is a north flange
|
|
18
|
+
* developing in +Y (`ymax`). This preserves the original axis-only unfold
|
|
19
|
+
* heuristic for external fixtures predating the explicit `side` field; parts from
|
|
20
|
+
* {@link authorPart} always set `side`, so this only kicks in for the legacy path.
|
|
21
|
+
*
|
|
22
|
+
* @param axisDir - The recorded bend axis as a `Vec3`.
|
|
23
|
+
* @returns The develop side (`'xmax'` for a +Y axis, `'ymax'` for a +X axis),
|
|
24
|
+
* or `undefined` when the axis does not point along +Y or +X.
|
|
25
|
+
*/
|
|
26
|
+
export declare function sideFromAxisDir(axisDir: Vec3): FlatSide | undefined;
|
|
27
|
+
//# sourceMappingURL=internal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../src/internal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,IAAI,EAAsB,MAAM,6BAA6B,CAAC;AACxF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,GAAG,KAAK,CAKlD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,IAAI,GAAG,QAAQ,GAAG,SAAS,CAInE"}
|
package/dist/internal.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { isSolid, getSolids } from '@faicad/faijs/brepjs-compat';
|
|
2
|
+
/**
|
|
3
|
+
* OCCT boolean fuse returns a compound wrapping the unioned solid. Extract the
|
|
4
|
+
* single solid so a part's cached shape is a true `Solid` (clean STEP/GLB
|
|
5
|
+
* export); fall back to the compound if the union produced multiple bodies.
|
|
6
|
+
*
|
|
7
|
+
* @param shape - The shape returned by the fuse boolean operation (a `Solid` or
|
|
8
|
+
* a compound wrapping one).
|
|
9
|
+
* @returns The single `Solid` when the compound wraps exactly one solid,
|
|
10
|
+
* otherwise the input `shape` unchanged.
|
|
11
|
+
*/
|
|
12
|
+
export function normalizeSolid(shape) {
|
|
13
|
+
if (isSolid(shape))
|
|
14
|
+
return shape;
|
|
15
|
+
const solids = getSolids(shape);
|
|
16
|
+
const only = solids.length === 1 ? solids[0] : undefined;
|
|
17
|
+
return only ?? shape;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Fallback develop side for a hand-built flange whose `baseEdge.side` is omitted:
|
|
21
|
+
* derive it from the recorded bend axis. An axis along +Y (|axisDir.y|>0.5) is an
|
|
22
|
+
* east flange that develops in +X (`xmax`); an axis along +X is a north flange
|
|
23
|
+
* developing in +Y (`ymax`). This preserves the original axis-only unfold
|
|
24
|
+
* heuristic for external fixtures predating the explicit `side` field; parts from
|
|
25
|
+
* {@link authorPart} always set `side`, so this only kicks in for the legacy path.
|
|
26
|
+
*
|
|
27
|
+
* @param axisDir - The recorded bend axis as a `Vec3`.
|
|
28
|
+
* @returns The develop side (`'xmax'` for a +Y axis, `'ymax'` for a +X axis),
|
|
29
|
+
* or `undefined` when the axis does not point along +Y or +X.
|
|
30
|
+
*/
|
|
31
|
+
export function sideFromAxisDir(axisDir) {
|
|
32
|
+
if (Math.abs(axisDir[1]) > 0.5)
|
|
33
|
+
return 'xmax';
|
|
34
|
+
if (Math.abs(axisDir[0]) > 0.5)
|
|
35
|
+
return 'ymax';
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=internal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"internal.js","sourceRoot":"","sources":["../src/internal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,OAAO,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAGxF;;;;;;;;;GASG;AACH,MAAM,UAAU,cAAc,CAAC,KAAY;IACzC,IAAI,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACjC,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;IAChC,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACzD,OAAO,IAAI,IAAI,KAAK,CAAC;AACvB,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,eAAe,CAAC,OAAa;IAC3C,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG;QAAE,OAAO,MAAM,CAAC;IAC9C,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG;QAAE,OAAO,MAAM,CAAC;IAC9C,OAAO,SAAS,CAAC;AACnB,CAAC","sourcesContent":["import { type Solid, type Vec3, isSolid, getSolids } from '@faicad/faijs/brepjs-compat';\nimport type { FlatSide } from './types.js';\n\n/**\n * OCCT boolean fuse returns a compound wrapping the unioned solid. Extract the\n * single solid so a part's cached shape is a true `Solid` (clean STEP/GLB\n * export); fall back to the compound if the union produced multiple bodies.\n *\n * @param shape - The shape returned by the fuse boolean operation (a `Solid` or\n * a compound wrapping one).\n * @returns The single `Solid` when the compound wraps exactly one solid,\n * otherwise the input `shape` unchanged.\n */\nexport function normalizeSolid(shape: Solid): Solid {\n if (isSolid(shape)) return shape;\n const solids = getSolids(shape);\n const only = solids.length === 1 ? solids[0] : undefined;\n return only ?? shape;\n}\n\n/**\n * Fallback develop side for a hand-built flange whose `baseEdge.side` is omitted:\n * derive it from the recorded bend axis. An axis along +Y (|axisDir.y|>0.5) is an\n * east flange that develops in +X (`xmax`); an axis along +X is a north flange\n * developing in +Y (`ymax`). This preserves the original axis-only unfold\n * heuristic for external fixtures predating the explicit `side` field; parts from\n * {@link authorPart} always set `side`, so this only kicks in for the legacy path.\n *\n * @param axisDir - The recorded bend axis as a `Vec3`.\n * @returns The develop side (`'xmax'` for a +Y axis, `'ymax'` for a +X axis),\n * or `undefined` when the axis does not point along +Y or +X.\n */\nexport function sideFromAxisDir(axisDir: Vec3): FlatSide | undefined {\n if (Math.abs(axisDir[1]) > 0.5) return 'xmax';\n if (Math.abs(axisDir[0]) > 0.5) return 'ymax';\n return undefined;\n}\n"]}
|
package/dist/jogFns.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type Result } from '@faicad/faijs/brepjs-compat';
|
|
2
|
+
import type { JogSpec, SheetMetalPart } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Author a jog (joggle): two opposite bends (`+θ` then `−θ`) that step a flat by
|
|
5
|
+
* `offsetHeight` perpendicular to its plane, then continue parallel past the step.
|
|
6
|
+
* Built on the contour-flange chainer: a flat position leg, a `+θ` up bend
|
|
7
|
+
* (`jog::<id>::0`), the connecting step run, a `−θ` down bend (`jog::<id>::1`), and
|
|
8
|
+
* a flat run-out leg. The development is EXACT (Σ legs + Σ bend allowances, via the
|
|
9
|
+
* table-aware {@link developedLength}). The connecting step run is
|
|
10
|
+
* `offsetHeight / sin(θ)` so the two bends realize the requested perpendicular step;
|
|
11
|
+
* the result is guarded to a valid single solid. OCCT-WASM-safe construction only.
|
|
12
|
+
*
|
|
13
|
+
* @param part - the sheet metal part to attach a jog to.
|
|
14
|
+
* @param spec - the jog specification.
|
|
15
|
+
* @returns the updated part with the jog feature recorded, or an error.
|
|
16
|
+
*/
|
|
17
|
+
export declare function jog(part: SheetMetalPart, spec: JogSpec): Result<SheetMetalPart>;
|
|
18
|
+
//# sourceMappingURL=jogFns.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jogFns.d.ts","sourceRoot":"","sources":["../src/jogFns.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAwC,MAAM,6BAA6B,CAAC;AAChG,OAAO,KAAK,EAIV,OAAO,EACP,cAAc,EACf,MAAM,YAAY,CAAC;AAapB;;;;;;;;;;;;;GAaG;AACH,wBAAgB,GAAG,CAAC,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC,cAAc,CAAC,CAqK/E"}
|
package/dist/jogFns.js
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { ok, err, validationError } from '@faicad/faijs/brepjs-compat';
|
|
2
|
+
import { normalizeSolid } from './internal.js';
|
|
3
|
+
import { developedLength } from './allowanceFns.js';
|
|
4
|
+
import { worldFrames } from './authorFns.js';
|
|
5
|
+
import { ROOT_FLAT_ID } from './featureTreeFns.js';
|
|
6
|
+
import { buildLineLeg, buildArcBend, regionEdge, initialSegmentFrame, } from './contourFlangeFns.js';
|
|
7
|
+
/**
|
|
8
|
+
* Author a jog (joggle): two opposite bends (`+θ` then `−θ`) that step a flat by
|
|
9
|
+
* `offsetHeight` perpendicular to its plane, then continue parallel past the step.
|
|
10
|
+
* Built on the contour-flange chainer: a flat position leg, a `+θ` up bend
|
|
11
|
+
* (`jog::<id>::0`), the connecting step run, a `−θ` down bend (`jog::<id>::1`), and
|
|
12
|
+
* a flat run-out leg. The development is EXACT (Σ legs + Σ bend allowances, via the
|
|
13
|
+
* table-aware {@link developedLength}). The connecting step run is
|
|
14
|
+
* `offsetHeight / sin(θ)` so the two bends realize the requested perpendicular step;
|
|
15
|
+
* the result is guarded to a valid single solid. OCCT-WASM-safe construction only.
|
|
16
|
+
*
|
|
17
|
+
* @param part - the sheet metal part to attach a jog to.
|
|
18
|
+
* @param spec - the jog specification.
|
|
19
|
+
* @returns the updated part with the jog feature recorded, or an error.
|
|
20
|
+
*/
|
|
21
|
+
export function jog(part, spec) {
|
|
22
|
+
if (part.solid === undefined) {
|
|
23
|
+
return err(validationError('NO_SOLID', `part has no solid to attach jog '${jogId(spec)}'`));
|
|
24
|
+
}
|
|
25
|
+
const id = jogId(spec);
|
|
26
|
+
if (id.includes('::')) {
|
|
27
|
+
return err(validationError('INVALID_JOG_ID', `jog id must not contain '::', got '${id}'`));
|
|
28
|
+
}
|
|
29
|
+
for (const existing of part.jogs ?? []) {
|
|
30
|
+
if (existing.id === id) {
|
|
31
|
+
return err(validationError('DUPLICATE_JOG', `duplicate jog id '${id}'`));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const thickness = part.thickness;
|
|
35
|
+
if (!Number.isFinite(spec.offsetHeight) || spec.offsetHeight <= 0) {
|
|
36
|
+
return err(validationError('INVALID_JOG_OFFSET', `jog offsetHeight must be positive, got ${spec.offsetHeight}`));
|
|
37
|
+
}
|
|
38
|
+
if (!Number.isFinite(spec.position) || spec.position <= 0) {
|
|
39
|
+
return err(validationError('INVALID_JOG_POSITION', `jog position must be positive, got ${spec.position}`));
|
|
40
|
+
}
|
|
41
|
+
const angleDeg = spec.angle ?? 45;
|
|
42
|
+
if (!Number.isFinite(angleDeg) || angleDeg <= 0 || angleDeg >= 90) {
|
|
43
|
+
return err(validationError('INVALID_JOG_ANGLE', `jog angle must be in (0, 90), got ${angleDeg}`));
|
|
44
|
+
}
|
|
45
|
+
const radius = spec.radius ?? thickness;
|
|
46
|
+
if (!Number.isFinite(radius) || radius <= 0) {
|
|
47
|
+
return err(validationError('INVALID_JOG_RADIUS', `jog radius must be positive, got ${radius}`));
|
|
48
|
+
}
|
|
49
|
+
const runOut = spec.runOut ?? spec.position;
|
|
50
|
+
if (!Number.isFinite(runOut) || runOut <= 0) {
|
|
51
|
+
return err(validationError('INVALID_JOG_RUNOUT', `jog runOut must be positive, got ${runOut}`));
|
|
52
|
+
}
|
|
53
|
+
const framesResult = worldFrames(part);
|
|
54
|
+
if (!framesResult.ok)
|
|
55
|
+
return framesResult;
|
|
56
|
+
const regionId = resolveRegion(spec.region);
|
|
57
|
+
const regionFrame = framesResult.value.get(regionId);
|
|
58
|
+
if (regionFrame === undefined) {
|
|
59
|
+
return err(validationError('UNKNOWN_REGION', `jog '${id}' references unknown region '${spec.region}'`));
|
|
60
|
+
}
|
|
61
|
+
const edge = regionEdge(regionFrame, spec.side);
|
|
62
|
+
const offset = spec.offset ?? 0;
|
|
63
|
+
const span = spec.width ?? edge.length;
|
|
64
|
+
if (!Number.isFinite(offset) || offset < -1e-9) {
|
|
65
|
+
return err(validationError('INVALID_OFFSET', `jog '${id}' offset must be non-negative`));
|
|
66
|
+
}
|
|
67
|
+
if (!Number.isFinite(span) || span <= 0) {
|
|
68
|
+
return err(validationError('INVALID_JOG_WIDTH', `jog '${id}' width must be positive`));
|
|
69
|
+
}
|
|
70
|
+
if (offset + span > edge.length + 1e-6) {
|
|
71
|
+
return err(validationError('JOG_OUT_OF_BOUNDS', `jog '${id}' [${offset}, ${offset + span}] exceeds region edge length ${edge.length}`));
|
|
72
|
+
}
|
|
73
|
+
const rule = spec.rule ?? { innerRadius: radius, kFactor: 0.44 };
|
|
74
|
+
// The connecting step run carries the flat across the offset at angle θ. The two
|
|
75
|
+
// arcs themselves each rise (T+2r)/2·(1−cosθ) perpendicular to the plane (the
|
|
76
|
+
// up-bend rises (T+r)(1−cosθ), the down-bend r(1−cosθ)); the step run adds
|
|
77
|
+
// L·sinθ. Solving for the total perpendicular rise = offsetHeight gives this L, so
|
|
78
|
+
// the run-out bottom face sits exactly offsetHeight above the base bottom face.
|
|
79
|
+
const theta = (angleDeg * Math.PI) / 180;
|
|
80
|
+
const arcRise = (thickness + 2 * radius) * (1 - Math.cos(theta));
|
|
81
|
+
const stepRun = (spec.offsetHeight - arcRise) / Math.sin(theta);
|
|
82
|
+
if (!(stepRun > 0)) {
|
|
83
|
+
return err(validationError('JOG_OFFSET_TOO_SMALL', `jog offsetHeight ${spec.offsetHeight} is too small for radius ${radius} at ${angleDeg}° (the bends alone rise ${arcRise.toFixed(3)}); increase offsetHeight or reduce radius/angle`));
|
|
84
|
+
}
|
|
85
|
+
let frame = initialSegmentFrame(edge, regionFrame.n, offset);
|
|
86
|
+
let solid = part.solid;
|
|
87
|
+
const bends = [];
|
|
88
|
+
const segments = [];
|
|
89
|
+
const bendIds = [];
|
|
90
|
+
let devTotal = 0;
|
|
91
|
+
// 1) Flat position leg out to the first bend.
|
|
92
|
+
{
|
|
93
|
+
const built = buildLineLeg(solid, frame, span, thickness, spec.position);
|
|
94
|
+
if (!built.ok)
|
|
95
|
+
return built;
|
|
96
|
+
solid = built.value.solid;
|
|
97
|
+
frame = built.value.frame;
|
|
98
|
+
segments.push({ kind: 'line', dev: spec.position });
|
|
99
|
+
devTotal += spec.position;
|
|
100
|
+
}
|
|
101
|
+
// 2) Up bend (+θ), 3) step run, 4) down bend (−θ): the two opposite bends.
|
|
102
|
+
const order = [
|
|
103
|
+
{ dir: 'up', step: stepRun },
|
|
104
|
+
{ dir: 'down' },
|
|
105
|
+
];
|
|
106
|
+
for (let i = 0; i < order.length; i += 1) {
|
|
107
|
+
const o = order[i];
|
|
108
|
+
if (o === undefined)
|
|
109
|
+
continue;
|
|
110
|
+
const built = buildArcBend(solid, frame, span, thickness, {
|
|
111
|
+
kind: 'arc',
|
|
112
|
+
radius,
|
|
113
|
+
angleDeg,
|
|
114
|
+
direction: o.dir,
|
|
115
|
+
});
|
|
116
|
+
if (!built.ok)
|
|
117
|
+
return built;
|
|
118
|
+
solid = built.value.solid;
|
|
119
|
+
frame = built.value.frame;
|
|
120
|
+
const devResult = developedLength(angleDeg, thickness, { ...rule, innerRadius: radius });
|
|
121
|
+
if (!devResult.ok)
|
|
122
|
+
return devResult;
|
|
123
|
+
const dev = devResult.value;
|
|
124
|
+
const bendId = `jog::${id}::${i}`;
|
|
125
|
+
bendIds.push(bendId);
|
|
126
|
+
bends.push({
|
|
127
|
+
id: bendId,
|
|
128
|
+
axisOrigin: [built.value.axisOrigin[0], built.value.axisOrigin[1], built.value.axisOrigin[2]],
|
|
129
|
+
axisDir: [edge.dir[0], edge.dir[1], edge.dir[2]],
|
|
130
|
+
angleDeg,
|
|
131
|
+
direction: o.dir,
|
|
132
|
+
rule: { ...rule, innerRadius: radius },
|
|
133
|
+
});
|
|
134
|
+
segments.push({ kind: 'arc', dev, angleDeg, direction: o.dir, bendId });
|
|
135
|
+
devTotal += dev;
|
|
136
|
+
if (o.step !== undefined) {
|
|
137
|
+
const stepBuilt = buildLineLeg(solid, frame, span, thickness, o.step);
|
|
138
|
+
if (!stepBuilt.ok)
|
|
139
|
+
return stepBuilt;
|
|
140
|
+
solid = stepBuilt.value.solid;
|
|
141
|
+
frame = stepBuilt.value.frame;
|
|
142
|
+
segments.push({ kind: 'line', dev: o.step });
|
|
143
|
+
devTotal += o.step;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// 5) Flat run-out leg, parallel to the original plane, offset by offsetHeight.
|
|
147
|
+
{
|
|
148
|
+
const built = buildLineLeg(solid, frame, span, thickness, runOut);
|
|
149
|
+
if (!built.ok)
|
|
150
|
+
return built;
|
|
151
|
+
solid = built.value.solid;
|
|
152
|
+
segments.push({ kind: 'line', dev: runOut });
|
|
153
|
+
devTotal += runOut;
|
|
154
|
+
}
|
|
155
|
+
const feature = {
|
|
156
|
+
id,
|
|
157
|
+
region: regionId,
|
|
158
|
+
side: spec.side,
|
|
159
|
+
offset,
|
|
160
|
+
span,
|
|
161
|
+
offsetHeight: spec.offsetHeight,
|
|
162
|
+
angleDeg,
|
|
163
|
+
developedLength: devTotal,
|
|
164
|
+
bends: bendIds,
|
|
165
|
+
segments,
|
|
166
|
+
};
|
|
167
|
+
return ok({
|
|
168
|
+
...part,
|
|
169
|
+
solid: normalizeSolid(solid),
|
|
170
|
+
bends: [...part.bends, ...bends],
|
|
171
|
+
jogs: [...(part.jogs ?? []), feature],
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
function jogId(spec) {
|
|
175
|
+
return spec.id ?? `jog-${spec.region}-${spec.side}`;
|
|
176
|
+
}
|
|
177
|
+
function resolveRegion(region) {
|
|
178
|
+
return region === 'base' || region === 'face-0' ? ROOT_FLAT_ID : region;
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=jogFns.js.map
|