@toolpath/dfm 0.0.0-stage → 0.2.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Toolpath
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,214 @@
1
- # Temporary Holding Version
1
+ # Toolpath DFM
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ `@toolpath/dfm` is the DFM (design for manufacturing) rule list: each rule
4
+ written as the sentence it is — "Holes with L/D to top of part ≥ 8" — whose
5
+ words are its fields, beside its colour and how many of a part's features
6
+ break it. Under the list, Import, Export and Copy LLM prompt share a rule set
7
+ as one JSON file.
8
+
9
+ It also draws a feature's reach chart: the walls beside the feature, how high
10
+ they stand and how far out.
11
+
12
+ And it shows a feature as the Engine read it — the feature panel: the features
13
+ a clicked face could mean, the rules the chosen one breaks, what was measured
14
+ of it, its reach, and its datasheet as sent. Its pinch points, the widest tool
15
+ standing where the feature is tightest, are placed here and drawn by
16
+ `@toolpath/viewer`.
17
+
18
+ The rules and the checker that counts them ship too, with no React, under
19
+ `@toolpath/dfm/model`.
20
+
21
+ ## Install
22
+
23
+ ```sh
24
+ npm install @toolpath/dfm @toolpath/ui react react-dom
25
+ ```
26
+
27
+ Add the styles after Tailwind and the `@toolpath/ui` theme:
28
+
29
+ ```css
30
+ @import 'tailwindcss';
31
+ @import '@toolpath/ui/theme.css';
32
+ @import '@toolpath/dfm/dfm.css';
33
+ ```
34
+
35
+ ## Exports
36
+
37
+ | Entry point | What it is |
38
+ | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
39
+ | `@toolpath/dfm` | `RuleList`, `Swatch`, the list's keyboard helpers, `ReachChart`, `ReachDrawing`, the feature panel (`FeaturePanel`, `FeatureDetails`, `CandidateList`, `ComparisonTable`, `storageFolds`), their types |
40
+ | `@toolpath/dfm/model` | The rules, `checkPart`, the rule-set file, the feature panel's rows, pinch points — no React, no DOM |
41
+ | `@toolpath/dfm/dfm.css` | Lets the app's Tailwind see the list's and chart's classes |
42
+
43
+ ## The rules are an input
44
+
45
+ The package ships no default rules and stores nothing — not the rules, nor
46
+ which of the feature panel's sections are open. The app keeps the rules
47
+ — in memory, `localStorage`, a server — and passes them in with the functions
48
+ that change them:
49
+
50
+ ```tsx
51
+ import { RuleList, type DfmRules } from '@toolpath/dfm'
52
+ import { checkPart, dfmFeatures } from '@toolpath/dfm/model'
53
+
54
+ const rules: DfmRules = { rules, add, update, remove, replace }
55
+
56
+ // From a part analysis: the Engine's report, and each feature's datasheet.
57
+ const features = dfmFeatures(report.features)
58
+ const check = checkPart(features, { sheets, datasheets, regionAreas }, rules.rules)
59
+
60
+ <RuleList
61
+ rules={rules}
62
+ check={check} // null while the part is still being analysed
63
+ features={features}
64
+ sheets={sheets}
65
+ units="mm"
66
+ selected={selectedTag}
67
+ selectedGroup={selectedGroup}
68
+ onInspect={(tag, group) => inspect(tag, group)}
69
+ onHover={(hovered) => highlight(hovered)}
70
+ />
71
+ ```
72
+
73
+ `sheets` is each feature's measurements, built with `featureSheet` from its
74
+ datasheet; `datasheets` is the datasheets as the API sent them, both by
75
+ lower-cased feature tag; `regionAreas` is each face's area by region index.
76
+
77
+ Put `ruleListKeys` on the panel that holds the list, so the arrow keys walk the
78
+ rules from anywhere in it.
79
+
80
+ ## Reach chart
81
+
82
+ `ReachChart` draws a feature's reach curve: the section through the part at the
83
+ feature's wall, to scale. The feature is on the left, in blue; the walls step up
84
+ on the right, with their heights and their distances from the feature written
85
+ on. Pointing at the walls reads the curve at that distance.
86
+
87
+ ```tsx
88
+ import { ReachChart, ReachDrawing } from '@toolpath/dfm'
89
+ import { featureProfile, readReachCurve } from '@toolpath/dfm/model'
90
+
91
+ const curve = readReachCurve(datasheet) // null where the feature has none
92
+ const feature = featureProfile(sheets[tag], featureType)
93
+
94
+ export const Reach = () =>
95
+ curve ? <ReachChart curve={curve} units="mm" feature={feature} /> : null
96
+ ```
97
+
98
+ `ReachChart` is as wide as its container. `ReachDrawing` is the same section
99
+ with a caption and a note on how to read it, for a larger window of its own.
100
+ Both stop drawing the curve half an inch past where the walls reach full
101
+ height; `ReachDrawing` says so in its note.
102
+
103
+ This is not the clearance overlay in `@toolpath/tool-drawing/clearance`: that
104
+ draws the walls beside a cutting tool, to show whether the tool fits. This
105
+ draws the walls alone.
106
+
107
+ ## Feature panel
108
+
109
+ `FeaturePanel` is the card a click on the part opens: every feature the face
110
+ belongs to, then the details of the one being read. With several faces
111
+ shift-clicked, each face's candidates sit under a letter, above a table of
112
+ what each was read as. It fills what it is put in — where it sits, and
113
+ whether it can be dragged, is the app's — and the app decides what a click
114
+ means: which faces, which candidates in which order, which is read.
115
+
116
+ ```tsx
117
+ import { FeatureDetails, FeaturePanel, storageFolds } from '@toolpath/dfm'
118
+ import { brokenRules, featureMeasurements, featureProfile } from '@toolpath/dfm/model'
119
+
120
+ const folds = storageFolds(localStorage, 'toolpath.fold.')
121
+
122
+ export const Inspector = () => (
123
+ <FeaturePanel
124
+ faces={[{ region, rows, selected }]}
125
+ active={0}
126
+ onSelect={(tag) => setSelected(tag)}
127
+ onHover={setHovered}
128
+ onClose={close}
129
+ folds={folds}
130
+ >
131
+ <FeatureDetails
132
+ feature={feature}
133
+ units="mm"
134
+ directionColor={directionHex}
135
+ profile={featureProfile(sheets[key], feature.featureType)}
136
+ required={check.required.has(key)}
137
+ onFrame={() => frame(feature.tag)}
138
+ rules={brokenRules(check, feature.tag, 'mm')}
139
+ measurements={{
140
+ status: 'ready',
141
+ value: featureMeasurements({ features, feature, sheets, units: 'mm' }),
142
+ }}
143
+ record={{ status: 'ready', value: records[key] ?? null }}
144
+ PopOut={AppWindow}
145
+ folds={folds}
146
+ />
147
+ </FeaturePanel>
148
+ )
149
+ ```
150
+
151
+ The parts work alone too. An app with an inspect panel of its own puts
152
+ `CandidateList` and `FeatureDetails` in it and fills their slots with its own
153
+ controls — a folder in a row's `detail`, a Pin button in its `action`, Zoom to
154
+ in the details' `actions`, a folder badge in `status`.
155
+
156
+ - **Rules broken** are `BrokenRule` rows: a colour, the rule's text, the figure
157
+ that broke it, and optionally the limit and a note on hover. `brokenRules`
158
+ builds them from this package's check; an app with a checker of its own
159
+ builds them from its hits.
160
+ - **Measurements** are `featureMeasurements` rows, each with how it was worked
161
+ out behind an ⓘ. `inchMark` writes inches as `0.46"`.
162
+ - **Reach, the datasheet fields and the raw record** read the feature's
163
+ `FeatureRecord`: its report entry and datasheet as the API sent them.
164
+ - **Reads take time.** `measurements` and `record` are `Loadable`. While
165
+ `loading`, a section says what it is reading; on `error` it shows the app's
166
+ message and a Retry, held until `retryAt`.
167
+ - **The look** differs where apps want it to: `look.rules`, `look.selected` and
168
+ `look.muted`. Left out, it is the CAD viewer's.
169
+ - **Folds** are kept in the app's `FoldStore`; `storageFolds(storage, prefix)`
170
+ builds one over a `Storage`. Without one, a section remembers only while it
171
+ is mounted.
172
+ - **Pop-outs** need `PopOut`, the app's window component: the panel decides
173
+ what is open and fills it, and the app draws the frame. Define it at module
174
+ level, and render it through a portal to the page's body: the panel's blur
175
+ makes it the box a `fixed` window inside it would be placed in.
176
+ - **Keys:** Escape closes the panel and the arrow keys walk the candidates. On
177
+ a `CandidateList` used alone, put `candidateListKeys` on whatever holds it.
178
+
179
+ ## Pinch points
180
+
181
+ The widest tool a feature admits, standing where the feature is tightest. The
182
+ data is here; the drawing is `@toolpath/viewer`'s `<ToolMarks>`, which knows
183
+ nothing of datasheets:
184
+
185
+ ```tsx
186
+ import { useMemo } from 'react'
187
+ import { pinchLabel, pinchMark, placePinchTool } from '@toolpath/dfm/model'
188
+ import { featureTriangles, ToolMarks, usePartContext } from '@toolpath/viewer'
189
+
190
+ // A child of <EnginePart> or <PartMesh>, which gives it the part.
191
+ export const PinchPoints = ({ datasheet, sheet, feature, units, shown }) => {
192
+ const { model, geometry } = usePartContext()
193
+ // Placing the tool searches the feature's faces: once per feature, not once per render.
194
+ const marks = useMemo(() => {
195
+ const mark = pinchMark(datasheet, sheet, feature.featureType)
196
+ const triangles = featureTriangles(model, geometry, feature.tag)
197
+ const tool = mark ? placePinchTool(mark, triangles, feature.machiningDirection) : null
198
+ return tool && mark ? [{ ...tool, ...pinchLabel(mark, units) }] : []
199
+ }, [datasheet, sheet, feature, model, geometry, units])
200
+ return <ToolMarks marks={marks} visible={shown} />
201
+ }
202
+ ```
203
+
204
+ `placePinchTool` finds the datasheet's tool frame against the feature's faces —
205
+ the API gives its discs across the tool without saying which way x and y lie —
206
+ and stands the tool on the tightest disc, turned toward the wall that pinches
207
+ it. If the app moved the geometry to centre it, pass `origin`: where the CAD
208
+ file's zero now sits.
209
+
210
+ Show and hide it from the viewer's toolbar — `ViewerToolbar.ToolsButton` on a
211
+ `tools` control — with the state held by the app; start it shown. The tool is
212
+ an end mill made from its diameter and corner; to draw it as the app's other
213
+ tools are drawn, give the mark a `profile` from `@toolpath/tool-drawing`'s
214
+ outline of a flat or bull nose end mill that size.
package/dfm.css ADDED
@@ -0,0 +1,10 @@
1
+ /*
2
+ * Lets the app's Tailwind see the classes the rule list and the feature panel
3
+ * use. Import it after Tailwind and `@toolpath/ui/theme.css`, whose colours
4
+ * they are drawn in:
5
+ *
6
+ * @import 'tailwindcss';
7
+ * @import '@toolpath/ui/theme.css';
8
+ * @import '@toolpath/dfm/dfm.css';
9
+ */
10
+ @source './dist';