建立方向盘项目初始版本

This commit is contained in:
2026-10-29 22:04:55 +08:00
commit 9962cbfbce
834 changed files with 1519203 additions and 0 deletions

View File

@@ -0,0 +1,159 @@
"""Closed-bore pillar concept for M9C12 / MT6826S; NOT a print-ready part.
Run with FreeCAD GUI via rebuild_pcd50_pillar_concept.FCMacro. The existing
models are input only. All nominal dimensions and limitations are in the report.
"""
from pathlib import Path
import math, time, traceback
import FreeCAD as A, FreeCADGui as G, Part, Import
from pivy import coin
R=Path(__file__).resolve().parent.parent
O=R/'models'/'pcd50_pillar_concept'; O.mkdir(parents=True,exist_ok=True)
V=A.Vector
def cy(r,h,x,y,z): return Part.makeCylinder(r,h,V(x,y,z))
def add(d,n,label,s,c,t=0):
o=d.addObject('PartDesign::Feature',n);o.Label=label;o.Shape=s
o.ViewObject.ShapeColor=c;o.ViewObject.LineColor=(.1,.1,.1);o.ViewObject.Transparency=t
return o
try:
src=A.openDocument(str(R/'models'/'pcd50_fit_concept'/'PCD50_Assembly.FCStd'))
names=['Motor','Holder','Magnet','PCB','Chip']+['PCBHead%d'%i for i in range(1,4)]+['Corner%d'%i for i in range(4)]+['Rail%d'%i for i in range(2)]
sh={n:src.getObject(n).Shape.copy() for n in names}
old=src.getObject('OpenBracket').Shape.copy()
A.closeDocument(src.Name)
# PCD50 source uses corner offset +2.0mm; move 0.5mm farther out.
for n in ['Corner%d'%i for i in range(4)]+['Rail%d'%i for i in range(2)]:
c=(sh[n].BoundBox.XMin+sh[n].BoundBox.XMax)/2
sh[n].translate(V(.5 if c>0 else -.5,0,0))
pts=[(25*math.cos(math.radians(45+90*i)),25*math.sin(math.radians(45+90*i))) for i in range(4)]
# Rebuild a complete panel rather than inherit the prior U-slot tool gaps.
# Preserve the original three PCB pads, not its open 50mm fastener ears.
pcbpads=old.common(Part.makeBox(120,120,3.2,V(-60,-60,10.9)))
panel=Part.makeBox(49,49,3.2,V(-24.5,-24.5,10.9)).cut(cy(16.8,3.22,0,0,10.89)).fuse(pcbpads)
p76=38/math.sqrt(2)
for sx in (-1,1):
for sy in (-1,1):panel=panel.cut(cy(5.2,3.24,sx*p76,sy*p76,10.88))
columns=[]; cutters=[]
for i in range(4):
bb=sh['Corner%d'%i].BoundBox
cutters.append(Part.makeBox(bb.XLength+.4,bb.YLength+.4,3.22,V(bb.XMin-.2,bb.YMin-.2,-.01)))
bracket=panel
for x,y in pts:
# Carry each column through the panel thickness so the screw head bears on
# a complete closed ring rather than hanging over the old open-panel slot.
col=cy(4.5,13.6,x,y,.5)
for cut in cutters:col=col.cut(cut)
bracket=bracket.fuse(col)
for x,y in pts:bracket=bracket.cut(cy(2.2,13.8,x,y,.4))
bracket=bracket.removeSplitter()
if bracket.isNull() or not bracket.isValid() or len(bracket.Solids)!=1:raise RuntimeError('Invalid / disconnected closed-hole bracket')
# Four identical nominal 0.5mm support shims, not under 76mm angle brackets.
shims=[]
for x,y in pts:
s=cy(4.5,.5,x,y,0).cut(cy(2.2,.52,x,y,-.01))
for cut in cutters:s=s.cut(cut)
s=s.removeSplitter()
if s.isNull() or not s.isValid() or len(s.Solids)!=1:raise RuntimeError('invalid shim')
shims.append(s)
doc=A.newDocument('PCD50_PillarConcept');objs={}
colors={'Motor':(.32,.34,.37),'Holder':(.98,.55,.1),'Magnet':(.8,.8,.8),'PCB':(.04,.65,.19),'Chip':(.1,.1,.1)}
for n in names:
c=colors.get(n,(.84,.47,.13) if n.startswith('Corner') else (.55,.56,.60) if n.startswith('Rail') else (.8,.8,.8))
objs[n]=add(doc,n,n,sh[n],c,70 if n=='Motor' else 20 if n.startswith('Corner') else 0)
b=add(doc,'PillarBracket','PCD50 closed-bore long pillar - conceptual',bracket,(.12,.43,.88))
shimobjs=[add(doc,'Shim%d'%i,'PCD50 0.5mm shim - illustrative',s,(.70,.70,.75)) for i,s in enumerate(shims)]
heads=[];shafts=[]
for i,(x,y) in enumerate(pts):
heads.append(add(doc,'Head50_%d'%i,'M4 head Ø7x3 assumed',cy(3.5,3,x,y,14.1),(.8,.8,.8)))
shafts.append(add(doc,'Shaft50_%d'%i,'M4x20 illustrative - nominal 5.9mm engagement',cy(2,20,x,y,-5.9),(.8,.8,.8)))
p=38/math.sqrt(2);mount76=[];washers76=[]
for sx in (-1,1):
for sy in (-1,1):
x,y=sx*p,sy*p
washer=cy(5,.8,x,y,3).cut(cy(2.15,.82,x,y,2.99)).removeSplitter()
washers76.append(add(doc,'Washer76_%d'%len(washers76),'M4 washer Ø10x0.8 assumed',washer,(.75,.75,.77)))
screw=cy(2,8,x,y,-4.2).fuse(cy(3.5,2.5,x,y,3.8)).removeSplitter()
mount76.append(add(doc,'Mount76_%d'%len(mount76),'M4x8 angle screw conceptual',screw,(.8,.8,.8)))
doc.recompute()
corners=[objs['Corner%d'%i] for i in range(4)];rails=[objs['Rail%d'%i] for i in range(2)]
pairs=[]
for a in [b]+shimobjs+heads+shafts:
for c in corners+rails:pairs.append((a,c))
for a in shafts:
for c in [b]+shimobjs:pairs.append((a,c))
for a in mount76+washers76:
for c in corners+rails+[b]+shimobjs+heads+shafts:pairs.append((a,c))
for a in [b]+shimobjs+heads+shafts:
for n in ['Motor','Holder','Magnet','PCB','Chip']+['PCBHead%d'%i for i in range(1,4)]:pairs.append((a,objs[n]))
hits=[]
for a,c in pairs:
if a.Shape.BoundBox.intersect(c.Shape.BoundBox):
vol=a.Shape.common(c.Shape).Volume
if vol>.001:hits.append('%s / %s %.6fmm3'%(a.Name,c.Name,vol))
# The motor-facing 50mm bores must remain complete; no side exit. Sample
# immediately outside the bore in 72 directions at a low and high Z.
bore=[]
for i,(x,y) in enumerate(pts):
for z in (1.0,13.0):
missing=sum(not bracket.isInside(V(x+2.22*math.cos(a*math.pi/36),y+2.22*math.sin(a*math.pi/36),z),1e-7,True) for a in range(72))
bore.append('Pillar%d z=%.1f closed-bore surrounding shell missing samples: %d/72'%(i,z,missing))
access=[]
for i,(x,y) in enumerate(pts):
tool=cy(3.75,20,x,y,17.11);found=[]
for o in [b]+corners+rails:
if tool.BoundBox.intersect(o.Shape.BoundBox):
v=tool.common(o.Shape).Volume
if v>.001:found.append('%s %.4f'%(o.Name,v))
access.append('M4 tool %d: %s'%(i,', '.join(found) if found else 'no checked obstruction'))
for sx in (-1,1):
for sy in (-1,1):
x,y=sx*p,sy*p;tool=cy(3.75,20,x,y,6.31);found=[]
for o in [b]+corners+rails:
if tool.BoundBox.intersect(o.Shape.BoundBox):
v=tool.common(o.Shape).Volume
if v>.001:found.append('%s %.4f'%(o.Name,v))
access.append('76 M4 tool %+.0f,%+.0f: %s'%(sx,sy,', '.join(found) if found else 'no checked obstruction'))
headareas=[]
for x,y in pts:
count=0;step=.2
for ix in range(-18,19):
for iy in range(-18,19):
dx,dy=ix*step,iy*step
if dx*dx+dy*dy<=3.5**2 and bracket.isInside(V(x+dx,y+dy,14.09),1e-7,True):count+=1
headareas.append(count*step*step)
report=['CONCEPT ONLY: PCD50/PCD76 phase45 and slot9.5x5.5 assumed.',
'Corners flush to motor, moved 2.5mm outward from slot midpoint; 4mm screw nominal slot-end clearance 0.25mm.',
'Pillars 0.5..10.9mm and panel 10.9..14.1mm; closed Ø4.4mm bore.',
'M4x20 nominal screw engagement in motor 20 - 14.1 = 5.9mm, true tapped depth unknown.',
'Pillar cylinders Ø9 extend from z0.5 to 14.1 and merge through panel; clipped at z0.5..3.21 against expanded corner projection 0.2mm.',
'Minimum low-side wall near angle ~0.54mm theoretical, no print/strength guarantee.',
'Estimated head bearing material by sampling (mm2): '+', '.join('%.2f'%x for x in headareas),
'Selected positive-volume intersections >0.001mm3:']+(hits or ['none'])+['Tool envelope Ø7.5:']+access+['Low-bore shell check (positive shell is expected):']+bore
(O/'geometry_check.txt').write_text('\n'.join(report)+'\n',encoding='utf8')
G.activateWorkbench('PartWorkbench');view=G.activeDocument().activeView()
def render(top,path):
if top:view.viewTop()
else:
view.viewAxonometric();G.updateGui();time.sleep(.5)
view.getCameraNode().orientation.setValue(coin.SbRotation(.880476,.115917,-.279848,-.364705))
G.updateGui();time.sleep(.8);view.fitAll();G.updateGui();time.sleep(.8);G.updateGui();view.saveImage(str(O/path),1600,1200,'White')
render(True,'rear.png');render(False,'assembly.png')
states={o.Name:o.ViewObject.Visibility for o in doc.Objects if hasattr(o,'Shape')}
for o in doc.Objects:
if hasattr(o,'Shape'):o.ViewObject.Visibility=False
b.ViewObject.Visibility=True;render(False,'bracket.png')
# Remove the outer half of the two upper pillars to show the bore cross
# section, then discard this object before saving the actual assembly.
b.ViewObject.Visibility=False
section=bracket.cut(Part.makeBox(120,60,25,V(-60,-60-25/math.sqrt(2),0))).removeSplitter()
sectionobj=add(doc,'SectionPreview','Half section for illustration only',section,(.12,.43,.88))
doc.recompute();render(False,'section.png');doc.removeObject(sectionobj.Name)
for o in doc.Objects:
if hasattr(o,'Shape'):o.ViewObject.Visibility=states[o.Name]
render(False,'assembly.png');doc.recompute();doc.saveAs(str(O/'PCD50_Pillar_Assembly.FCStd'))
Import.export([o for o in doc.Objects if hasattr(o,'Shape')],str(O/'PCD50_Pillar_Assembly.step'))
(O/'DONE.txt').write_text('ok\n',encoding='utf8')
except Exception:
(O/'ERROR.txt').write_text(traceback.format_exc(),encoding='utf8');raise