Add DB9 CAD reference data

This commit is contained in:
2026-09-21 12:32:19 +02:00
parent 7d76a41540
commit 6406142b21
8 changed files with 46777 additions and 0 deletions
File diff suppressed because it is too large Load Diff
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016 Adafruit Industries
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+2
View File
@@ -0,0 +1,2 @@
# Adafruit_CAD_Parts
STEP, Fusion 360, and STL files for various boards, components and parts
+86
View File
@@ -0,0 +1,86 @@
"""Read pinned STEP B-rep; write reproducible solid/face measurements beside it."""
import json
import re
from pathlib import Path
from OCP.STEPControl import STEPControl_Reader
from OCP.IFSelect import IFSelect_RetDone
from OCP.TopExp import TopExp_Explorer
from OCP.TopAbs import TopAbs_SOLID, TopAbs_FACE
from OCP.TopoDS import TopoDS
from OCP.Bnd import Bnd_Box
from OCP.BRepBndLib import BRepBndLib
from OCP.BRepAdaptor import BRepAdaptor_Surface
from OCP.GeomAbs import GeomAbs_Cylinder, GeomAbs_Plane
from OCP.BRepCheck import BRepCheck_Analyzer
HERE = Path(__file__).resolve().parent
def bounds(s):
b = Bnd_Box()
BRepBndLib.AddOptimal_s(s, b, False, False)
return list(b.Get())
def xyz(p):
return [p.X(), p.Y(), p.Z()]
r = STEPControl_Reader()
assert r.ReadFile(str(HERE / '5988 RS-232 Level Shifter.step')) == IFSelect_RetDone
r.TransferRoots()
entities = dict(re.findall(r'#(\d+)\s*=\s*(.*?);', (HERE / '5988 RS-232 Level Shifter.step').read_text(), re.S))
styles = {}
for text in entities.values():
if text.startswith('STYLED_ITEM('):
refs = re.findall(r'#(\d+)', text)
styles[refs[-1]] = refs[:-1]
def colors(refs, seen=None):
seen = set() if seen is None else seen
result = []
for ref in refs:
if ref in seen:
continue
seen.add(ref)
text = entities[ref]
if text.startswith('COLOUR_RGB('):
result.append(text.replace('\n', ''))
else:
result.extend(colors(re.findall(r'#(\d+)', text), seen))
return result
e = TopExp_Explorer(r.OneShape(), TopAbs_SOLID)
out = []
while e.More():
s = TopoDS.Solid_s(e.Current())
item = dict(id=len(out)+1, bounds=bounds(s), valid=BRepCheck_Analyzer(s).IsValid(), faces=[])
f = TopExp_Explorer(s, TopAbs_FACE)
while f.More():
face = TopoDS.Face_s(f.Current())
a = BRepAdaptor_Surface(face)
# These unique bodies retain CLOSED_SHELL face order on this pinned import.
shell_id = {1: '14428', 12: '14431', 13: '14432', 14: '14433'}.get(item['id'])
refs = re.findall(r'#(\d+)', entities[shell_id]) if shell_id else []
step_id = refs[len(item['faces'])] if refs else '0'
if refs:
surface_id = re.findall(r'#(\d+)', entities[step_id])[-1]
surface = entities[surface_id]
expected = str(a.GetType()).split('_')[-1].upper()
assert surface.startswith({'PLANE': 'PLANE(', 'CYLINDER': 'CYLINDRICAL_SURFACE(', 'CONE': 'CONICAL_SURFACE(', 'TORUS': 'TOROIDAL_SURFACE('}[expected])
if a.GetType() == GeomAbs_Cylinder:
assert abs(float(surface.rsplit(',', 1)[1].rstrip(')')) - a.Cylinder().Radius()) < 1e-8
row = dict(id=len(item['faces'])+1, bounds=bounds(face), type=str(a.GetType()), orientation=str(face.Orientation()))
row.update(step_id=step_id, colors=colors(styles.get(step_id, [])))
if a.GetType() == GeomAbs_Cylinder:
c = a.Cylinder()
row.update(radius=c.Radius(), location=xyz(c.Location()), axis=xyz(c.Axis().Direction()))
elif a.GetType() == GeomAbs_Plane:
p = a.Plane()
row.update(location=xyz(p.Location()), normal=xyz(p.Axis().Direction()))
item['faces'].append(row)
f.Next()
if shell_id:
assert len(item['faces']) == len(refs)
out.append(item)
e.Next()
(HERE / 'geometry.json').write_text(json.dumps(out, indent=2)+'\n')
for s in out:
print(s['id'], 'valid', s['valid'], 'bounds', [round(v,6) for v in s['bounds']], 'faces', len(s['faces']))
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,207 @@
{
"front_flange_exposed_faces": {
"solid": 12,
"faces": [
8,
11,
12,
13,
14
],
"bounds": [
0.47499999999999787,
27.006999999999998,
1.5726202399843388,
31.275000000000002,
27.407,
14.1226202399843
],
"dimensions": [
30.800000000000004,
0.40000000000000213,
12.549999999999962
]
},
"shell_straight_outer_surface": {
"solid": 12,
"faces": [
18,
19,
20,
21,
22,
23,
24,
25
],
"bounds": [
8.035981107410171,
28.006999999999998,
3.89762023998434,
23.71401889258985,
32.607,
11.79762023998434
],
"dimensions": [
15.678037785179677,
4.600000000000001,
7.9
]
},
"shell_outer_including_root_and_lip": {
"solid": 12,
"faces": [
18,
19,
20,
21,
22,
23,
24,
25,
81,
82,
83,
84,
85,
86,
87,
88,
103,
104,
105,
106,
107,
108,
109,
110
],
"bounds": [
7.4359811074101705,
27.406999999999996,
3.29762023998434,
24.31401889258985,
33.207,
12.397620239984342
],
"dimensions": [
16.87803778517968,
5.800000000000004,
9.100000000000001
]
},
"left_front_hex_post": {
"solid": 12,
"faces": [
53,
54,
55,
56,
57,
58,
59,
60,
61,
89
],
"bounds": [
0.7191887617277004,
27.407,
5.547620239984319,
6.030811238272291,
32.207,
10.147620239984366
],
"dimensions": [
5.31162247654459,
4.800000000000001,
4.600000000000048
]
},
"right_front_hex_post": {
"solid": 12,
"faces": [
62,
63,
64,
65,
66,
67,
68,
69,
70,
90
],
"bounds": [
25.71918876172773,
27.407,
5.547620239984328,
31.030811238272328,
32.207,
10.14762023998434
],
"dimensions": [
5.311622476544599,
4.800000000000001,
4.600000000000012
]
},
"front_lock_axes": [
{
"id": 60,
"bounds": [
1.875,
27.407,
6.34762023998434,
4.875,
32.007,
9.34762023998434
],
"type": "GeomAbs_SurfaceType.GeomAbs_Cylinder",
"orientation": "TopAbs_Orientation.TopAbs_REVERSED",
"step_id": "13822",
"colors": [
"COLOUR_RGB('Opaque(245,245,246)',0.96078431372549,0.96078431372549,0.964705882352941)"
],
"radius": 1.5,
"location": [
3.375,
32.207,
7.84762023998434
],
"axis": [
0.0,
-1.0,
0.0
]
},
{
"id": 69,
"bounds": [
26.875,
27.407,
6.34762023998434,
29.875,
32.007,
9.34762023998434
],
"type": "GeomAbs_SurfaceType.GeomAbs_Cylinder",
"orientation": "TopAbs_Orientation.TopAbs_REVERSED",
"step_id": "13831",
"colors": [
"COLOUR_RGB('Opaque(245,245,246)',0.96078431372549,0.96078431372549,0.964705882352941)"
],
"radius": 1.5,
"location": [
28.375,
32.207,
7.84762023998434
],
"axis": [
0.0,
-1.0,
0.0
]
}
]
}
@@ -0,0 +1,17 @@
[
{
"file": "5988 RS-232 Level Shifter.step",
"url": "https://raw.githubusercontent.com/adafruit/Adafruit_CAD_Parts/5b8a21fb5e2e48478faf8901077be812f5224066/5988%20RS-232%20Level%20Shifter/5988%20RS-232%20Level%20Shifter.step",
"sha256": "8eab9dc591a985fc987ceec2d7534e5e259263c73bebc64c0c3cd847e135906f"
},
{
"file": "LICENSE",
"url": "https://raw.githubusercontent.com/adafruit/Adafruit_CAD_Parts/5b8a21fb5e2e48478faf8901077be812f5224066/LICENSE",
"sha256": "f60436644e066301d4c2e8f1f0674a157b1f60e4fd5586717b99dde255cc18cd"
},
{
"file": "README.md",
"url": "https://raw.githubusercontent.com/adafruit/Adafruit_CAD_Parts/5b8a21fb5e2e48478faf8901077be812f5224066/README.md",
"sha256": "9ed9beac7600623b7b9bd0d02e1fdcd86610ce22966723179e6c6b618e399dce"
}
]
@@ -0,0 +1,46 @@
"""Validate registration and selected face subsets; no board writes."""
import hashlib
import json
from pathlib import Path
import xml.etree.ElementTree as ET
HERE = Path(__file__).resolve().parent
D = json.loads((HERE / 'geometry.json').read_text())
assert len(D) == 15 and all(s['valid'] for s in D)
for source in json.loads((HERE / 'sources.json').read_text()):
assert hashlib.sha256((HERE / source['file']).read_bytes()).hexdigest() == source['sha256']
board = HERE.parent / 'Adafruit RS-232 Full Pinout Level-Shifter Breakout.brd'
assert hashlib.sha256(board.read_bytes()).hexdigest() == '6a7b35ef909f5a2d243c326c34db3957aaa01275bf2aac8e0c749ff18162e870'
root = ET.parse(board).getroot()
b = root.find('drawing/board')
jp = b.find("elements/element[@name='JP2']")
pkg = b.find("libraries/library[@name='microbuilder']/packages/package[@name='1X12_ROUND_76MIL']")
for pad in pkg.findall('pad'):
x, y = float(jp.get('x')) + float(pad.get('x')), float(jp.get('y')) + float(pad.get('y'))
assert any(abs(f.get('radius', 0)-0.5)<1e-8 and abs(f['location'][0]-x)<1e-8 and abs(f['location'][1]-y)<1e-8 for f in D[0]['faces'])
for x in [3.81, 27.94]:
assert any(abs(f.get('radius',0)-1.5)<1e-8 and abs(f['location'][0]-x)<1e-8 and abs(f['location'][1]-11.43)<1e-8 for f in D[0]['faces'])
assert all(abs(a-b)<1e-8 for a,b in zip(D[0]['bounds'], [0,0,0,31.75,29.337,1.57]))
subsets = {
'front_flange_exposed_faces': [8,11,12,13,14],
'shell_straight_outer_surface': list(range(18,26)),
'shell_outer_including_root_and_lip': list(range(18,26))+list(range(81,89))+list(range(103,111)),
'left_front_hex_post': list(range(53,62))+[89],
'right_front_hex_post': list(range(62,71))+[90],
}
result = {}
for name, ids in subsets.items():
faces = [D[11]['faces'][i-1] for i in ids]
assert all(any('Opaque(245,245,246)' in c for c in f['colors']) for f in faces)
bounds = [min(f['bounds'][i] for f in faces) for i in range(3)] + [max(f['bounds'][i] for f in faces) for i in range(3,6)]
result[name] = dict(solid=12, faces=ids, bounds=bounds, dimensions=[bounds[i+3]-bounds[i] for i in range(3)])
left, right = [D[11]['faces'][i-1] for i in [60,69]]
assert abs(right['location'][0]-left['location'][0]-25)<1e-8
assert left['radius'] == right['radius'] == 1.5
result['front_lock_axes'] = [left, right]
(HERE / 'measurements.json').write_text(json.dumps(result,indent=2)+'\n')
print('PASS: source hashes, 15 valid solids, all 12 Eagle header centres, PCB bounds, mounting holes, metal-appearance subsets and front lock cylinders.')
for name, row in result.items():
if isinstance(row,dict):
print(name, [round(v,6) for v in row['bounds']], 'size', [round(v,6) for v in row['dimensions']])