@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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jogFns.js","sourceRoot":"","sources":["../src/jogFns.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 JogFeature,\n JogSpec,\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 * Author a jog (joggle): two opposite bends (`+θ` then `−θ`) that step a flat by\n * `offsetHeight` perpendicular to its plane, then continue parallel past the step.\n * Built on the contour-flange chainer: a flat position leg, a `+θ` up bend\n * (`jog::<id>::0`), the connecting step run, a `−θ` down bend (`jog::<id>::1`), and\n * a flat run-out leg. The development is EXACT (Σ legs + Σ bend allowances, via the\n * table-aware {@link developedLength}). The connecting step run is\n * `offsetHeight / sin(θ)` so the two bends realize the requested perpendicular step;\n * the result is guarded to a valid single solid. OCCT-WASM-safe construction only.\n *\n * @param part - the sheet metal part to attach a jog to.\n * @param spec - the jog specification.\n * @returns the updated part with the jog feature recorded, or an error.\n */\nexport function jog(part: SheetMetalPart, spec: JogSpec): Result<SheetMetalPart> {\n if (part.solid === undefined) {\n return err(validationError('NO_SOLID', `part has no solid to attach jog '${jogId(spec)}'`));\n }\n const id = jogId(spec);\n if (id.includes('::')) {\n return err(validationError('INVALID_JOG_ID', `jog id must not contain '::', got '${id}'`));\n }\n for (const existing of part.jogs ?? []) {\n if (existing.id === id) {\n return err(validationError('DUPLICATE_JOG', `duplicate jog id '${id}'`));\n }\n }\n\n const thickness = part.thickness;\n if (!Number.isFinite(spec.offsetHeight) || spec.offsetHeight <= 0) {\n return err(validationError('INVALID_JOG_OFFSET', `jog offsetHeight must be positive, got ${spec.offsetHeight}`));\n }\n if (!Number.isFinite(spec.position) || spec.position <= 0) {\n return err(validationError('INVALID_JOG_POSITION', `jog position must be positive, got ${spec.position}`));\n }\n const angleDeg = spec.angle ?? 45;\n if (!Number.isFinite(angleDeg) || angleDeg <= 0 || angleDeg >= 90) {\n return err(validationError('INVALID_JOG_ANGLE', `jog angle must be in (0, 90), got ${angleDeg}`));\n }\n const radius = spec.radius ?? thickness;\n if (!Number.isFinite(radius) || radius <= 0) {\n return err(validationError('INVALID_JOG_RADIUS', `jog radius must be positive, got ${radius}`));\n }\n const runOut = spec.runOut ?? spec.position;\n if (!Number.isFinite(runOut) || runOut <= 0) {\n return err(validationError('INVALID_JOG_RUNOUT', `jog runOut must be positive, got ${runOut}`));\n }\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', `jog '${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', `jog '${id}' offset must be non-negative`));\n }\n if (!Number.isFinite(span) || span <= 0) {\n return err(validationError('INVALID_JOG_WIDTH', `jog '${id}' width must be positive`));\n }\n if (offset + span > edge.length + 1e-6) {\n return err(\n validationError('JOG_OUT_OF_BOUNDS', `jog '${id}' [${offset}, ${offset + span}] exceeds region edge length ${edge.length}`)\n );\n }\n\n const rule: BendRule = spec.rule ?? { innerRadius: radius, kFactor: 0.44 };\n // The connecting step run carries the flat across the offset at angle θ. The two\n // arcs themselves each rise (T+2r)/2·(1−cosθ) perpendicular to the plane (the\n // up-bend rises (T+r)(1−cosθ), the down-bend r(1−cosθ)); the step run adds\n // L·sinθ. Solving for the total perpendicular rise = offsetHeight gives this L, so\n // the run-out bottom face sits exactly offsetHeight above the base bottom face.\n const theta = (angleDeg * Math.PI) / 180;\n const arcRise = (thickness + 2 * radius) * (1 - Math.cos(theta));\n const stepRun = (spec.offsetHeight - arcRise) / Math.sin(theta);\n if (!(stepRun > 0)) {\n return err(\n validationError(\n 'JOG_OFFSET_TOO_SMALL',\n `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`\n )\n );\n }\n\n let frame: SegmentFrame = initialSegmentFrame(edge, regionFrame.n, offset);\n let solid: Solid = part.solid;\n const bends: BendFeature[] = [];\n const segments: JogFeature['segments'] = [];\n const bendIds: string[] = [];\n let devTotal = 0;\n\n // 1) Flat position leg out to the first bend.\n {\n const built = buildLineLeg(solid, frame, span, thickness, spec.position);\n if (!built.ok) return built;\n solid = built.value.solid;\n frame = built.value.frame;\n segments.push({ kind: 'line', dev: spec.position });\n devTotal += spec.position;\n }\n\n // 2) Up bend (+θ), 3) step run, 4) down bend (−θ): the two opposite bends.\n const order: { dir: 'up' | 'down'; step?: number }[] = [\n { dir: 'up', step: stepRun },\n { dir: 'down' },\n ];\n for (let i = 0; i < order.length; i += 1) {\n const o = order[i];\n if (o === undefined) continue;\n const built = buildArcBend(solid, frame, span, thickness, {\n kind: 'arc',\n radius,\n angleDeg,\n direction: o.dir,\n });\n if (!built.ok) return built;\n solid = built.value.solid;\n frame = built.value.frame;\n\n const devResult = developedLength(angleDeg, thickness, { ...rule, innerRadius: radius });\n if (!devResult.ok) return devResult;\n const dev = devResult.value;\n\n const bendId = `jog::${id}::${i}`;\n bendIds.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,\n direction: o.dir,\n rule: { ...rule, innerRadius: radius },\n });\n segments.push({ kind: 'arc', dev, angleDeg, direction: o.dir, bendId });\n devTotal += dev;\n\n if (o.step !== undefined) {\n const stepBuilt = buildLineLeg(solid, frame, span, thickness, o.step);\n if (!stepBuilt.ok) return stepBuilt;\n solid = stepBuilt.value.solid;\n frame = stepBuilt.value.frame;\n segments.push({ kind: 'line', dev: o.step });\n devTotal += o.step;\n }\n }\n\n // 5) Flat run-out leg, parallel to the original plane, offset by offsetHeight.\n {\n const built = buildLineLeg(solid, frame, span, thickness, runOut);\n if (!built.ok) return built;\n solid = built.value.solid;\n segments.push({ kind: 'line', dev: runOut });\n devTotal += runOut;\n }\n\n const feature: JogFeature = {\n id,\n region: regionId,\n side: spec.side,\n offset,\n span,\n offsetHeight: spec.offsetHeight,\n angleDeg,\n developedLength: devTotal,\n bends: bendIds,\n segments,\n };\n\n return ok({\n ...part,\n solid: normalizeSolid(solid),\n bends: [...part.bends, ...bends],\n jogs: [...(part.jogs ?? []), feature],\n });\n}\n\nfunction jogId(spec: JogSpec): string {\n return spec.id ?? `jog-${spec.region}-${spec.side}`;\n}\n\nfunction resolveRegion(region: string): string {\n return region === 'base' || region === 'face-0' ? ROOT_FLAT_ID : region;\n}\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type Result } from '@faicad/faijs/brepjs-compat';
|
|
2
|
+
import type { LoftedFlangeSpec, SheetMetalPart } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Author a lofted / ruled transition flange between two parallel OPEN profiles.
|
|
5
|
+
* The two profiles are lofted (ruled, straight generators) into a transition surface
|
|
6
|
+
* and thickened to a valid solid; the result is fused onto the part. The developed
|
|
7
|
+
* pattern is produced by TRIANGULATION (the standard transition development): each
|
|
8
|
+
* quad between consecutive vertex pairs is split into two triangles laid flat
|
|
9
|
+
* edge-length-preserving and accumulated into a developed boundary.
|
|
10
|
+
*
|
|
11
|
+
* The triangulated development is EXACT (to tolerance) when the ruled surface is
|
|
12
|
+
* developable — every quad is planar, so the two triangles share their diagonal in
|
|
13
|
+
* 3D and the flat layout preserves all lengths and angles. When a quad is non-planar
|
|
14
|
+
* (twisted ruling) the surface is not developable and the flat layout is an
|
|
15
|
+
* approximation; {@link approximate} is set and the unfold emits a
|
|
16
|
+
* `DEVELOPMENT_APPROXIMATE` warning.
|
|
17
|
+
*
|
|
18
|
+
* @param part - the sheet metal part to attach a lofted flange to.
|
|
19
|
+
* @param spec - the lofted flange specification.
|
|
20
|
+
* @returns the updated part with the lofted flange feature recorded, or an error.
|
|
21
|
+
*/
|
|
22
|
+
export declare function authorLoftedFlange(part: SheetMetalPart, spec: LoftedFlangeSpec): Result<SheetMetalPart>;
|
|
23
|
+
//# sourceMappingURL=loftedFlangeFns.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loftedFlangeFns.d.ts","sourceRoot":"","sources":["../src/loftedFlangeFns.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,MAAM,EAeZ,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAuB,gBAAgB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAexF;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,cAAc,EACpB,IAAI,EAAE,gBAAgB,GACrB,MAAM,CAAC,cAAc,CAAC,CAkExB"}
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { ok, err, validationError, line, wireLoop, extrude, face, fuse, isPlanarWire, isSolid, getSolids, } from '@faicad/faijs/brepjs-compat';
|
|
2
|
+
/**
|
|
3
|
+
* Relative out-of-plane tolerance for the per-quad developability test: a quad is
|
|
4
|
+
* twisted (non-developable) when its far corner sits off the diagonal-triangle plane
|
|
5
|
+
* by more than this fraction of the quad's own size. Relative — not absolute — so a
|
|
6
|
+
* genuinely flat quad is not false-flagged on a large part (numerical noise scales
|
|
7
|
+
* with coordinates), nor a real twist missed on a tiny one.
|
|
8
|
+
*/
|
|
9
|
+
const DEVELOPABLE_REL_TOL = 1e-6;
|
|
10
|
+
/** Floor on the size scale so a degenerate (near-zero-size) quad keeps a sane tolerance. */
|
|
11
|
+
const DEVELOPABLE_MIN_SCALE = 1e-9;
|
|
12
|
+
/**
|
|
13
|
+
* Author a lofted / ruled transition flange between two parallel OPEN profiles.
|
|
14
|
+
* The two profiles are lofted (ruled, straight generators) into a transition surface
|
|
15
|
+
* and thickened to a valid solid; the result is fused onto the part. The developed
|
|
16
|
+
* pattern is produced by TRIANGULATION (the standard transition development): each
|
|
17
|
+
* quad between consecutive vertex pairs is split into two triangles laid flat
|
|
18
|
+
* edge-length-preserving and accumulated into a developed boundary.
|
|
19
|
+
*
|
|
20
|
+
* The triangulated development is EXACT (to tolerance) when the ruled surface is
|
|
21
|
+
* developable — every quad is planar, so the two triangles share their diagonal in
|
|
22
|
+
* 3D and the flat layout preserves all lengths and angles. When a quad is non-planar
|
|
23
|
+
* (twisted ruling) the surface is not developable and the flat layout is an
|
|
24
|
+
* approximation; {@link approximate} is set and the unfold emits a
|
|
25
|
+
* `DEVELOPMENT_APPROXIMATE` warning.
|
|
26
|
+
*
|
|
27
|
+
* @param part - the sheet metal part to attach a lofted flange to.
|
|
28
|
+
* @param spec - the lofted flange specification.
|
|
29
|
+
* @returns the updated part with the lofted flange feature recorded, or an error.
|
|
30
|
+
*/
|
|
31
|
+
export function authorLoftedFlange(part, spec) {
|
|
32
|
+
if (spec.id === '' || spec.id.includes('::')) {
|
|
33
|
+
return err(validationError('INVALID_LOFTED_ID', `lofted flange id must be non-empty and must not contain '::', got '${spec.id}'`));
|
|
34
|
+
}
|
|
35
|
+
for (const existing of part.loftedFlanges ?? []) {
|
|
36
|
+
if (existing.id === spec.id) {
|
|
37
|
+
return err(validationError('DUPLICATE_LOFTED', `duplicate lofted flange id '${spec.id}'`));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (spec.profileA.length < 2 || spec.profileB.length < 2) {
|
|
41
|
+
return err(validationError('INVALID_LOFTED_PROFILE', `lofted flange '${spec.id}' profiles need ≥ 2 points each`));
|
|
42
|
+
}
|
|
43
|
+
if (spec.profileA.length !== spec.profileB.length) {
|
|
44
|
+
return err(validationError('PROFILE_VERTEX_MISMATCH', `lofted flange '${spec.id}' profiles must have equal vertex counts (${spec.profileA.length} vs ${spec.profileB.length}) to pair into ruling triangles`));
|
|
45
|
+
}
|
|
46
|
+
if (!Number.isFinite(spec.height) || spec.height <= 0) {
|
|
47
|
+
return err(validationError('INVALID_LOFTED_HEIGHT', `lofted flange '${spec.id}' height must be positive, got ${spec.height}`));
|
|
48
|
+
}
|
|
49
|
+
const thickness = spec.thickness ?? part.thickness;
|
|
50
|
+
if (!Number.isFinite(thickness) || thickness <= 0) {
|
|
51
|
+
return err(validationError('INVALID_THICKNESS', `lofted flange '${spec.id}' thickness must be positive`));
|
|
52
|
+
}
|
|
53
|
+
// 3D profile points: profileA in the z=0 plane, profileB at z=height.
|
|
54
|
+
const a3 = spec.profileA.map(([x, y]) => [x, y, 0]);
|
|
55
|
+
const b3 = spec.profileB.map(([x, y]) => [x, y, spec.height]);
|
|
56
|
+
const solidResult = buildLoftedSolid(spec.id, a3, b3, thickness);
|
|
57
|
+
if (!solidResult.ok)
|
|
58
|
+
return solidResult;
|
|
59
|
+
const dev = developRuled(a3, b3);
|
|
60
|
+
const loopResult = closedLoopWire(dev.loop);
|
|
61
|
+
if (!loopResult.ok)
|
|
62
|
+
return loopResult;
|
|
63
|
+
const feature = {
|
|
64
|
+
id: spec.id,
|
|
65
|
+
developedLoop: dev.loop,
|
|
66
|
+
developedArea: dev.area,
|
|
67
|
+
approximate: dev.approximate,
|
|
68
|
+
};
|
|
69
|
+
// The lofted transition is an independent body fused to the part; like a contour
|
|
70
|
+
// flange it carries no straight-bend tree entry (its development is the
|
|
71
|
+
// triangulated loop, not a rectilinear strip).
|
|
72
|
+
if (part.solid === undefined) {
|
|
73
|
+
return ok({
|
|
74
|
+
...part,
|
|
75
|
+
solid: solidResult.value,
|
|
76
|
+
loftedFlanges: [...(part.loftedFlanges ?? []), feature],
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
const fused = fuse(part.solid, solidResult.value);
|
|
80
|
+
if (!fused.ok)
|
|
81
|
+
return fused;
|
|
82
|
+
return ok({
|
|
83
|
+
...part,
|
|
84
|
+
solid: normalize(fused.value),
|
|
85
|
+
loftedFlanges: [...(part.loftedFlanges ?? []), feature],
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
function normalize(shape) {
|
|
89
|
+
if (isSolid(shape))
|
|
90
|
+
return shape;
|
|
91
|
+
const solids = getSolids(shape);
|
|
92
|
+
return solids.length === 1 ? solids[0] : shape;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Build the thickened ruled transition solid as the union of per-quad plates. Each
|
|
96
|
+
* quad of the ruled surface is triangulated and each triangle extruded by
|
|
97
|
+
* `thickness` along its own normal, then all plates are fused. This per-triangle
|
|
98
|
+
* construction is robust on OCCT-WASM for both planar (developable) and twisted
|
|
99
|
+
* (non-developable) transitions, where a single lofted-then-shelled surface can fail
|
|
100
|
+
* to sew.
|
|
101
|
+
*/
|
|
102
|
+
function buildLoftedSolid(id, a, b, thickness) {
|
|
103
|
+
let solid;
|
|
104
|
+
for (let i = 0; i + 1 < a.length; i += 1) {
|
|
105
|
+
const a0 = a[i];
|
|
106
|
+
const a1 = a[i + 1];
|
|
107
|
+
const b0 = b[i];
|
|
108
|
+
const b1 = b[i + 1];
|
|
109
|
+
if (a0 === undefined || a1 === undefined || b0 === undefined || b1 === undefined) {
|
|
110
|
+
return err(validationError('LOFTED_QUAD_FAILED', `lofted flange '${id}' failed to index quad ${i}`));
|
|
111
|
+
}
|
|
112
|
+
const plate = buildQuadPlate(id, a0, a1, b1, b0, thickness);
|
|
113
|
+
if (!plate.ok)
|
|
114
|
+
return plate;
|
|
115
|
+
if (solid === undefined) {
|
|
116
|
+
solid = plate.value;
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
const fused = fuse(solid, plate.value);
|
|
120
|
+
if (!fused.ok)
|
|
121
|
+
return fused;
|
|
122
|
+
solid = normalize(fused.value);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (solid === undefined) {
|
|
126
|
+
return err(validationError('LOFTED_EMPTY', `lofted flange '${id}' produced no quads`));
|
|
127
|
+
}
|
|
128
|
+
return ok(solid);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* One quad of the ruled surface (corners a0→a1→b1→b0), thickened into a plate. The
|
|
132
|
+
* quad is split along its a0–b1 diagonal into two triangles lofted to the same
|
|
133
|
+
* triangles offset by `thickness` along the quad normal — robust on a non-planar
|
|
134
|
+
* (twisted) quad where a single planar face would fail. We loft each triangle face
|
|
135
|
+
* pair; for the common planar quad this is exact, for a twisted quad it is the
|
|
136
|
+
* faceted plate matching the triangulated development.
|
|
137
|
+
*/
|
|
138
|
+
function buildQuadPlate(id, a0, a1, b1, b0, thickness) {
|
|
139
|
+
const t1 = buildTrianglePlate(id, a0, a1, b1, thickness);
|
|
140
|
+
if (!t1.ok)
|
|
141
|
+
return t1;
|
|
142
|
+
const t2 = buildTrianglePlate(id, a0, b1, b0, thickness);
|
|
143
|
+
if (!t2.ok)
|
|
144
|
+
return t2;
|
|
145
|
+
const fused = fuse(t1.value, t2.value);
|
|
146
|
+
if (!fused.ok)
|
|
147
|
+
return fused;
|
|
148
|
+
return ok(normalize(fused.value));
|
|
149
|
+
}
|
|
150
|
+
/** A flat triangle (p0,p1,p2) extruded by `thickness` along its own normal. */
|
|
151
|
+
function buildTrianglePlate(id, p0, p1, p2, thickness) {
|
|
152
|
+
const e0 = line(p0, p1);
|
|
153
|
+
const e1 = line(p1, p2);
|
|
154
|
+
const e2 = line(p2, p0);
|
|
155
|
+
const loopResult = wireLoop([e0, e1, e2]);
|
|
156
|
+
if (!loopResult.ok)
|
|
157
|
+
return loopResult;
|
|
158
|
+
const loop = loopResult.value;
|
|
159
|
+
if (!isPlanarWire(loop)) {
|
|
160
|
+
return err(validationError('LOFTED_TRIANGLE_NONPLANAR', `lofted flange '${id}' triangle is not planar`));
|
|
161
|
+
}
|
|
162
|
+
const faceResult = face(loop);
|
|
163
|
+
if (!faceResult.ok)
|
|
164
|
+
return faceResult;
|
|
165
|
+
const n = triangleNormal(p0, p1, p2);
|
|
166
|
+
const dir = [n[0] * thickness, n[1] * thickness, n[2] * thickness];
|
|
167
|
+
const extruded = extrude(faceResult.value, dir);
|
|
168
|
+
if (!extruded.ok)
|
|
169
|
+
return extruded;
|
|
170
|
+
return ok(normalize(extruded.value));
|
|
171
|
+
}
|
|
172
|
+
function triangleNormal(p0, p1, p2) {
|
|
173
|
+
const u = [p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2]];
|
|
174
|
+
const v = [p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2]];
|
|
175
|
+
const c = [u[1] * v[2] - u[2] * v[1], u[2] * v[0] - u[0] * v[2], u[0] * v[1] - u[1] * v[0]];
|
|
176
|
+
const len = Math.hypot(c[0], c[1], c[2]) || 1;
|
|
177
|
+
return [c[0] / len, c[1] / len, c[2] / len];
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Triangulated development of a ruled transition between two equal-length vertex
|
|
181
|
+
* lists `a` (z=0) and `b` (z=height). Each quad (a[i],a[i+1],b[i+1],b[i]) is split
|
|
182
|
+
* along the a[i]–b[i+1] diagonal into two triangles; the triangles are laid out
|
|
183
|
+
* flat one strip at a time, hinging each new triangle onto the previously-placed
|
|
184
|
+
* shared edge with its true 3D edge lengths preserved. The developed boundary is the
|
|
185
|
+
* `a` chain laid along the bottom and the `b` chain returning along the top.
|
|
186
|
+
*
|
|
187
|
+
* Developable check: a quad is developable iff its two triangles, sharing the
|
|
188
|
+
* diagonal, are coplanar — equivalently the far corner b[i] lies in the plane of
|
|
189
|
+
* (a[i],a[i+1],b[i+1]). Any quad failing this within tolerance flags the whole
|
|
190
|
+
* development approximate.
|
|
191
|
+
*/
|
|
192
|
+
function developRuled(a, b) {
|
|
193
|
+
const aFlat = [];
|
|
194
|
+
const bFlat = [];
|
|
195
|
+
let area = 0;
|
|
196
|
+
let approximate = false;
|
|
197
|
+
// Place the first ruling (a[0]–b[0]) vertically: a[0] at origin, b[0] straight up
|
|
198
|
+
// by its true 3D length, so the developed strip grows along +x.
|
|
199
|
+
const d0 = dist(a[0], b[0]);
|
|
200
|
+
let aPrev = [0, 0];
|
|
201
|
+
let bPrev = [0, d0];
|
|
202
|
+
aFlat.push(aPrev);
|
|
203
|
+
bFlat.push(bPrev);
|
|
204
|
+
for (let i = 0; i + 1 < a.length; i += 1) {
|
|
205
|
+
const A0 = a[i];
|
|
206
|
+
const A1 = a[i + 1];
|
|
207
|
+
const B0 = b[i];
|
|
208
|
+
const B1 = b[i + 1];
|
|
209
|
+
// True 3D edge lengths of this quad. Diagonal A0–B1 splits it into
|
|
210
|
+
// T1=(A0,A1,B1) and T2=(A0,B1,B0).
|
|
211
|
+
const lBottom = dist(A0, A1); // A0→A1
|
|
212
|
+
const lTop = dist(B0, B1); // B0→B1
|
|
213
|
+
const lDiag = dist(A0, B1); // shared diagonal A0→B1
|
|
214
|
+
const lRight = dist(A1, B1); // right ruling A1→B1 (= next quad's left ruling)
|
|
215
|
+
// Developable test: is B0 coplanar with (A0, A1, B1)? If not, the quad is
|
|
216
|
+
// twisted and the flat layout cannot preserve all edges — mark approximate.
|
|
217
|
+
// Tolerance is relative to the quad's size so the check is scale-invariant.
|
|
218
|
+
const scale = Math.max(lBottom, lTop, lDiag, lRight, DEVELOPABLE_MIN_SCALE);
|
|
219
|
+
if (planeDistance(A0, A1, B1, B0) > DEVELOPABLE_REL_TOL * scale)
|
|
220
|
+
approximate = true;
|
|
221
|
+
// Place B1 from the known A0(=aPrev, dist=lDiag) and B0(=bPrev, dist=lTop).
|
|
222
|
+
const b1Flat = placeFrom(aPrev, bPrev, lDiag, lTop);
|
|
223
|
+
// Place A1 from A0(=aPrev, dist=lBottom) and the just-placed B1(dist=lRight),
|
|
224
|
+
// preserving the right ruling so it equals the next quad's left ruling exactly.
|
|
225
|
+
const a1Flat = placeFrom(aPrev, b1Flat, lBottom, lRight);
|
|
226
|
+
aFlat.push(a1Flat);
|
|
227
|
+
bFlat.push(b1Flat);
|
|
228
|
+
area += triArea3(A0, A1, B1) + triArea3(A0, B1, B0);
|
|
229
|
+
aPrev = a1Flat;
|
|
230
|
+
bPrev = b1Flat;
|
|
231
|
+
}
|
|
232
|
+
// Developed boundary: a-chain forward, b-chain back.
|
|
233
|
+
const loop = [...aFlat, ...[...bFlat].reverse()];
|
|
234
|
+
return { loop, area, approximate };
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Place a new point at distance `r1` from `p1` and `r2` from `p2` in 2D, choosing
|
|
238
|
+
* the solution on the +x side of the directed line p1→p2 (the strip grows toward +x,
|
|
239
|
+
* so successive rulings advance rightward). Falls back to the midpoint if the circles
|
|
240
|
+
* do not intersect (degenerate strip).
|
|
241
|
+
*/
|
|
242
|
+
function placeFrom(p1, p2, r1, r2) {
|
|
243
|
+
const dx = p2[0] - p1[0];
|
|
244
|
+
const dy = p2[1] - p1[1];
|
|
245
|
+
const d = Math.hypot(dx, dy);
|
|
246
|
+
if (d < 1e-12)
|
|
247
|
+
return [p1[0] + r1, p1[1]];
|
|
248
|
+
const a = (r1 * r1 - r2 * r2 + d * d) / (2 * d);
|
|
249
|
+
const h2 = r1 * r1 - a * a;
|
|
250
|
+
const h = h2 > 0 ? Math.sqrt(h2) : 0;
|
|
251
|
+
const mx = p1[0] + (a * dx) / d;
|
|
252
|
+
const my = p1[1] + (a * dy) / d;
|
|
253
|
+
// Perpendicular toward +x: pick the offset whose x is larger.
|
|
254
|
+
const ox = (-dy / d) * h;
|
|
255
|
+
const oy = (dx / d) * h;
|
|
256
|
+
const s1 = [mx + ox, my + oy];
|
|
257
|
+
const s2 = [mx - ox, my - oy];
|
|
258
|
+
return s1[0] >= s2[0] ? s1 : s2;
|
|
259
|
+
}
|
|
260
|
+
function dist(p, q) {
|
|
261
|
+
return Math.hypot(p[0] - q[0], p[1] - q[1], p[2] - q[2]);
|
|
262
|
+
}
|
|
263
|
+
function triArea3(p0, p1, p2) {
|
|
264
|
+
const u = [p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2]];
|
|
265
|
+
const v = [p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2]];
|
|
266
|
+
const c = [u[1] * v[2] - u[2] * v[1], u[2] * v[0] - u[0] * v[2], u[0] * v[1] - u[1] * v[0]];
|
|
267
|
+
return Math.hypot(c[0], c[1], c[2]) / 2;
|
|
268
|
+
}
|
|
269
|
+
/** Perpendicular distance of `q` from the plane through (p0,p1,p2). */
|
|
270
|
+
function planeDistance(p0, p1, p2, q) {
|
|
271
|
+
const u = [p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2]];
|
|
272
|
+
const v = [p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2]];
|
|
273
|
+
const c = [u[1] * v[2] - u[2] * v[1], u[2] * v[0] - u[0] * v[2], u[0] * v[1] - u[1] * v[0]];
|
|
274
|
+
const len = Math.hypot(c[0], c[1], c[2]);
|
|
275
|
+
if (len < 1e-12)
|
|
276
|
+
return 0;
|
|
277
|
+
const w = [q[0] - p0[0], q[1] - p0[1], q[2] - p0[2]];
|
|
278
|
+
return Math.abs((w[0] * c[0] + w[1] * c[1] + w[2] * c[2]) / len);
|
|
279
|
+
}
|
|
280
|
+
/** Trace a closed developed-plane loop (≥ 3 points) into a morph {@link Wire}. */
|
|
281
|
+
function closedLoopWire(loop) {
|
|
282
|
+
const deduped = [];
|
|
283
|
+
for (const p of loop) {
|
|
284
|
+
const last = deduped[deduped.length - 1];
|
|
285
|
+
if (last === undefined || Math.hypot(p[0] - last[0], p[1] - last[1]) > 1e-9)
|
|
286
|
+
deduped.push(p);
|
|
287
|
+
}
|
|
288
|
+
// Drop a trailing point coincident with the first (the loop closes implicitly).
|
|
289
|
+
if (deduped.length > 1) {
|
|
290
|
+
const first = deduped[0];
|
|
291
|
+
const last = deduped[deduped.length - 1];
|
|
292
|
+
if (Math.hypot(first[0] - last[0], first[1] - last[1]) < 1e-9)
|
|
293
|
+
deduped.pop();
|
|
294
|
+
}
|
|
295
|
+
if (deduped.length < 3) {
|
|
296
|
+
return err(validationError('LOFTED_LOOP_TOO_SMALL', `developed loop has ${deduped.length} points, need ≥ 3`));
|
|
297
|
+
}
|
|
298
|
+
const edges = [];
|
|
299
|
+
for (let i = 0; i < deduped.length; i += 1) {
|
|
300
|
+
const p = deduped[i];
|
|
301
|
+
const next = deduped[(i + 1) % deduped.length];
|
|
302
|
+
edges.push(line([p[0], p[1], 0], [next[0], next[1], 0]));
|
|
303
|
+
}
|
|
304
|
+
return wireLoop(edges);
|
|
305
|
+
}
|
|
306
|
+
//# sourceMappingURL=loftedFlangeFns.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loftedFlangeFns.js","sourceRoot":"","sources":["../src/loftedFlangeFns.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type Result,\n type Vec3,\n type Wire,\n type Solid,\n ok,\n err,\n validationError,\n line,\n wireLoop,\n extrude,\n face,\n fuse,\n isPlanarWire,\n isSolid,\n getSolids,\n} from '@faicad/faijs/brepjs-compat';\nimport type { LoftedFlangeFeature, LoftedFlangeSpec, SheetMetalPart } from './types.js';\n\ntype Pt2 = [number, number];\n\n/**\n * Relative out-of-plane tolerance for the per-quad developability test: a quad is\n * twisted (non-developable) when its far corner sits off the diagonal-triangle plane\n * by more than this fraction of the quad's own size. Relative — not absolute — so a\n * genuinely flat quad is not false-flagged on a large part (numerical noise scales\n * with coordinates), nor a real twist missed on a tiny one.\n */\nconst DEVELOPABLE_REL_TOL = 1e-6;\n/** Floor on the size scale so a degenerate (near-zero-size) quad keeps a sane tolerance. */\nconst DEVELOPABLE_MIN_SCALE = 1e-9;\n\n/**\n * Author a lofted / ruled transition flange between two parallel OPEN profiles.\n * The two profiles are lofted (ruled, straight generators) into a transition surface\n * and thickened to a valid solid; the result is fused onto the part. The developed\n * pattern is produced by TRIANGULATION (the standard transition development): each\n * quad between consecutive vertex pairs is split into two triangles laid flat\n * edge-length-preserving and accumulated into a developed boundary.\n *\n * The triangulated development is EXACT (to tolerance) when the ruled surface is\n * developable — every quad is planar, so the two triangles share their diagonal in\n * 3D and the flat layout preserves all lengths and angles. When a quad is non-planar\n * (twisted ruling) the surface is not developable and the flat layout is an\n * approximation; {@link approximate} is set and the unfold emits a\n * `DEVELOPMENT_APPROXIMATE` warning.\n *\n * @param part - the sheet metal part to attach a lofted flange to.\n * @param spec - the lofted flange specification.\n * @returns the updated part with the lofted flange feature recorded, or an error.\n */\nexport function authorLoftedFlange(\n part: SheetMetalPart,\n spec: LoftedFlangeSpec\n): Result<SheetMetalPart> {\n if (spec.id === '' || spec.id.includes('::')) {\n return err(\n validationError('INVALID_LOFTED_ID', `lofted flange id must be non-empty and must not contain '::', got '${spec.id}'`)\n );\n }\n for (const existing of part.loftedFlanges ?? []) {\n if (existing.id === spec.id) {\n return err(validationError('DUPLICATE_LOFTED', `duplicate lofted flange id '${spec.id}'`));\n }\n }\n if (spec.profileA.length < 2 || spec.profileB.length < 2) {\n return err(\n validationError('INVALID_LOFTED_PROFILE', `lofted flange '${spec.id}' profiles need ≥ 2 points each`)\n );\n }\n if (spec.profileA.length !== spec.profileB.length) {\n return err(\n validationError('PROFILE_VERTEX_MISMATCH', `lofted flange '${spec.id}' profiles must have equal vertex counts (${spec.profileA.length} vs ${spec.profileB.length}) to pair into ruling triangles`)\n );\n }\n if (!Number.isFinite(spec.height) || spec.height <= 0) {\n return err(validationError('INVALID_LOFTED_HEIGHT', `lofted flange '${spec.id}' height must be positive, got ${spec.height}`));\n }\n\n const thickness = spec.thickness ?? part.thickness;\n if (!Number.isFinite(thickness) || thickness <= 0) {\n return err(validationError('INVALID_THICKNESS', `lofted flange '${spec.id}' thickness must be positive`));\n }\n\n // 3D profile points: profileA in the z=0 plane, profileB at z=height.\n const a3: Vec3[] = spec.profileA.map(([x, y]) => [x, y, 0]);\n const b3: Vec3[] = spec.profileB.map(([x, y]) => [x, y, spec.height]);\n\n const solidResult = buildLoftedSolid(spec.id, a3, b3, thickness);\n if (!solidResult.ok) return solidResult;\n\n const dev = developRuled(a3, b3);\n const loopResult = closedLoopWire(dev.loop);\n if (!loopResult.ok) return loopResult;\n\n const feature: LoftedFlangeFeature = {\n id: spec.id,\n developedLoop: dev.loop,\n developedArea: dev.area,\n approximate: dev.approximate,\n };\n\n // The lofted transition is an independent body fused to the part; like a contour\n // flange it carries no straight-bend tree entry (its development is the\n // triangulated loop, not a rectilinear strip).\n if (part.solid === undefined) {\n return ok({\n ...part,\n solid: solidResult.value,\n loftedFlanges: [...(part.loftedFlanges ?? []), feature],\n });\n }\n const fused = fuse(part.solid, solidResult.value);\n if (!fused.ok) return fused;\n\n return ok({\n ...part,\n solid: normalize(fused.value),\n loftedFlanges: [...(part.loftedFlanges ?? []), feature],\n });\n}\n\nfunction normalize(shape: Solid): Solid {\n if (isSolid(shape)) return shape;\n const solids = getSolids(shape);\n return solids.length === 1 ? (solids[0] as Solid) : shape;\n}\n\n/**\n * Build the thickened ruled transition solid as the union of per-quad plates. Each\n * quad of the ruled surface is triangulated and each triangle extruded by\n * `thickness` along its own normal, then all plates are fused. This per-triangle\n * construction is robust on OCCT-WASM for both planar (developable) and twisted\n * (non-developable) transitions, where a single lofted-then-shelled surface can fail\n * to sew.\n */\nfunction buildLoftedSolid(id: string, a: Vec3[], b: Vec3[], thickness: number): Result<Solid> {\n let solid: Solid | undefined;\n for (let i = 0; i + 1 < a.length; i += 1) {\n const a0 = a[i];\n const a1 = a[i + 1];\n const b0 = b[i];\n const b1 = b[i + 1];\n if (a0 === undefined || a1 === undefined || b0 === undefined || b1 === undefined) {\n return err(validationError('LOFTED_QUAD_FAILED', `lofted flange '${id}' failed to index quad ${i}`));\n }\n const plate = buildQuadPlate(id, a0, a1, b1, b0, thickness);\n if (!plate.ok) return plate;\n if (solid === undefined) {\n solid = plate.value;\n } else {\n const fused = fuse(solid, plate.value);\n if (!fused.ok) return fused;\n solid = normalize(fused.value);\n }\n }\n if (solid === undefined) {\n return err(validationError('LOFTED_EMPTY', `lofted flange '${id}' produced no quads`));\n }\n return ok(solid);\n}\n\n/**\n * One quad of the ruled surface (corners a0→a1→b1→b0), thickened into a plate. The\n * quad is split along its a0–b1 diagonal into two triangles lofted to the same\n * triangles offset by `thickness` along the quad normal — robust on a non-planar\n * (twisted) quad where a single planar face would fail. We loft each triangle face\n * pair; for the common planar quad this is exact, for a twisted quad it is the\n * faceted plate matching the triangulated development.\n */\nfunction buildQuadPlate(id: string, a0: Vec3, a1: Vec3, b1: Vec3, b0: Vec3, thickness: number): Result<Solid> {\n const t1 = buildTrianglePlate(id, a0, a1, b1, thickness);\n if (!t1.ok) return t1;\n const t2 = buildTrianglePlate(id, a0, b1, b0, thickness);\n if (!t2.ok) return t2;\n const fused = fuse(t1.value, t2.value);\n if (!fused.ok) return fused;\n return ok(normalize(fused.value));\n}\n\n/** A flat triangle (p0,p1,p2) extruded by `thickness` along its own normal. */\nfunction buildTrianglePlate(id: string, p0: Vec3, p1: Vec3, p2: Vec3, thickness: number): Result<Solid> {\n const e0 = line(p0, p1);\n const e1 = line(p1, p2);\n const e2 = line(p2, p0);\n const loopResult = wireLoop([e0, e1, e2]);\n if (!loopResult.ok) return loopResult;\n const loop = loopResult.value;\n if (!isPlanarWire(loop)) {\n return err(validationError('LOFTED_TRIANGLE_NONPLANAR', `lofted flange '${id}' triangle is not planar`));\n }\n const faceResult = face(loop);\n if (!faceResult.ok) return faceResult;\n const n = triangleNormal(p0, p1, p2);\n const dir: Vec3 = [n[0] * thickness, n[1] * thickness, n[2] * thickness];\n const extruded = extrude(faceResult.value, dir);\n if (!extruded.ok) return extruded;\n return ok(normalize(extruded.value));\n}\n\nfunction triangleNormal(p0: Vec3, p1: Vec3, p2: Vec3): Vec3 {\n const u: Vec3 = [p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2]];\n const v: Vec3 = [p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2]];\n const c: Vec3 = [u[1] * v[2] - u[2] * v[1], u[2] * v[0] - u[0] * v[2], u[0] * v[1] - u[1] * v[0]];\n const len = Math.hypot(c[0], c[1], c[2]) || 1;\n return [c[0] / len, c[1] / len, c[2] / len];\n}\n\ninterface Development {\n loop: Pt2[];\n area: number;\n approximate: boolean;\n}\n\n/**\n * Triangulated development of a ruled transition between two equal-length vertex\n * lists `a` (z=0) and `b` (z=height). Each quad (a[i],a[i+1],b[i+1],b[i]) is split\n * along the a[i]–b[i+1] diagonal into two triangles; the triangles are laid out\n * flat one strip at a time, hinging each new triangle onto the previously-placed\n * shared edge with its true 3D edge lengths preserved. The developed boundary is the\n * `a` chain laid along the bottom and the `b` chain returning along the top.\n *\n * Developable check: a quad is developable iff its two triangles, sharing the\n * diagonal, are coplanar — equivalently the far corner b[i] lies in the plane of\n * (a[i],a[i+1],b[i+1]). Any quad failing this within tolerance flags the whole\n * development approximate.\n */\nfunction developRuled(a: Vec3[], b: Vec3[]): Development {\n const aFlat: Pt2[] = [];\n const bFlat: Pt2[] = [];\n let area = 0;\n let approximate = false;\n\n // Place the first ruling (a[0]–b[0]) vertically: a[0] at origin, b[0] straight up\n // by its true 3D length, so the developed strip grows along +x.\n const d0 = dist(a[0] as Vec3, b[0] as Vec3);\n let aPrev: Pt2 = [0, 0];\n let bPrev: Pt2 = [0, d0];\n aFlat.push(aPrev);\n bFlat.push(bPrev);\n\n for (let i = 0; i + 1 < a.length; i += 1) {\n const A0 = a[i] as Vec3;\n const A1 = a[i + 1] as Vec3;\n const B0 = b[i] as Vec3;\n const B1 = b[i + 1] as Vec3;\n\n // True 3D edge lengths of this quad. Diagonal A0–B1 splits it into\n // T1=(A0,A1,B1) and T2=(A0,B1,B0).\n const lBottom = dist(A0, A1); // A0→A1\n const lTop = dist(B0, B1); // B0→B1\n const lDiag = dist(A0, B1); // shared diagonal A0→B1\n const lRight = dist(A1, B1); // right ruling A1→B1 (= next quad's left ruling)\n\n // Developable test: is B0 coplanar with (A0, A1, B1)? If not, the quad is\n // twisted and the flat layout cannot preserve all edges — mark approximate.\n // Tolerance is relative to the quad's size so the check is scale-invariant.\n const scale = Math.max(lBottom, lTop, lDiag, lRight, DEVELOPABLE_MIN_SCALE);\n if (planeDistance(A0, A1, B1, B0) > DEVELOPABLE_REL_TOL * scale) approximate = true;\n\n // Place B1 from the known A0(=aPrev, dist=lDiag) and B0(=bPrev, dist=lTop).\n const b1Flat = placeFrom(aPrev, bPrev, lDiag, lTop);\n // Place A1 from A0(=aPrev, dist=lBottom) and the just-placed B1(dist=lRight),\n // preserving the right ruling so it equals the next quad's left ruling exactly.\n const a1Flat = placeFrom(aPrev, b1Flat, lBottom, lRight);\n\n aFlat.push(a1Flat);\n bFlat.push(b1Flat);\n\n area += triArea3(A0, A1, B1) + triArea3(A0, B1, B0);\n aPrev = a1Flat;\n bPrev = b1Flat;\n }\n\n // Developed boundary: a-chain forward, b-chain back.\n const loop: Pt2[] = [...aFlat, ...[...bFlat].reverse()];\n return { loop, area, approximate };\n}\n\n/**\n * Place a new point at distance `r1` from `p1` and `r2` from `p2` in 2D, choosing\n * the solution on the +x side of the directed line p1→p2 (the strip grows toward +x,\n * so successive rulings advance rightward). Falls back to the midpoint if the circles\n * do not intersect (degenerate strip).\n */\nfunction placeFrom(p1: Pt2, p2: Pt2, r1: number, r2: number): Pt2 {\n const dx = p2[0] - p1[0];\n const dy = p2[1] - p1[1];\n const d = Math.hypot(dx, dy);\n if (d < 1e-12) return [p1[0] + r1, p1[1]];\n const a = (r1 * r1 - r2 * r2 + d * d) / (2 * d);\n const h2 = r1 * r1 - a * a;\n const h = h2 > 0 ? Math.sqrt(h2) : 0;\n const mx = p1[0] + (a * dx) / d;\n const my = p1[1] + (a * dy) / d;\n // Perpendicular toward +x: pick the offset whose x is larger.\n const ox = (-dy / d) * h;\n const oy = (dx / d) * h;\n const s1: Pt2 = [mx + ox, my + oy];\n const s2: Pt2 = [mx - ox, my - oy];\n return s1[0] >= s2[0] ? s1 : s2;\n}\n\nfunction dist(p: Vec3, q: Vec3): number {\n return Math.hypot(p[0] - q[0], p[1] - q[1], p[2] - q[2]);\n}\n\nfunction triArea3(p0: Vec3, p1: Vec3, p2: Vec3): number {\n const u: Vec3 = [p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2]];\n const v: Vec3 = [p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2]];\n const c: Vec3 = [u[1] * v[2] - u[2] * v[1], u[2] * v[0] - u[0] * v[2], u[0] * v[1] - u[1] * v[0]];\n return Math.hypot(c[0], c[1], c[2]) / 2;\n}\n\n/** Perpendicular distance of `q` from the plane through (p0,p1,p2). */\nfunction planeDistance(p0: Vec3, p1: Vec3, p2: Vec3, q: Vec3): number {\n const u: Vec3 = [p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2]];\n const v: Vec3 = [p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2]];\n const c: Vec3 = [u[1] * v[2] - u[2] * v[1], u[2] * v[0] - u[0] * v[2], u[0] * v[1] - u[1] * v[0]];\n const len = Math.hypot(c[0], c[1], c[2]);\n if (len < 1e-12) return 0;\n const w: Vec3 = [q[0] - p0[0], q[1] - p0[1], q[2] - p0[2]];\n return Math.abs((w[0] * c[0] + w[1] * c[1] + w[2] * c[2]) / len);\n}\n\n/** Trace a closed developed-plane loop (≥ 3 points) into a morph {@link Wire}. */\nfunction closedLoopWire(loop: Pt2[]): Result<Wire> {\n const deduped: Pt2[] = [];\n for (const p of loop) {\n const last = deduped[deduped.length - 1];\n if (last === undefined || Math.hypot(p[0] - last[0], p[1] - last[1]) > 1e-9) deduped.push(p);\n }\n // Drop a trailing point coincident with the first (the loop closes implicitly).\n if (deduped.length > 1) {\n const first = deduped[0] as Pt2;\n const last = deduped[deduped.length - 1] as Pt2;\n if (Math.hypot(first[0] - last[0], first[1] - last[1]) < 1e-9) deduped.pop();\n }\n if (deduped.length < 3) {\n return err(validationError('LOFTED_LOOP_TOO_SMALL', `developed loop has ${deduped.length} points, need ≥ 3`));\n }\n const edges = [];\n for (let i = 0; i < deduped.length; i += 1) {\n const p = deduped[i] as Pt2;\n const next = deduped[(i + 1) % deduped.length] as Pt2;\n edges.push(line([p[0], p[1], 0], [next[0], next[1], 0]));\n }\n return wireLoop(edges);\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { MaterialSpec } from './types.js';
|
|
2
|
+
/** Default K-factor applied when a material does not specify one. */
|
|
3
|
+
export declare const DEFAULT_K_FACTOR = 0.44;
|
|
4
|
+
/** Standard steel sheet gauges, keyed by gauge name. */
|
|
5
|
+
export declare const STEEL_GAUGES: Readonly<Record<string, MaterialSpec>>;
|
|
6
|
+
/** Standard aluminum sheet gauges, keyed by gauge name. */
|
|
7
|
+
export declare const ALUMINUM_GAUGES: Readonly<Record<string, MaterialSpec>>;
|
|
8
|
+
/** All available materials, merged from the steel and aluminum gauge sets. */
|
|
9
|
+
export declare const MATERIALS: Readonly<Record<string, MaterialSpec>>;
|
|
10
|
+
/**
|
|
11
|
+
* Look up a material by its name.
|
|
12
|
+
* @param name - The material name to look up.
|
|
13
|
+
* @returns The matching material spec, or undefined if not found.
|
|
14
|
+
*/
|
|
15
|
+
export declare function getMaterial(name: string): MaterialSpec | undefined;
|
|
16
|
+
//# sourceMappingURL=materials.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"materials.d.ts","sourceRoot":"","sources":["../src/materials.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,qEAAqE;AACrE,eAAO,MAAM,gBAAgB,OAAO,CAAC;AAUrC,wDAAwD;AACxD,eAAO,MAAM,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAS/D,CAAC;AAEF,2DAA2D;AAC3D,eAAO,MAAM,eAAe,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAQlE,CAAC;AAEF,8EAA8E;AAC9E,eAAO,MAAM,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAG5D,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAElE"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** Default K-factor applied when a material does not specify one. */
|
|
2
|
+
export const DEFAULT_K_FACTOR = 0.44;
|
|
3
|
+
function gauge(name, thickness, kFactor = DEFAULT_K_FACTOR) {
|
|
4
|
+
return {
|
|
5
|
+
name,
|
|
6
|
+
thickness,
|
|
7
|
+
defaultRule: { innerRadius: thickness, kFactor },
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
/** Standard steel sheet gauges, keyed by gauge name. */
|
|
11
|
+
export const STEEL_GAUGES = {
|
|
12
|
+
'steel-24ga': gauge('steel-24ga', 0.61),
|
|
13
|
+
'steel-22ga': gauge('steel-22ga', 0.76),
|
|
14
|
+
'steel-20ga': gauge('steel-20ga', 0.91),
|
|
15
|
+
'steel-18ga': gauge('steel-18ga', 1.21),
|
|
16
|
+
'steel-16ga': gauge('steel-16ga', 1.52),
|
|
17
|
+
'steel-14ga': gauge('steel-14ga', 1.9),
|
|
18
|
+
'steel-12ga': gauge('steel-12ga', 2.66),
|
|
19
|
+
'steel-10ga': gauge('steel-10ga', 3.42),
|
|
20
|
+
};
|
|
21
|
+
/** Standard aluminum sheet gauges, keyed by gauge name. */
|
|
22
|
+
export const ALUMINUM_GAUGES = {
|
|
23
|
+
'aluminum-22ga': gauge('aluminum-22ga', 0.64),
|
|
24
|
+
'aluminum-20ga': gauge('aluminum-20ga', 0.81),
|
|
25
|
+
'aluminum-18ga': gauge('aluminum-18ga', 1.02),
|
|
26
|
+
'aluminum-16ga': gauge('aluminum-16ga', 1.29),
|
|
27
|
+
'aluminum-14ga': gauge('aluminum-14ga', 1.63),
|
|
28
|
+
'aluminum-12ga': gauge('aluminum-12ga', 2.05),
|
|
29
|
+
'aluminum-10ga': gauge('aluminum-10ga', 2.59),
|
|
30
|
+
};
|
|
31
|
+
/** All available materials, merged from the steel and aluminum gauge sets. */
|
|
32
|
+
export const MATERIALS = {
|
|
33
|
+
...STEEL_GAUGES,
|
|
34
|
+
...ALUMINUM_GAUGES,
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Look up a material by its name.
|
|
38
|
+
* @param name - The material name to look up.
|
|
39
|
+
* @returns The matching material spec, or undefined if not found.
|
|
40
|
+
*/
|
|
41
|
+
export function getMaterial(name) {
|
|
42
|
+
return MATERIALS[name];
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=materials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"materials.js","sourceRoot":"","sources":["../src/materials.ts"],"names":[],"mappings":"AAEA,qEAAqE;AACrE,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAErC,SAAS,KAAK,CAAC,IAAY,EAAE,SAAiB,EAAE,OAAO,GAAG,gBAAgB;IACxE,OAAO;QACL,IAAI;QACJ,SAAS;QACT,WAAW,EAAE,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE;KACjD,CAAC;AACJ,CAAC;AAED,wDAAwD;AACxD,MAAM,CAAC,MAAM,YAAY,GAA2C;IAClE,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,IAAI,CAAC;IACvC,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,IAAI,CAAC;IACvC,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,IAAI,CAAC;IACvC,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,IAAI,CAAC;IACvC,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,IAAI,CAAC;IACvC,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,GAAG,CAAC;IACtC,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,IAAI,CAAC;IACvC,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,IAAI,CAAC;CACxC,CAAC;AAEF,2DAA2D;AAC3D,MAAM,CAAC,MAAM,eAAe,GAA2C;IACrE,eAAe,EAAE,KAAK,CAAC,eAAe,EAAE,IAAI,CAAC;IAC7C,eAAe,EAAE,KAAK,CAAC,eAAe,EAAE,IAAI,CAAC;IAC7C,eAAe,EAAE,KAAK,CAAC,eAAe,EAAE,IAAI,CAAC;IAC7C,eAAe,EAAE,KAAK,CAAC,eAAe,EAAE,IAAI,CAAC;IAC7C,eAAe,EAAE,KAAK,CAAC,eAAe,EAAE,IAAI,CAAC;IAC7C,eAAe,EAAE,KAAK,CAAC,eAAe,EAAE,IAAI,CAAC;IAC7C,eAAe,EAAE,KAAK,CAAC,eAAe,EAAE,IAAI,CAAC;CAC9C,CAAC;AAEF,8EAA8E;AAC9E,MAAM,CAAC,MAAM,SAAS,GAA2C;IAC/D,GAAG,YAAY;IACf,GAAG,eAAe;CACnB,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC;AACzB,CAAC","sourcesContent":["import type { MaterialSpec } from './types.js';\n\n/** Default K-factor applied when a material does not specify one. */\nexport const DEFAULT_K_FACTOR = 0.44;\n\nfunction gauge(name: string, thickness: number, kFactor = DEFAULT_K_FACTOR): MaterialSpec {\n return {\n name,\n thickness,\n defaultRule: { innerRadius: thickness, kFactor },\n };\n}\n\n/** Standard steel sheet gauges, keyed by gauge name. */\nexport const STEEL_GAUGES: Readonly<Record<string, MaterialSpec>> = {\n 'steel-24ga': gauge('steel-24ga', 0.61),\n 'steel-22ga': gauge('steel-22ga', 0.76),\n 'steel-20ga': gauge('steel-20ga', 0.91),\n 'steel-18ga': gauge('steel-18ga', 1.21),\n 'steel-16ga': gauge('steel-16ga', 1.52),\n 'steel-14ga': gauge('steel-14ga', 1.9),\n 'steel-12ga': gauge('steel-12ga', 2.66),\n 'steel-10ga': gauge('steel-10ga', 3.42),\n};\n\n/** Standard aluminum sheet gauges, keyed by gauge name. */\nexport const ALUMINUM_GAUGES: Readonly<Record<string, MaterialSpec>> = {\n 'aluminum-22ga': gauge('aluminum-22ga', 0.64),\n 'aluminum-20ga': gauge('aluminum-20ga', 0.81),\n 'aluminum-18ga': gauge('aluminum-18ga', 1.02),\n 'aluminum-16ga': gauge('aluminum-16ga', 1.29),\n 'aluminum-14ga': gauge('aluminum-14ga', 1.63),\n 'aluminum-12ga': gauge('aluminum-12ga', 2.05),\n 'aluminum-10ga': gauge('aluminum-10ga', 2.59),\n};\n\n/** All available materials, merged from the steel and aluminum gauge sets. */\nexport const MATERIALS: Readonly<Record<string, MaterialSpec>> = {\n ...STEEL_GAUGES,\n ...ALUMINUM_GAUGES,\n};\n\n/**\n * Look up a material by its name.\n * @param name - The material name to look up.\n * @returns The matching material spec, or undefined if not found.\n */\nexport function getMaterial(name: string): MaterialSpec | undefined {\n return MATERIALS[name];\n}\n"]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type Result, type Vec3 } from '@faicad/faijs/brepjs-compat';
|
|
2
|
+
import type { SheetMetalPart } from './types.js';
|
|
3
|
+
/** An oriented cutting plane: material on the `+normal` side is removed. */
|
|
4
|
+
export interface MiterPlane {
|
|
5
|
+
origin: Vec3;
|
|
6
|
+
normal: Vec3;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* General miter primitive: cut a sheet-metal part by a plane, removing the
|
|
10
|
+
* material on the `+normal` side. The tool is a half-space block sized to the
|
|
11
|
+
* part's bounding box, so the cut is exact regardless of part extent.
|
|
12
|
+
*
|
|
13
|
+
* @param part - the sheet metal part to cut.
|
|
14
|
+
* @param plane - the miter plane definition.
|
|
15
|
+
* @returns the updated part with the miter cut applied, or an error.
|
|
16
|
+
*/
|
|
17
|
+
export declare function miterCut(part: SheetMetalPart, plane: MiterPlane): Result<SheetMetalPart>;
|
|
18
|
+
/**
|
|
19
|
+
* Auto corner-miter between two adjacent flanges. The miter plane bisects the two
|
|
20
|
+
* flanges' fold-up directions at their shared corner and is offset by half the gap,
|
|
21
|
+
* so the cut falls on the flat/flange regions and never crosses a bend patch. The
|
|
22
|
+
* single bisector cut trims both flanges to a clean mitered corner.
|
|
23
|
+
*
|
|
24
|
+
* @param part - the sheet metal part to miter.
|
|
25
|
+
* @param flangeIdA - the first flange ID.
|
|
26
|
+
* @param flangeIdB - the second flange ID.
|
|
27
|
+
* @param gap - the gap between flanges (default 0).
|
|
28
|
+
* @returns the updated part with the miter corner applied, or an error.
|
|
29
|
+
*/
|
|
30
|
+
export declare function autoMiterCorner(part: SheetMetalPart, flangeIdA: string, flangeIdB: string, gap?: number): Result<SheetMetalPart>;
|
|
31
|
+
//# sourceMappingURL=miterFns.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"miterFns.d.ts","sourceRoot":"","sources":["../src/miterFns.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,MAAM,EACX,KAAK,IAAI,EAgBV,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGjD,4EAA4E;AAC5E,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,IAAI,CAAC;IACb,MAAM,EAAE,IAAI,CAAC;CACd;AAED;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,UAAU,GAAG,MAAM,CAAC,cAAc,CAAC,CAYxF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,cAAc,EACpB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,GAAG,SAAI,GACN,MAAM,CAAC,cAAc,CAAC,CAqCxB"}
|