brepjs-sheetmetal 0.1.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/LICENSE +191 -0
- package/README.md +415 -0
- package/dist/allowanceFns.d.ts +21 -0
- package/dist/api.d.ts +128 -0
- package/dist/authorFns.d.ts +100 -0
- package/dist/bendTableFns.d.ts +58 -0
- package/dist/brepjs-sheetmetal.cjs +7321 -0
- package/dist/brepjs-sheetmetal.js +7233 -0
- package/dist/contourFlangeFns.d.ts +74 -0
- package/dist/cutoutFns.d.ts +52 -0
- package/dist/dxfFns.d.ts +38 -0
- package/dist/facade.d.ts +175 -0
- package/dist/featureTreeFns.d.ts +55 -0
- package/dist/foldFns.d.ts +68 -0
- package/dist/foreignUnfoldFns.d.ts +54 -0
- package/dist/formFns.d.ts +43 -0
- package/dist/hemFns.d.ts +12 -0
- package/dist/index.d.ts +35 -0
- package/dist/internal.d.ts +17 -0
- package/dist/jogFns.d.ts +13 -0
- package/dist/loftedFlangeFns.d.ts +18 -0
- package/dist/materials.d.ts +6 -0
- package/dist/miterFns.d.ts +20 -0
- package/dist/nestFns.d.ts +95 -0
- package/dist/polygonFns.d.ts +48 -0
- package/dist/reliefFns.d.ts +28 -0
- package/dist/reportFns.d.ts +19 -0
- package/dist/tabFns.d.ts +33 -0
- package/dist/types.d.ts +608 -0
- package/dist/unfoldFns.d.ts +80 -0
- package/dist/validateFns.d.ts +14 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to the Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by the Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding any notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
Copyright 2025-2026 Andy Aragon
|
|
180
|
+
|
|
181
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
182
|
+
you may not use this file except in compliance with the License.
|
|
183
|
+
You may obtain a copy of the License at
|
|
184
|
+
|
|
185
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
186
|
+
|
|
187
|
+
Unless required by applicable law or agreed to in writing, software
|
|
188
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
189
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
190
|
+
See the License for the specific language governing permissions and
|
|
191
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
# brepjs-sheetmetal
|
|
2
|
+
|
|
3
|
+
> Experimental, unpublished satellite package.
|
|
4
|
+
|
|
5
|
+
A sheet-metal CAD domain for [brepjs](https://github.com/andymai/brepjs). It authors parametric
|
|
6
|
+
sheet-metal parts (flange/bend features), auto-miters corners, folds to 3D, and unfolds to a flat
|
|
7
|
+
pattern for fabrication.
|
|
8
|
+
|
|
9
|
+
Pipeline: **author part → auto-miter corners → fold to 3D → unfold to flat pattern → export DXF/STEP/GLB + bend report.**
|
|
10
|
+
|
|
11
|
+
## Scope
|
|
12
|
+
|
|
13
|
+
Straight (cylindrical) bends, authoring our own parts (not unfolding foreign solids). The bend model is
|
|
14
|
+
K-factor based by default, with optional **shop bend tables** (see below) for parts that develop per a
|
|
15
|
+
press-brake's measured allowance/deduction values.
|
|
16
|
+
|
|
17
|
+
- Bend allowance: `BA = (π/180)·|angle|·(R + K·T)`, or interpolated from a referenced bend table.
|
|
18
|
+
- Defaults: units are mm; K-factor `0.44`; inner radius `= thickness`.
|
|
19
|
+
|
|
20
|
+
Authoring supports arbitrary bend trees: flanges off any of the four base edges, **chained
|
|
21
|
+
flange-off-flange** bends (U-channels, Z-profiles, box walls), **up/down** fold direction, and
|
|
22
|
+
**partial/offset** flanges (more than one flange per edge). Closed profiles (tubes/boxes) author a seam
|
|
23
|
+
edge that the unfold leaves uncut as a `SEAM_CUT`, flattening into a valid connected pattern.
|
|
24
|
+
|
|
25
|
+
## Status
|
|
26
|
+
|
|
27
|
+
| Area | State |
|
|
28
|
+
| --------------- | -------------------------------------------------------------------------------------------- |
|
|
29
|
+
| Authoring | 4-edge flanges, chained bends, up/down, partial/offset flanges, closed-box seams |
|
|
30
|
+
| Unfold | recursive BFS tree-walk → rectilinear-union flat pattern + bend lines + developed area |
|
|
31
|
+
| Fold | `FlatInput` region-tree → 3D part (inverse of unfold); round-trips `unfold(fold)` |
|
|
32
|
+
| Reliefs | bend reliefs (slots at partial-flange bend-line ends), corner reliefs (notch at a corner) |
|
|
33
|
+
| Cutouts | holes / slots (rect + obround) / polygons punched on the base or a folded flange |
|
|
34
|
+
| Tabs / joints | additive edge tabs, self-fixturing tab-and-slot joints (tab + matching mating slot) |
|
|
35
|
+
| Form features | louvers (3-side cut + formed flap), embosses / dimples (round raised / recessed forms) |
|
|
36
|
+
| Contour flange | open line/arc profile swept along a base edge (multi-bend section); EXACT development |
|
|
37
|
+
| Lofted flange | ruled transition between two open profiles; triangulated development (exact if developable) |
|
|
38
|
+
| Hems | edge folded back ~180°+ (closed / open / teardrop / rolled); EXACT development |
|
|
39
|
+
| Jogs | two opposite bends stepping a flat by a Z-offset (joggle); EXACT development |
|
|
40
|
+
| Bend tables | shop allowance/deduction tables, angle-linear + thickness×radius-bilinear interp, clamp-warn |
|
|
41
|
+
| Miter / outputs | auto corner-miter, multi-layer DXF (incl. FORM layer), JSON bend report, warnings |
|
|
42
|
+
| API | functional `*Fns` → short-named `api.ts` → fluent `sheetMetal()` facade |
|
|
43
|
+
|
|
44
|
+
`fold(input: FlatInput)` folds a flat pattern back up into a 3D part, the inverse of `unfold`. A
|
|
45
|
+
`FlatInput` is a region-tree (a base rectangle plus child fold regions, each with a fold line, angle,
|
|
46
|
+
direction, and bend rule), the inverse of the unfold layout. `patternToFlatInput(pattern, { thickness,
|
|
47
|
+
ruleFor })` recovers that region-tree from the **2D flat-pattern geometry alone** (the developed outline
|
|
48
|
+
wire and bend-line edges), reading real coordinates back out with the public `getEdges` / `curveStartPoint`
|
|
49
|
+
/ `curveEndPoint` readers; only the bend rule (which a flat pattern cannot encode) is a supplied input.
|
|
50
|
+
`partToFlatInput(part)` chains `unfold` → `patternToFlatInput`, so `fold(partToFlatInput(part))` round-trips
|
|
51
|
+
volume, validity, and bend/flange counts through the 2D geometry, making the round-trip a genuine,
|
|
52
|
+
non-circular oracle. Fold reuses the forward authoring bend geometry wholesale (no duplicated bend math),
|
|
53
|
+
and rides SEAM_CUT / MIN_RADIUS warnings inside the Ok payload.
|
|
54
|
+
|
|
55
|
+
## Reliefs
|
|
56
|
+
|
|
57
|
+
Multi-bend parts tear or collide at corners unless material is relieved. Reliefs are recorded features
|
|
58
|
+
(like corner miters): cut from the folded 3D solid and replayed by `unfold` as 2D notches subtracted from
|
|
59
|
+
the developed outline (so they ride in the DXF `OUTLINE` layer and shrink `developedArea`).
|
|
60
|
+
|
|
61
|
+
- `addBendRelief(part, flangeId, spec?)` cuts a slot at each **mid-edge end** of a partial/offset flange's
|
|
62
|
+
bend line, the ends that don't reach the parent-edge endpoint, where the parent material would tear.
|
|
63
|
+
`autoBendReliefs(part, spec?)` adds one to every partial-span flange (full-span flanges are skipped).
|
|
64
|
+
- `cornerRelief(part, flangeIdA, flangeIdB, spec?)` cuts a square notch centred on the shared corner of two
|
|
65
|
+
adjacent flanges, the notch alternative to a 45° miter. The square side is `spec.width` when given, else
|
|
66
|
+
the depth clearance. It records the corner as resolved, so the `COLLISION` warning the un-relieved corner
|
|
67
|
+
raised goes away.
|
|
68
|
+
|
|
69
|
+
A `ReliefSpec` is `{ shape: 'rectangular' | 'obround'; width?; depth? }`. Defaults: `width ≈ thickness`,
|
|
70
|
+
`depth ≈ developedLength(bend) + thickness`. `obround` records the rounded-slot intent; the developed notch
|
|
71
|
+
and 3D cut are rectangular for both shapes in this version. Reliefs round-trip through `fold` via a
|
|
72
|
+
`FoldRegion.bendRelief` field; the strict geometric round-trip oracle (`partToFlatInput → fold`) skips
|
|
73
|
+
relief'd parts because a notched outline is not a plain rectangle the parser can re-derive (same as mitered
|
|
74
|
+
parts).
|
|
75
|
+
|
|
76
|
+
## Cutouts
|
|
77
|
+
|
|
78
|
+
Holes, slots, and polygon cutouts are 2D features punched through a flat region's thickness. They survive
|
|
79
|
+
fold/unfold: each appears in the 3D solid, in the flat pattern (`FlatPattern.holes`, one closed wire per
|
|
80
|
+
cutout), and in the DXF (a `CUTOUT` layer). The outer outline is unchanged; cutouts are interior loops.
|
|
81
|
+
|
|
82
|
+
A `CutoutSpec` names the `region` (a flange id, or `'base'`/`'root'` for the base flat) and places the
|
|
83
|
+
feature in that region's **local** frame: `(0, 0)` is the region's near corner, `+x` runs along the region's
|
|
84
|
+
`u` axis and `+y` along its `v` axis. The discriminated union is `hole` (`{ x, y, diameter }`), `slot`
|
|
85
|
+
(`{ x, y, length, width, angleDeg?, round? }`, where `round` makes the ends semicircular/obround), or `polygon`
|
|
86
|
+
(`{ points: [x, y][] }`).
|
|
87
|
+
|
|
88
|
+
- `addCutout(part, spec)` / `addHole(part, region, x, y, diameter)` / `addSlot(part, region, opts)` /
|
|
89
|
+
`addPolygonCutout(part, region, points)` cut the feature through the sheet on the **correct face**: the 2D
|
|
90
|
+
profile is built in region-local coords, then placed via the region's world frame (origin/u/v/n), so a
|
|
91
|
+
hole authored on a folded flange lands on the flange face, not the base plane. Rejects a feature outside
|
|
92
|
+
the region (`CUTOUT_OUT_OF_BOUNDS`) and guards a valid, single-bodied result (`CUTOUT_SEVERED_SOLID`).
|
|
93
|
+
- `unfold` emits the matching developed loop at the cutout's flat-pattern location (mapped through the same
|
|
94
|
+
region's developed frame) and drops `developedArea` by the cutout areas.
|
|
95
|
+
|
|
96
|
+
Cutouts round-trip through `fold` via `FoldRegion.cutouts` (and `FlatInput.baseCutouts` for the base):
|
|
97
|
+
`partToFlatInput(part)` carries the region-local specs across, so `fold(partToFlatInput(part))` reproduces a
|
|
98
|
+
cutout'd part's volume. Unlike notched reliefs, cutouts don't change the outer outline, so a cutout'd part
|
|
99
|
+
still round-trips through the strict outline oracle.
|
|
100
|
+
|
|
101
|
+
## Tabs and tab-and-slot joints
|
|
102
|
+
|
|
103
|
+
A **tab** is the additive counterpart of a cutout: a rectangular protrusion of material extending
|
|
104
|
+
**outward** from a flat region's edge. `addTab(part, { region, side, offset, width, length })` builds the
|
|
105
|
+
tab as a region-local rectangle just past the chosen `side`, places it on the correct folded face via the
|
|
106
|
+
region's world frame, extrudes it through the sheet thickness and **fuses** it on, so the volume rises by
|
|
107
|
+
`width · length · thickness`. The same rectangle is mapped through the region's developed frame and recorded
|
|
108
|
+
as a `TabFeature`, so `unfold` **extends the outer outline** by the protrusion and grows `developedArea` by
|
|
109
|
+
the tab area. Guards a valid, single-bodied result (`TAB_INVALID_SOLID`) and rejects a tab overhanging its
|
|
110
|
+
edge (`TAB_OUT_OF_BOUNDS`).
|
|
111
|
+
|
|
112
|
+
`tabAndSlot(part, tab, { region, x, y, clearance? })` is the headline self-locating joint: it fuses a tab on
|
|
113
|
+
one region **and** punches a matching **slot cutout** on the mating region, sized so the tab's cross-section
|
|
114
|
+
(`width × thickness`) inserts into the slot. The slot is `tab.width + clearance` long by `thickness +
|
|
115
|
+
clearance` wide, always strictly larger than the tab cross-section, so the joint mates (clearance defaults
|
|
116
|
+
to `0.1` mm). Use it to make boxes/enclosures that self-fixture before welding.
|
|
117
|
+
|
|
118
|
+
Tabs ride through `fold` via `FoldRegion.tabs` / `FlatInput.baseTabs`, so `fold` re-fuses them and a tab'd
|
|
119
|
+
part folds back into the same volume. Like cutouts and forms, a tab carries its region-local spec, so
|
|
120
|
+
`partToFlatInput` re-attaches it onto the recovered region and `fold(partToFlatInput(part))` round-trips a
|
|
121
|
+
tab'd part's volume through the strict oracle. Although a tab protrudes the outer outline, the parser
|
|
122
|
+
recovers the base/flange rectangles from the bend lines and the unprotruded edges, so the protrusion does
|
|
123
|
+
not corrupt region recovery (a tab on an unmappable region fails loudly with `TAB_REGION_UNMAPPED` rather
|
|
124
|
+
than being silently dropped).
|
|
125
|
+
|
|
126
|
+
## Form features (louvers, embosses, dimples)
|
|
127
|
+
|
|
128
|
+
Form features are **locally formed**: they neither remove nor add net material, so the developed **outline
|
|
129
|
+
and area are unchanged**; the flat pattern instead carries the fabrication markers on a `FORM` DXF layer.
|
|
130
|
+
|
|
131
|
+
- `louver(part, { region, x, y, length, width, height, direction? })`: a vent cut on three sides with the
|
|
132
|
+
flap formed up along the hinge. In the **flat pattern** it emits the three cut sides as an **open** cut
|
|
133
|
+
path in `FlatPattern.formCuts` (the fabricator cuts three of the footprint's four sides, all but the
|
|
134
|
+
hinge) plus the hinge fold line in `FlatPattern.formHinges`, so a fabricator cuts the U and folds the flap
|
|
135
|
+
about the uncut hinge. The cut path is emitted as an open LWPOLYLINE on the DXF `FORM` layer (not a closed
|
|
136
|
+
rectangle, which would drop the flap out).
|
|
137
|
+
- `emboss(part, { region, x, y, diameter, height, kind: 'emboss' | 'dimple' })`: a round local form, raised
|
|
138
|
+
(`emboss`) or recessed (`dimple`). The footprint circle is emitted as a marker (`FlatPattern.formMarkers`).
|
|
139
|
+
|
|
140
|
+
**3D fidelity (simplified, documented).** The public CSG-only API makes true sheet forming (bending the flap
|
|
141
|
+
continuous with the parent, or stretch-forming a spherical cap) impractical, so the 3D representations are
|
|
142
|
+
deliberately simplified while keeping a **valid single solid**; the flat pattern is the fabrication-critical
|
|
143
|
+
output:
|
|
144
|
+
|
|
145
|
+
- _Louver_: the vent opening (`length × width`) is cut fully through the sheet, and the formed flap is
|
|
146
|
+
represented as a thin plate hinged on one side and tilted up to `height`, fused at the hinge so the result
|
|
147
|
+
stays one body. (True forming keeps the flap material continuous with the parent.)
|
|
148
|
+
- _Emboss_: a short cylinder fused onto the formed face (raised by `height`). _Dimple_: a shallow cylinder
|
|
149
|
+
recess cut into the formed face (recessed by `height`, never through; rejected if `height ≥ thickness`).
|
|
150
|
+
The flat-topped round approximates a true spherical/conical form so the solid stays valid.
|
|
151
|
+
|
|
152
|
+
Forms ride through `fold` via `FoldRegion.forms` / `FlatInput.baseForms`. Because they are material-neutral
|
|
153
|
+
they don't change the outline, so a **formed part still round-trips** through the strict outline oracle
|
|
154
|
+
(`partToFlatInput` carries the region-local form specs across, exactly as it does for cutouts).
|
|
155
|
+
|
|
156
|
+
## Contour & lofted/ruled transition flanges
|
|
157
|
+
|
|
158
|
+
Two profile-driven flanges extend authoring beyond single straight bends:
|
|
159
|
+
|
|
160
|
+
- `contourFlange(part, { id, side, profile, rule?, offset?, width? })`: an **open 2D profile** (alternating
|
|
161
|
+
`{ kind: 'line', length }` flats and `{ kind: 'arc', radius, angleDeg, direction }` bends) swept along a
|
|
162
|
+
straight base edge into a multi-bend cross-section (a return, a hat/top-hat, a J). Each arc becomes a
|
|
163
|
+
recorded `BendFeature` (id `contour::<flange>::<n>`) and chains frame-to-frame exactly as flange-off-flange
|
|
164
|
+
bends do. The **development is EXACT**: the developed strip length is the sum of each segment's developed
|
|
165
|
+
length (lines: `length`; arcs: the canonical `developedLength` = `(π/180)·|angle|·(R + K·T)`). The unfold
|
|
166
|
+
lays the strip out straight along the base edge with one bend line per arc at its exact cumulative
|
|
167
|
+
arc-length offset.
|
|
168
|
+
- `loftedFlange(part, { id, profileA, profileB, height, thickness? })`: a **ruled transition** between two
|
|
169
|
+
parallel open profiles (`profileA` at z=0, `profileB` at z=`height`, equal vertex counts), thickened to a
|
|
170
|
+
valid solid by triangulating each quad of the ruled surface and extruding each triangle. The
|
|
171
|
+
**development is by triangulation**, the standard sheet-metal transition development: each quad is split
|
|
172
|
+
along a diagonal into two triangles laid out flat preserving their true 3D edge lengths. For a genuinely
|
|
173
|
+
**developable** transition (planar quads / single-curvature) this is **exact to tolerance**; for a
|
|
174
|
+
non-developable (twisted) transition it is an **approximation**, and the unfold emits a
|
|
175
|
+
`DEVELOPMENT_APPROXIMATE` warning (inside the `Ok` payload). The triangulated boundary is emitted as a
|
|
176
|
+
valid closed wire in `FlatPattern.loftedDevelopments`.
|
|
177
|
+
|
|
178
|
+
Both flanges have **non-rectilinear** developments, so, like miters and tabs, they sit outside the strict
|
|
179
|
+
`patternToFlatInput` outline oracle: verification leans on developed-length/area invariants and solid
|
|
180
|
+
validity rather than a fold round-trip. The contour flange's developed strip still joins the rectilinear
|
|
181
|
+
outline union; the lofted flange's triangulated blank is a separate developed boundary.
|
|
182
|
+
|
|
183
|
+
## Hems & jogs
|
|
184
|
+
|
|
185
|
+
Hems and jogs are multi-bend edge features built on the same frame-to-frame profile chainer as the contour
|
|
186
|
+
flange, so their developments are **EXACT** (Σ segment developed lengths, via the table-aware
|
|
187
|
+
`developedLength`). Both attach to any flat **region**, the base (`'base'`/`'root'`) or a named flange,
|
|
188
|
+
edge, record their curl/step sub-bends as `hem::<id>::<n>` / `jog::<id>::<n>` `BendFeature`s (skipped by the
|
|
189
|
+
feature-tree spanning walk, like `contour::` bends), and lay their developed strip out straight along the
|
|
190
|
+
region edge with one bend line per sub-bend.
|
|
191
|
+
|
|
192
|
+
- `hem(part, { region, side, type, length?, radius?, gap?, offset?, width?, rule? })` folds a region edge
|
|
193
|
+
back onto its parent. Four `type`s set the curl angle and the gap:
|
|
194
|
+
- **`closed`**: a tight ~180° fold, the return flat against the parent. The curl radius is inflated by a
|
|
195
|
+
small **HAIR** clearance (0.05 mm) so the fused solid stays valid (a true zero-gap fold makes the curl's
|
|
196
|
+
inner cylinder coincide with the parent face → a self-touching, non-manifold solid). `length` is the
|
|
197
|
+
return-leg length (must be ≥ one thickness).
|
|
198
|
+
- **`open`**: a ~180° fold with a clear `gap` (default = `radius`) between the return and the parent; the
|
|
199
|
+
curl radius is sized from the gap, so its developed curl length is the exact 180° allowance at that radius.
|
|
200
|
+
- **`teardrop`**: a >180° (~210°) curl leaving a small teardrop opening, then a short return.
|
|
201
|
+
- **`rolled`**: a full ~270° circular roll (a curled / safe edge); no flat return.
|
|
202
|
+
|
|
203
|
+
The curl is split into ≤120° sub-arcs (the bend-patch wedge degenerates at ≥180°), each a recorded
|
|
204
|
+
sub-bend; the bend allowance is linear in angle, so Σ sub-arc allowances == the single-curl allowance. The
|
|
205
|
+
**developed length** is Σ curl allowances + the return length, laid out straight past the edge.
|
|
206
|
+
|
|
207
|
+
- `jog(part, { region, side, position, offsetHeight, angle?, runOut?, radius?, offset?, width?, rule? })`
|
|
208
|
+
steps a flat by `offsetHeight` perpendicular to its plane with **two opposite bends** (`+θ` then `−θ`,
|
|
209
|
+
`angle` default 45°), then continues parallel. The connecting step run is solved so the two arcs' rise plus
|
|
210
|
+
the step rise equals `offsetHeight` exactly; the resulting run-out bottom face sits exactly `offsetHeight`
|
|
211
|
+
above the base bottom face (verified by measurement, not just construction). The flat carries the **two
|
|
212
|
+
bend lines** (one `up`, one `down`); the **developed length** is `position + 2·allowance + stepRun +
|
|
213
|
+
runOut`. `offsetHeight` must exceed the bends' own rise `(T + 2R)(1 − cos θ)`, else the jog is rejected.
|
|
214
|
+
|
|
215
|
+
Like the contour/lofted flanges, hems and jogs have non-rectilinear developments and so sit outside the
|
|
216
|
+
strict `partToFlatInput → fold` round-trip oracle; verification leans on developed-length invariants, the
|
|
217
|
+
measured jog offset, and solid validity.
|
|
218
|
+
|
|
219
|
+
## Nesting
|
|
220
|
+
|
|
221
|
+
`nest(patterns, { sheet, margin?, spacing?, allowRotation?, strategy? })` arranges a set of developed flat
|
|
222
|
+
patterns onto stock sheets to reduce waste, returning `Result<NestResult>` (`{ sheets, unplaced, warnings }`).
|
|
223
|
+
Each sheet carries its `placements` (`{ patternIndex, x, y, rotationDeg }`) and a `utilization`.
|
|
224
|
+
`nestToDXF(result, patterns, sheetIndex)` then emits one fabrication-ready, multi-layer DXF per sheet,
|
|
225
|
+
translating and rotating every placed pattern (outline + bend lines + holes + forms) onto the sheet.
|
|
226
|
+
|
|
227
|
+
Two strategies are available via `strategy`:
|
|
228
|
+
|
|
229
|
+
### `strategy: "bbox"` (default): bounding-box nesting
|
|
230
|
+
|
|
231
|
+
Packs each part as its axis-aligned **outline bounding box**, so parts do **not** interlock: a concave or
|
|
232
|
+
L-shaped part reserves its full rectangular footprint and leaves the in-bbox waste unused. `rotationDeg` is
|
|
233
|
+
`0` or `90`. This remains the default so existing callers are unchanged.
|
|
234
|
+
|
|
235
|
+
- **Algorithm.** A shelf (level) packing heuristic. Parts are sorted largest-first (by bbox height, then area)
|
|
236
|
+
and placed left-to-right into horizontal shelves; a new shelf opens below when the current row fills, and a
|
|
237
|
+
**new sheet** opens when the next shelf would overflow the usable height.
|
|
238
|
+
- **Rotation.** With `allowRotation`, each part is tried at `0°` and `90°` and the orientation that fits (or
|
|
239
|
+
packs shorter) is kept.
|
|
240
|
+
- **Utilization.** Σ placed part **bounding-box** areas ÷ usable-sheet area, a conservative lower bound that
|
|
241
|
+
does not credit inter-part gaps or intra-bbox waste.
|
|
242
|
+
|
|
243
|
+
### `strategy: "nfp"`: true-shape (no-fit-polygon) nesting
|
|
244
|
+
|
|
245
|
+
Packs the actual **outline polygons** so concave parts interlock; an L-shaped part nests a rotated copy into
|
|
246
|
+
its notch, fitting more material per sheet than bbox packing ever can. `rotationDeg` may be `0/90/180/270`.
|
|
247
|
+
|
|
248
|
+
> **Heuristic, not optimal.** The nfp packer is a **bottom-left-fill** heuristic (largest-first, with a
|
|
249
|
+
> discretised raster scan over each part's orientations). It finds good interlocks but is **not** a provably
|
|
250
|
+
> optimal solver; a different part order or finer rotation set may pack tighter. What it guarantees: placed
|
|
251
|
+
> outline polygons never overlap (within `spacing`), all placements stay inside `[margin, sheet − margin]`, an
|
|
252
|
+
> oversized part goes to `unplaced` with a `PART_TOO_LARGE` warning (never dropped, never an infinite loop).
|
|
253
|
+
|
|
254
|
+
- **Correctness backbone.** Overlap is tested at the **polygon** level: two outlines overlap iff any edges
|
|
255
|
+
cross **or** one contains a vertex of the other (full containment), with a configurable `spacing` clearance.
|
|
256
|
+
A bbox-only check would let a part collide with a concave neighbour, so the true outline is always used.
|
|
257
|
+
- **Utilization.** Σ placed part **outline-polygon** areas ÷ usable-sheet area, so an L-shaped part credits
|
|
258
|
+
only its true material; the two strategies' utilizations are directly comparable on the same parts.
|
|
259
|
+
- **Geometry assumption.** Outlines are read as straight-edged polylines (one vertex per edge start point);
|
|
260
|
+
arc edges are approximated by their endpoints, the same assumption the DXF/SVG writers and the bbox nester
|
|
261
|
+
make.
|
|
262
|
+
|
|
263
|
+
On a set of identical L-shaped parts, `"nfp"` interlocks them and typically uses roughly **half the sheets** of
|
|
264
|
+
`"bbox"` (see the snapshot harness's `nfp vs bbox` demo).
|
|
265
|
+
|
|
266
|
+
```ts
|
|
267
|
+
const patterns = parts.map((p) => unfold(p).value.pattern);
|
|
268
|
+
|
|
269
|
+
// Default bounding-box nesting:
|
|
270
|
+
const bbox = nest(patterns, {
|
|
271
|
+
sheet: { width: 1250, height: 2500 },
|
|
272
|
+
margin: 5,
|
|
273
|
+
spacing: 3,
|
|
274
|
+
allowRotation: true,
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// True-shape nesting - interlocks concave parts for higher utilization:
|
|
278
|
+
const nfp = nest(patterns, {
|
|
279
|
+
sheet: { width: 1250, height: 2500 },
|
|
280
|
+
margin: 5,
|
|
281
|
+
spacing: 3,
|
|
282
|
+
allowRotation: true,
|
|
283
|
+
strategy: 'nfp',
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
const dxf = nestToDXF(nfp.value, patterns, 0); // fabrication-ready DXF for sheet 0
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## Foreign-solid import & unfold
|
|
290
|
+
|
|
291
|
+
`unfoldSolid(solid, { kFactor? })` (fluent: `fromSolid(solid).unfold()`) flattens an **arbitrary imported
|
|
292
|
+
sheet-metal solid that has no feature tree** by detecting its geometry numerically, the reverse of the
|
|
293
|
+
authored path. It reads only the B-rep:
|
|
294
|
+
|
|
295
|
+
- **Classify faces** by surface type: planar faces are panel faces, cylindrical faces are bend faces. Any
|
|
296
|
+
other surface type (cone, sphere, spline, …) is reported `UNSUPPORTED_FACE` and skipped (the unfold never
|
|
297
|
+
silently invents a flat for an unrecognised face).
|
|
298
|
+
- **Pair flats**: the two large parallel planar faces of a panel, a sheet thickness apart, are the
|
|
299
|
+
mid-surface flat. Thickness is detected as the smallest opposed-planar-pair gap.
|
|
300
|
+
- **Fit bends**: `fitCylinder(face)` recovers a bend's axis + radius + swept angle from sampled points and
|
|
301
|
+
normals. The axis direction is the common perpendicular of the sampled normals (every cylinder normal is
|
|
302
|
+
⟂ the axis), recovered as the sign-aligned average of cross products of non-parallel normal pairs; the
|
|
303
|
+
axis line and radius come from a least-squares circle fit of the points projected onto the plane ⟂ axis;
|
|
304
|
+
the radius is the mean point-to-axis distance. A fit whose residual exceeds tolerance is rejected (warned).
|
|
305
|
+
Recovery is high-precision: radius and axis of a known `cylinder()` primitive are recovered to ~1e-6.
|
|
306
|
+
- **Build the bend graph** (flats = nodes; a bend connects the two flats it shares edges with), take a
|
|
307
|
+
spanning tree from a root flat, and turn non-tree bends into seam cuts (`SEAM_CUT`, warned).
|
|
308
|
+
- **Unfold** by walking the tree, replacing each cylindrical region with a developed strip of length
|
|
309
|
+
`developedLength(angle, thickness, { innerRadius, kFactor })`. Because a foreign solid carries no material
|
|
310
|
+
K-factor, the default is the **mid-surface neutral axis (K = 0.5)**; pass `{ kFactor }` to match a known
|
|
311
|
+
material. Bend direction (up/down) is read from the cylinder centre relative to the parent flat.
|
|
312
|
+
|
|
313
|
+
**Supported class**: roughly-uniform-thickness solids whose panels are **planar** and whose bends are
|
|
314
|
+
**cylindrical** (straight bends). Outside this class the unfold **warns rather than mis-unfolding**:
|
|
315
|
+
non-uniform thickness, conical/spline bend faces, non-manifold / non-sheet topology, and faces that don't
|
|
316
|
+
fit a cylinder within tolerance all surface as warnings inside the `Ok` payload (or a clear `Err` when no
|
|
317
|
+
sheet-metal structure is detected at all, e.g. a bare sphere). The detection is validated by an oracle:
|
|
318
|
+
author a part, take only its `.solid`, run `unfoldSolid`, and confirm the detected unfold reproduces the
|
|
319
|
+
authored unfold's developed area, bend count, flat count, and total flat bbox (single bend, L-bracket,
|
|
320
|
+
U-channel), reading only the solid, never the feature tree.
|
|
321
|
+
|
|
322
|
+
## Bend tables
|
|
323
|
+
|
|
324
|
+
By default a bend develops by the K-factor formula `BA = (π/180)·|angle|·(R + K·T)`. A `BendRule` may
|
|
325
|
+
instead reference a **shop bend table** by id (`rule.bendTableRef`), in which case the developed length is
|
|
326
|
+
**interpolated from the table**: linear in bend angle, bilinear across thickness × inner radius (with
|
|
327
|
+
nearest-radius substitution for sparse one-radius-per-gauge tables), clamping to the nearest tabulated
|
|
328
|
+
entry outside the table's range (and emitting a `MIN_RADIUS` unfold warning rather than extrapolating).
|
|
329
|
+
|
|
330
|
+
A table is `{ id, kind: 'allowance' | 'deduction', rows: { thickness, radius, angleDeg, value }[] }`. An
|
|
331
|
+
`allowance` table tabulates the developed arc length directly; a `deduction` table tabulates the bend
|
|
332
|
+
deduction and is converted on lookup via the outside-setback relation `OSSB = (R+T)·tan(θ/2)`,
|
|
333
|
+
`BD = 2·OSSB − BA` ⇒ `BA = 2·OSSB − BD` (for a 90° bend, `OSSB = R+T`).
|
|
334
|
+
|
|
335
|
+
Resolution precedence at the single resolution point (`resolveBendAllowance`, which `developedLength`
|
|
336
|
+
delegates to): **(1) `bendTableRef` table → (2) explicit `rule.allowance` override → (3) K-factor formula**.
|
|
337
|
+
Parts with no `bendTableRef` are byte-for-byte unchanged. Two starter air-bend tables ship auto-registered
|
|
338
|
+
on first access (`steel-airbend` (mild steel) and `aluminum-airbend` (5052-H32)) with published 90°
|
|
339
|
+
allowances scaled across {30, 60, 90, 120}° (sources: SheetMetal.Me charts, Machinery's Handbook 29th ed.).
|
|
340
|
+
|
|
341
|
+
```ts
|
|
342
|
+
import { registerBendTable, author, unfold } from 'brepjs-sheetmetal';
|
|
343
|
+
|
|
344
|
+
registerBendTable({
|
|
345
|
+
id: 'my-shop-steel',
|
|
346
|
+
kind: 'allowance',
|
|
347
|
+
rows: [{ thickness: 1.52, radius: 1.5, angleDeg: 90, value: 3.63 }],
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
const part = author({
|
|
351
|
+
thickness: 1.52,
|
|
352
|
+
base: { length: 40, width: 40 },
|
|
353
|
+
flanges: [
|
|
354
|
+
{
|
|
355
|
+
id: 'fx',
|
|
356
|
+
length: 18,
|
|
357
|
+
angleDeg: 90,
|
|
358
|
+
side: 'xmax',
|
|
359
|
+
rule: { innerRadius: 1.5, kFactor: 0.44, bendTableRef: 'my-shop-steel' },
|
|
360
|
+
},
|
|
361
|
+
],
|
|
362
|
+
});
|
|
363
|
+
// unfold(part.value) now develops the bend at BA = 3.63 mm (the table), not (π/2)·(R + K·T).
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
## Design
|
|
367
|
+
|
|
368
|
+
All geometry for **authored** parts is computed analytically from the feature tree; bend axis, radius, and
|
|
369
|
+
angle are known inputs recorded on each `BendFeature`, so the unfold reads the tree rather than
|
|
370
|
+
reverse-engineering the B-rep. The **foreign-solid** path (`unfoldSolid`) is the exception: it detects bend
|
|
371
|
+
geometry numerically from the public surface queries (no recorded tree). No kernel/WASM changes are required
|
|
372
|
+
for either path.
|
|
373
|
+
|
|
374
|
+
## Usage
|
|
375
|
+
|
|
376
|
+
```ts
|
|
377
|
+
import { MATERIALS, getMaterial } from 'brepjs-sheetmetal';
|
|
378
|
+
|
|
379
|
+
const steel = getMaterial('steel-16ga');
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
All public operations return `Result<T>` (from `brepjs`); non-fatal warnings travel inside the `Ok` payload.
|
|
383
|
+
|
|
384
|
+
## Snapshot harness
|
|
385
|
+
|
|
386
|
+
A standalone visual harness lives in `harness/snapshot.ts`. It imports
|
|
387
|
+
`brepjs-sheetmetal` directly (no playground dependency), builds a set of demos
|
|
388
|
+
(the headline L-bracket with a mitered corner, a tray, reliefs, a cutout panel, a
|
|
389
|
+
**tab-and-slot box** (self-locating corner joint), and a **louvered panel** (vent
|
|
390
|
+
flaps + emboss/dimple)), then renders each folded 3D part next to its developed
|
|
391
|
+
flat pattern as a single side-by-side SVG and also writes the folded solid as STEP.
|
|
392
|
+
It also runs a **nesting** demo (a handful of parts bbox-packed onto one sheet),
|
|
393
|
+
printing the sheet count + utilization and writing the nested sheet as DXF + SVG,
|
|
394
|
+
plus an **nfp vs bbox** comparison nesting identical L-shaped parts with both
|
|
395
|
+
strategies and printing the true-shape utilization gain.
|
|
396
|
+
|
|
397
|
+
```bash
|
|
398
|
+
npm run snapshot --workspace=brepjs-sheetmetal
|
|
399
|
+
# writes harness/out/bracket.svg (folded isometric wireframe + flat pattern)
|
|
400
|
+
# harness/out/bracket.step (folded solid)
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
The folded view is an isometric edge-wireframe projection of the kernel mesh, so
|
|
404
|
+
the harness runs as a plain `tsx` script wherever the WASM kernel runs, with no
|
|
405
|
+
headless browser or GPU required. Output lands in `harness/out/` (gitignored).
|
|
406
|
+
|
|
407
|
+
## Development
|
|
408
|
+
|
|
409
|
+
```bash
|
|
410
|
+
npm run typecheck --workspace=brepjs-sheetmetal
|
|
411
|
+
npm run lint --workspace=brepjs-sheetmetal
|
|
412
|
+
npm run build --workspace=brepjs-sheetmetal
|
|
413
|
+
npm run test --workspace=brepjs-sheetmetal
|
|
414
|
+
npx tsc -p packages/brepjs-sheetmetal/harness/tsconfig.json # typecheck the harness
|
|
415
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Result } from 'brepjs';
|
|
2
|
+
import { BendRule, SheetMetalWarning } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Bend allowance — the developed (flat) length of material consumed by a bend,
|
|
5
|
+
* measured along the neutral axis. Resolution precedence (see
|
|
6
|
+
* {@link resolveBendAllowance}): a referenced bend table, then an explicit
|
|
7
|
+
* `rule.allowance` override, then the K-factor formula
|
|
8
|
+
* BA = (π/180)·|angle|·(R + K·T). An optional `onWarning` receives the clamp
|
|
9
|
+
* warning when a table query falls outside its tabulated range.
|
|
10
|
+
*/
|
|
11
|
+
export declare function bendAllowance(angleDeg: number, thickness: number, rule: BendRule, onWarning?: (warning: SheetMetalWarning) => void): Result<number>;
|
|
12
|
+
/**
|
|
13
|
+
* Developed length of the bend region when flattened — the neutral-axis arc
|
|
14
|
+
* length that replaces the curved patch in the flat pattern. Identical to the
|
|
15
|
+
* bend allowance (kept distinct in name for the unfold call sites that consume
|
|
16
|
+
* it as a strip width), and routed through the same {@link resolveBendAllowance}
|
|
17
|
+
* resolution (table → explicit allowance → K-factor).
|
|
18
|
+
*/
|
|
19
|
+
export declare function developedLength(angleDeg: number, thickness: number, rule: BendRule, onWarning?: (warning: SheetMetalWarning) => void): Result<number>;
|
|
20
|
+
/** Neutral-axis radius R + K·T (the radius the developed arc length is measured at). */
|
|
21
|
+
export declare function neutralRadius(thickness: number, rule: BendRule): Result<number>;
|