Skip to content

Commit c249311

Browse files
committed
Add DIN 915 black steel dog-point screw references
1 parent 6e5dced commit c249311

13 files changed

Lines changed: 600 additions & 9 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ An exact sourced configuration also embeds its designation, source URL and recor
9494

9595
The eight supplied reference images are available from the relevant presets. The extension includes all 27 SFU table rows, 38 unique jaw-coupling bore pairs, four miniature spur pinions and all 12 bevel table rows arranged into six pairs. Ball screws have 131 nut configurations and 230 assembly presets, including 100–550 mm miniature shaft lengths. Two listing codes without a matching drawing, `SFK602` and `SFE3210`, remain explicitly unverified examples. See [ball screw coverage](docs/ball-screw-catalog.md) and [gear reference mapping](docs/reference-gears.md). Reference load ratings are read-only source specifications and do not certify edited models.
9696

97+
The **Set screw / grub screw** package includes 11 **DIN 915 · black 12.9** references from M2 through M16, with a helical thread, blind hex socket and cylindrical dog point. Overall lengths are editable prototype choices because the supplied table does not list stock lengths. See [DIN 915 source dimensions and verification](docs/din915.md).
98+
9799
The servo linkage extension adds 28 horn references, 16 servo gear presets (13 actual ServoCity catalog sizes and three prototype examples), and 24 clevis presets. Find them under **MOTION → SERVO LINKAGES**, or search for **servo**, **horn** or **clevis**. The 18 supplied images remain linked from the corresponding references. See [servo linkage coverage](docs/servo-linkages.md) for source dimensions, modeling limits and native FreeCAD verification.
98100

99101
Find **Piston** and **Connecting rod** under **MOTION → PISTONS & CONNECTING RODS**. Piston catalog presets include all eight supplied nominal sizes: 42, 47, 48, 51, 65, 70, 80 and 90 mm. Other dimensions, pneumatic configurations and connecting-rod presets are editable prototype examples. Both packages support assembled, exploded and body-only states, with movable FreeCAD components. See [piston and connecting-rod coverage](docs/piston-linkages.md) for reference evidence, editing and verification.

data/din915-native-validation.json

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
{
2+
"checkedAt": "2026-09-14T02:04:28.734716+00:00",
3+
"freecadVersion": [
4+
"1",
5+
"0",
6+
"2",
7+
"39319 (Git)",
8+
"https://github.com/FreeCAD/FreeCAD",
9+
"2025/08/05 20:19:07",
10+
"(HEAD detached at 1.0.2)",
11+
"256fc7eff3379911ab5daf88e10182c509aa8052"
12+
],
13+
"casesRequested": 4,
14+
"passed": 4,
15+
"failed": 0,
16+
"cases": [
17+
{
18+
"id": "reference-din915-m2",
19+
"codeSha256": "dc970c51870b26437484c129113df6318717959ae838f207ed7b03a712877068",
20+
"passed": true,
21+
"dimensions": [
22+
1.9999958400859286,
23+
1.99999474921474,
24+
6.000000000000001
25+
],
26+
"volume": 11.748165991244877,
27+
"relativePreviewVolumeError": 0.001147716559918627,
28+
"pointProbes": 48,
29+
"socketProbes": 14,
30+
"shoulderProbes": 2,
31+
"stepRoundtrip": true,
32+
"durationSeconds": 2.2306867910083383
33+
},
34+
{
35+
"id": "reference-din915-m2.5",
36+
"codeSha256": "02f9945890f9196a36f04fb9d773ee0befc6fb4d7efa4e18b1f49c8425b563ff",
37+
"passed": true,
38+
"dimensions": [
39+
2.4999931574947545,
40+
2.4999934629955405,
41+
8.0
42+
],
43+
"volume": 25.890217933398354,
44+
"relativePreviewVolumeError": 0.0010701125780395331,
45+
"pointProbes": 48,
46+
"socketProbes": 14,
47+
"shoulderProbes": 2,
48+
"stepRoundtrip": true,
49+
"durationSeconds": 2.647479542007204
50+
},
51+
{
52+
"id": "reference-din915-m6",
53+
"codeSha256": "da2eca4b8ff7abac40bfb5e2ff44caf5a376f35b34e51756ac23d738668e9fd2",
54+
"passed": true,
55+
"dimensions": [
56+
5.99999974675121,
57+
5.999990497946376,
58+
16.00000000000001
59+
],
60+
"volume": 313.8068289013597,
61+
"relativePreviewVolumeError": 0.0010142487389965624,
62+
"pointProbes": 48,
63+
"socketProbes": 14,
64+
"shoulderProbes": 2,
65+
"stepRoundtrip": true,
66+
"durationSeconds": 2.63459808396874
67+
},
68+
{
69+
"id": "reference-din915-m16",
70+
"codeSha256": "840d2928fdf3689ac937825f532e459e911ccdbe8a299bc972319f07c654ba9a",
71+
"passed": true,
72+
"dimensions": [
73+
15.99999198585584,
74+
15.99998185839087,
75+
40.0
76+
],
77+
"volume": 5989.5375789190075,
78+
"relativePreviewVolumeError": 0.0009205007450940882,
79+
"pointProbes": 48,
80+
"socketProbes": 14,
81+
"shoulderProbes": 2,
82+
"stepRoundtrip": true,
83+
"durationSeconds": 4.989246708981227
84+
}
85+
]
86+
}

docs/din915.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# DIN 915 dog-point set screws
2+
3+
Search parts for **DIN915**, open **Set screw / grub screw**, then search **black 12.9** in **Browse presets**. Eleven reference presets cover M2, M2.5, M3, M4, M5, M6, M8, M10, M12, M14 and M16. They use a modeled helical thread, a blind hex socket and a cylindrical dog point with an editable shoulder chamfer. Display finish can be changed between plain metal and black steel; the FreeCAD macro carries the same color.
4+
5+
The supplied [dimension table](../public/references/din915-dimensions.png) provides pitch, point diameter limits, cylindrical point length limits, nominal socket size, printed socket limits and minimum socket depth. Presets start at the maximum point diameter/length and nominal socket size. The printed socket limits remain separate reference specifications: some do not contain the printed nominal value. The model does not infer a socket fit from those inconsistent columns.
6+
7+
The image does not provide overall stock lengths. Each preset therefore starts at an explicitly labeled, editable prototype length; **L includes the cylindrical point and shoulder transition**. L and the shoulder chamfer are excluded from verified catalog dimensions. Existing supplier DIN 915 stock presets remain available with their original length-dependent point dimensions. The stated black steel and grade 12.9 are source attributes; color does not model material strength.
8+
9+
## Package ownership
10+
11+
The change is contained in `src/parts/set-screw`: `part.ts` owns appearance and parameter definitions, `lib/core/fasteners.ts` owns the local preview and FreeCAD recipe, `lib/catalog/din915.ts` retains the transcribed table, and `presets.json` contains the complete reference configurations. Other fastener packages keep their own geometry helpers.
12+
13+
## Focused checks
14+
15+
`node --import tsx --test tests/din915.test.ts` checks all eleven supplied rows, source scope, closed outward mesh edges, overall length, full cylindrical point, socket floor and six socket flats, and invalid overlapping point/socket configurations.
16+
17+
Native checks are optional and remain outside the GitHub Pages build:
18+
19+
```sh
20+
node --import tsx scripts/verify-din915.ts
21+
/Applications/FreeCAD.app/Contents/Resources/bin/python scripts/verify-din915.py
22+
```
23+
24+
Use a FreeCAD-capable Python on other platforms. The native checks cover M2, M2.5, M6 and M16: valid closed solids, preview/native dimensions and volume, cylindrical point and shoulder probes, socket dimensions, source metadata and STEP round trips. The recorded local results are in `data/din915-native-validation.json`.
352 KB
Loading
852 KB
Loading

scripts/verify-din915.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#!/usr/bin/env python3
2+
"""Check DIN 915 macro solids, cylindrical point, shoulder, socket and STEP export.
3+
4+
Prepare with node --import tsx scripts/verify-din915.ts, then run this script with
5+
a FreeCAD-capable Python. These four optional native checks do not run in CI.
6+
"""
7+
import argparse
8+
from datetime import datetime, timezone
9+
import hashlib
10+
import json
11+
import math
12+
from pathlib import Path
13+
import sys
14+
import tempfile
15+
import time
16+
17+
parser = argparse.ArgumentParser(description=__doc__)
18+
parser.add_argument('cases', nargs='?', default='/tmp/protolab-din915-cases.json')
19+
parser.add_argument('--freecad-lib', default='/Applications/FreeCAD.app/Contents/Resources/lib')
20+
parser.add_argument('--output', default='/tmp/protolab-din915-results.json')
21+
args = parser.parse_args()
22+
sys.path.insert(0, args.freecad_lib)
23+
import FreeCAD as App
24+
import Part
25+
26+
cases = json.loads(Path(args.cases).read_text())
27+
results = []
28+
checked_at = datetime.now(timezone.utc).isoformat()
29+
with tempfile.TemporaryDirectory(prefix='protolab-din915-qa-') as folder:
30+
for index, case in enumerate(cases):
31+
doc = App.newDocument('DIN915QA'+str(index))
32+
started = time.perf_counter()
33+
record = {'id': case['id'], 'codeSha256': hashlib.sha256(case['code'].encode()).hexdigest(), 'passed': False}
34+
try:
35+
exec(case['code'], {})
36+
obj = next(obj for obj in doc.Objects if 'PartId' in obj.PropertiesList)
37+
shape = obj.Shape
38+
p = case['parameters']
39+
assert shape.isValid() and shape.isClosed() and len(shape.Solids) == 1 and shape.Volume > 0, 'Invalid screw solid'
40+
# OCC's optimal box adds a conservative gap for trimmed helical faces.
41+
# Measure actual surface vertices at a tighter deflection than the assertion.
42+
vertices, _ = shape.tessellate(.003)
43+
dimensions = [max(getattr(v,axis) for v in vertices)-min(getattr(v,axis) for v in vertices) for axis in ('x','y','z')]
44+
assert all(abs(a-b) < .025 for a,b in zip(dimensions,[p['diameter'],p['diameter'],p['length']])), 'Incorrect overall diameter/length'
45+
relative_error = abs(shape.Volume-case['previewVolume'])/case['previewVolume']
46+
assert relative_error < .015, 'Preview/native volume mismatch'
47+
def inside(r, angle, z):
48+
return shape.isInside(App.Vector(r*math.cos(angle),r*math.sin(angle),z-p['length']/2),1e-7,True)
49+
# Z is the full cylindrical segment, before the shoulder transition.
50+
for fraction in [.1,.9]:
51+
for i in range(12):
52+
a = (i+.17)*math.pi/6
53+
assert inside(p['tipDiameter']/2-.005,a,p['tipLength']*fraction), 'Point cylinder missing material'
54+
assert not inside(p['tipDiameter']/2+.005,a,p['tipLength']*fraction), 'Point cylinder exceeds dp'
55+
# Below the thread root, the shoulder follows its conical envelope.
56+
fraction = .25
57+
r = p['tipDiameter']/2+(p['diameter']-p['tipDiameter'])/2*fraction
58+
z = p['tipLength']+p['dogShoulderLength']*fraction
59+
assert inside(r-.005,.173,z) and not inside(r+.005,.173,z), 'Shoulder transition differs from preview'
60+
floor = p['length']-p['driveDepth']
61+
assert inside(0,0,floor-.005) and not inside(0,0,floor+.005), 'Incorrect blind socket depth'
62+
for i in range(6):
63+
a = i*math.pi/3
64+
z = p['length']-p['driveDepth']/2
65+
assert not inside(p['driveWidth']/2-.005,a,z), 'Hex socket too narrow'
66+
assert inside(p['driveWidth']/2+.005,a,z), 'Hex socket too wide'
67+
assert obj.CatalogSource == 'references/din915-dimensions.png', 'Reference source missing'
68+
assert 'length' not in json.loads(obj.CatalogDimensions), 'Prototype length incorrectly verified'
69+
step = str(Path(folder)/(str(index)+'.step'))
70+
Part.export([obj],step)
71+
restored = Part.Shape()
72+
restored.read(step)
73+
assert restored.isValid() and len(restored.Solids) == 1, 'STEP lost solid'
74+
assert abs(restored.Volume-shape.Volume) < max(.01,shape.Volume*1e-5), 'STEP volume changed'
75+
record.update(passed=True,dimensions=dimensions,volume=shape.Volume,relativePreviewVolumeError=relative_error,pointProbes=48,socketProbes=14,shoulderProbes=2,stepRoundtrip=True)
76+
except Exception as error:
77+
record['error'] = str(error)
78+
finally:
79+
App.closeDocument(doc.Name)
80+
record['durationSeconds'] = time.perf_counter()-started
81+
results.append(record)
82+
report = {'checkedAt': checked_at, 'freecadVersion': App.Version(), 'casesRequested': len(cases), 'passed': sum(row['passed'] for row in results), 'failed': sum(not row['passed'] for row in results), 'cases': results}
83+
Path(args.output).write_text(json.dumps(report,indent=2))
84+
print(json.dumps(record),flush=True)
85+
sys.exit(any(not row['passed'] for row in results))

scripts/verify-din915.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/** Prepare four boundary/representative DIN 915 macros for optional native FreeCAD QA. */
2+
import fs from 'node:fs';
3+
import { Mesh, Vector3 } from 'three';
4+
import part from '../src/parts/set-screw/part';
5+
import { generateScript } from '../src/core/freecad';
6+
import { disposeModel } from '../src/core/mechanical';
7+
8+
const cases = part.presets
9+
.filter(
10+
(preset) =>
11+
preset.id.startsWith('reference-din915-') &&
12+
[2, 2.5, 6, 16].includes(Number(preset.parameters.diameter)),
13+
)
14+
.map((preset) => {
15+
const model = part.buildGeometry(preset.parameters, 'default');
16+
let volume = 0;
17+
try {
18+
model.updateMatrixWorld(true);
19+
model.traverse((child) => {
20+
if (!(child instanceof Mesh)) return;
21+
const position = child.geometry.getAttribute('position'),
22+
index = child.geometry.index;
23+
for (let i = 0; i < (index?.count ?? position.count); i += 3) {
24+
const points = [0, 1, 2].map((j) =>
25+
new Vector3()
26+
.fromBufferAttribute(position, index ? index.getX(i + j) : i + j)
27+
.applyMatrix4(child.matrixWorld),
28+
);
29+
volume += points[0].dot(points[1].clone().cross(points[2])) / 6;
30+
}
31+
});
32+
return {
33+
id: preset.id,
34+
parameters: preset.parameters,
35+
previewVolume: volume,
36+
code: generateScript(part, preset.parameters, 'default'),
37+
};
38+
} finally {
39+
disposeModel(model);
40+
}
41+
});
42+
const output = process.argv[2] ?? '/tmp/protolab-din915-cases.json';
43+
fs.writeFileSync(output, JSON.stringify(cases));
44+
console.log(`Prepared ${cases.length} DIN 915 native cases in ${output}.`);

src/parts/set-screw/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,9 @@ This folder owns this part's parameter schema, defaults, preview, FreeCAD recipe
99
- `index.ts`: API version, stable part ID and display order.
1010

1111
Only the generic geometry SDK under `src/core` is shared. Run `npm run parts:check -- set-screw` to check this package and `npm run parts:export -- set-screw <output-folder>` to prepare a runnable handoff with the SDK and reference assets.
12+
13+
## DIN 915 references
14+
15+
The 11 **black 12.9** presets retain the supplied M2–M16 table in `lib/catalog/din915.ts`. `tipLength` is the full cylindrical dog length Z; `dogShoulderLength` follows it within overall L. The table does not supply L or the shoulder chamfer, so their initial values are editable prototype choices and are not marked as verified stock dimensions. `finish` changes preview and FreeCAD display color. Existing supplier presets retain their original dimensions and square shoulder.
16+
17+
Run `node --import tsx --test tests/din915.test.ts` from the app root for the focused geometry checks. See `docs/din915.md` for source scope and the optional native FreeCAD check.
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
export interface Din915Range {
2+
min: number;
3+
max: number;
4+
}
5+
6+
/** Dimensions in millimetres, transcribed from the user-supplied DIN 915 table. */
7+
export interface Din915Row {
8+
diameter: number;
9+
pitch: number;
10+
pointDiameter: Din915Range;
11+
socketNominal: number;
12+
socketActual: Din915Range;
13+
socketDepthMin: number;
14+
pointLength: Din915Range;
15+
}
16+
17+
export const din915ReferenceFiles = {
18+
dimensions: 'references/din915-dimensions.png',
19+
photo: 'references/din915-dog-point.png',
20+
};
21+
22+
// Preserve the printed "metric", "max" and "min" socket columns independently.
23+
// The nominal value is outside the stated interval in several rows. In particular,
24+
// M2.5 prints nominal 1.3 above the maximum 1.295; this source inconsistency is
25+
// retained, not silently corrected or treated as a verified socket fit.
26+
export const din915Rows: readonly Din915Row[] = [
27+
{
28+
diameter: 2,
29+
pitch: 0.4,
30+
pointDiameter: { min: 0.75, max: 1 },
31+
socketNominal: 0.9,
32+
socketActual: { min: 0.889, max: 0.902 },
33+
socketDepthMin: 0.8,
34+
pointLength: { min: 1, max: 1.25 },
35+
},
36+
{
37+
diameter: 2.5,
38+
pitch: 0.45,
39+
pointDiameter: { min: 1.25, max: 1.5 },
40+
socketNominal: 1.3,
41+
socketActual: { min: 1.27, max: 1.295 },
42+
socketDepthMin: 1.2,
43+
pointLength: { min: 1.25, max: 1.5 },
44+
},
45+
{
46+
diameter: 3,
47+
pitch: 0.5,
48+
pointDiameter: { min: 1.75, max: 2 },
49+
socketNominal: 1.5,
50+
socketActual: { min: 1.52, max: 1.545 },
51+
socketDepthMin: 1.2,
52+
pointLength: { min: 1.5, max: 1.75 },
53+
},
54+
{
55+
diameter: 4,
56+
pitch: 0.7,
57+
pointDiameter: { min: 2.25, max: 2.5 },
58+
socketNominal: 2,
59+
socketActual: { min: 2.02, max: 2.045 },
60+
socketDepthMin: 1.5,
61+
pointLength: { min: 2, max: 2.25 },
62+
},
63+
{
64+
diameter: 5,
65+
pitch: 0.8,
66+
pointDiameter: { min: 3.2, max: 3.5 },
67+
socketNominal: 2.5,
68+
socketActual: { min: 2.52, max: 2.56 },
69+
socketDepthMin: 2,
70+
pointLength: { min: 2.5, max: 2.75 },
71+
},
72+
{
73+
diameter: 6,
74+
pitch: 1,
75+
pointDiameter: { min: 3.7, max: 4 },
76+
socketNominal: 3,
77+
socketActual: { min: 3.02, max: 3.06 },
78+
socketDepthMin: 2,
79+
pointLength: { min: 3, max: 3.25 },
80+
},
81+
{
82+
diameter: 8,
83+
pitch: 1.25,
84+
pointDiameter: { min: 5.2, max: 5.5 },
85+
socketNominal: 4,
86+
socketActual: { min: 4.02, max: 4.095 },
87+
socketDepthMin: 3,
88+
pointLength: { min: 4, max: 4.3 },
89+
},
90+
{
91+
diameter: 10,
92+
pitch: 1.5,
93+
pointDiameter: { min: 6.64, max: 7 },
94+
socketNominal: 5,
95+
socketActual: { min: 5.02, max: 5.095 },
96+
socketDepthMin: 4,
97+
pointLength: { min: 5, max: 5.3 },
98+
},
99+
{
100+
diameter: 12,
101+
pitch: 1.75,
102+
pointDiameter: { min: 8.14, max: 8.5 },
103+
socketNominal: 6,
104+
socketActual: { min: 6.02, max: 6.095 },
105+
socketDepthMin: 4.8,
106+
pointLength: { min: 6, max: 6.3 },
107+
},
108+
{
109+
diameter: 14,
110+
pitch: 2,
111+
pointDiameter: { min: 9.64, max: 10 },
112+
socketNominal: 6,
113+
socketActual: { min: 6.02, max: 6.095 },
114+
socketDepthMin: 5.6,
115+
pointLength: { min: 7, max: 7.36 },
116+
},
117+
{
118+
diameter: 16,
119+
pitch: 2,
120+
pointDiameter: { min: 11.57, max: 12 },
121+
socketNominal: 8,
122+
socketActual: { min: 8.025, max: 8.115 },
123+
socketDepthMin: 6.4,
124+
pointLength: { min: 8, max: 8.36 },
125+
},
126+
];

0 commit comments

Comments
 (0)