建立方向盘项目初始版本

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,128 @@
"""PCD50 3/4-open pillar print trial. Run through FreeCAD GUI macro."""
from pathlib import Path
import math, time, traceback
import FreeCAD as A, FreeCADGui as G, Part, Mesh, Import
from pivy import coin
R=Path(__file__).resolve().parent.parent
O=R/'models'/'pcd50_open_pillar_v1'; O.mkdir(parents=True,exist_ok=True)
V=A.Vector
def cy(r,h,x=0,y=0,z=0): return Part.makeCylinder(r,h,V(x,y,z))
def item(d,n,label,s,c,trans=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=trans;return o
def check(n,s):
if s.isNull() or not s.isValid() or len(s.Solids)!=1 or s.Volume<=0:raise RuntimeError('Invalid solid: '+n)
def wedge(x,y,z,h):
# A 90 degree sector points 22.5 degrees from the outward X direction toward
# the nearby corner; mirrored independently for all four quadrants.
sx=1 if x>0 else -1;sy=1 if y>0 else -1
points=[V(x,y,z)]
for i in range(19):
a=math.radians(-22.5+90.0*i/18.0)
points.append(V(x+sx*20*math.cos(a),y+sy*20*math.sin(a),z))
points.append(points[0])
return Part.Face(Part.Wire(Part.makePolygon(points).Edges)).extrude(V(0,0,h))
try:
src=A.openDocument(str(R/'models'/'pcd50_pillar_concept'/'PCD50_Pillar_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)]+['Mount76_%d'%i for i in range(4)]+['Washer76_%d'%i for i in range(4)]+['Head50_%d'%i for i in range(4)]+['Shaft50_%d'%i for i in range(4)]
sh={n:src.getObject(n).Shape.copy() for n in names}; old=src.getObject('PillarBracket').Shape.copy()
A.closeDocument(src.Name)
pts=[(25*math.cos(math.radians(45+90*i)),25*math.sin(math.radians(45+90*i))) for i in range(4)]
# Retain the already-verified closed-hole 49mm panel and PCB mounting pads.
bracket=old.common(Part.makeBox(120,120,3.2,V(-60,-60,10.9)))
for x,y in pts:
col=cy(6,10.4,x,y,.5).cut(cy(2.2,10.42,x,y,.49)).cut(wedge(x,y,.49,10.42))
check('column',col);bracket=bracket.fuse(col)
bracket=bracket.removeSplitter();check('bracket',bracket)
def shim(x,y,thick,z):
return cy(6,thick,x,y,z).cut(cy(2.2,thick+.02,x,y,z-.01)).cut(wedge(x,y,z-.01,thick+.02)).removeSplitter()
shims=[shim(x,y,.5,0) for x,y in pts]
for i,s in enumerate(shims):check('shim'+str(i),s)
# Printable placement: panel flat on bed, open columns upwards.
parts=A.newDocument('PCD50OpenPillarParts')
orient=bracket.copy();orient.translate(V(0,0,-14.1));orient.rotate(V(),V(1,0,0),180)
neutral05=shim(10,10,.5,0);neutral05.translate(V(-10,-10,0))
neutral10=shim(10,10,1,0);neutral10.translate(V(-10,-10,0))
outparts=[item(parts,'EncoderBracket','PCD50 open pillar bracket',orient,(.1,.42,.85)),item(parts,'Shim05','0.5mm C shim; rotate to opening',neutral05,(.7,.7,.7)),item(parts,'Shim10','1mm C shim; rotate to opening',neutral10,(.7,.7,.7))]
parts.recompute();meshlog=[]
for o in outparts:
check(o.Name,o.Shape);Mesh.export([o],str(O/(o.Name+'.stl')));Import.export([o],str(O/(o.Name+'.step')))
m=Mesh.Mesh(str(O/(o.Name+'.stl')));meshlog.append('%s solid=%s triangles=%d volume=%.5f zmin=%.5f'%(o.Name,m.isSolid(),m.CountFacets,m.Volume,m.BoundBox.ZMin))
if not m.isSolid() or m.Volume<=0 or abs(m.BoundBox.ZMin)>.001:raise RuntimeError('bad mesh '+o.Name)
(O/'mesh_check.txt').write_text('\n'.join(meshlog)+'\n',encoding='utf8')
parts.saveAs(str(O/'PrintParts.FCStd'));A.closeDocument(parts.Name)
# Real assembled orientation, from existing motor and hardware geometry.
d=A.newDocument('PCD50OpenPillarAssembly'); colors={'Motor':(.32,.34,.37),'Holder':(.98,.55,.1),'Magnet':(.8,.8,.8),'PCB':(.04,.65,.19),'Chip':(.1,.1,.1)}
objs={}
for n in names:
col=colors.get(n,(.84,.47,.13) if n.startswith('Corner') else (.55,.56,.60) if n.startswith('Rail') else (.8,.8,.8))
objs[n]=item(d,n,n,sh[n],col,70 if n=='Motor' else 20 if n.startswith('Corner') else 0)
b=item(d,'OpenPillarBracket','four open pillars - print trial',bracket,(.12,.43,.88))
shimobjs=[item(d,'AdjustShim%d'%i,'0.5mm C shim',s,(.7,.7,.75)) for i,s in enumerate(shims)]
d.recompute();hits=[]
# Test all printed parts and all motor screws against every other object;
# intended mating contact is only a shared zero-volume face.
allparts=[o for o in d.Objects if hasattr(o,'Shape')];focus=[b]+shimobjs
for a in focus:
for c in allparts:
if a.Name==c.Name:continue
if not a.Shape.BoundBox.intersect(c.Shape.BoundBox):continue
vol=a.Shape.common(c.Shape).Volume
if vol>.001:hits.append('%s / %s %.6fmm3'%(a.Name,c.Name,vol))
# Motor-side nominal separation of every pillar from every corner.
distances=[]
for i,(x,y) in enumerate(pts):
part=bracket.common(cy(6.01,10.39,x,y,.5))
dmin=min(part.distToShape(sh['Corner%d'%j])[0] for j in range(4))
distances.append(dmin)
# Clearance for an axial 7.5mm diameter tool above each head.
access=[]
for i,(x,y) in enumerate(pts):
tool=cy(3.75,20,x,y,17.11);h=[]
for n in ['Corner%d'%j for j in range(4)]+['Rail%d'%j for j in range(2)]:
v=tool.common(sh[n]).Volume
if v>.001:h.append(n+':%.4f'%v)
access.append('tool%d: %s'%(i,', '.join(h) if h else 'no checked obstruction'))
# Verify top panel material lies under screw head and bores stay open.
heads=[]
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
heads.append(count*step*step)
report=['50 PCD; 76 corners flush against motor, 2.5mm outboard in 9.5x5.5 slot (width assumed).',
'3/4 open pillars outer diameter 12mm, M4 bore 4.4mm; nominal radial wall away from opening 3.8mm.',
'Each 90 degree opening faces 22.5 degrees from outward X toward its respective nearby 76mm corner.',
'PCB and magnet positions retain nominal 1mm gap with 0.5mm C shims.',
'M4x20 bolt without a head washer: nominal motor engagement 5.9mm; actual thread depth unknown.',
'minimum pillar/corner distances mm: '+', '.join('%.4f'%x for x in distances),
'Estimated material under 7mm head by sampling mm2: '+', '.join('%.2f'%x for x in heads),
'Positive volume intersections >0.001mm3 for support and shims:']+(hits or ['none in checked pairs'])+['Tool clearance, 7.5mm cylinder:']+access
(O/'geometry_check.txt').write_text('\n'.join(report)+'\n',encoding='utf8')
if hits: raise RuntimeError('Interference in print trial: '+', '.join(hits))
view=G.activeDocument().activeView();G.activateWorkbench('PartWorkbench')
def render(kind,path):
if kind=='rear':view.viewTop()
else:
view.viewAxonometric();G.updateGui();time.sleep(.5)
view.getCameraNode().orientation.setValue(coin.SbRotation(.880476,.115917,-.279848,-.364705))
G.updateGui();time.sleep(.7);view.fitAll();G.updateGui();time.sleep(.7);view.saveImage(str(O/path),1600,1200,'White')
render('rear','rear.png');render('axon','assembly.png')
state={o.Name:o.ViewObject.Visibility for o in d.Objects if hasattr(o,'Shape')}
for o in d.Objects:
if hasattr(o,'Shape'):o.ViewObject.Visibility=False
b.ViewObject.Visibility=True;render('axon','bracket.png')
# Cutting one half through two bores makes the head-bearing face and slots visible.
b.ViewObject.Visibility=False
sec=bracket.cut(Part.makeBox(120,60,25,V(-60,-60-25/math.sqrt(2),0))).removeSplitter()
so=item(d,'SectionPreview','section preview only',sec,(.12,.43,.88));d.recompute();render('axon','section.png');d.removeObject(so.Name)
for o in d.Objects:
if hasattr(o,'Shape'):o.ViewObject.Visibility=state.get(o.Name,True)
render('axon','assembly.png');d.recompute();d.saveAs(str(O/'Assembly.FCStd'))
Import.export([o for o in d.Objects if hasattr(o,'Shape')],str(O/'Assembly.step'))
(O/'DONE.txt').write_text('ok\n',encoding='utf8')
except Exception:
(O/'ERROR.txt').write_text(traceback.format_exc(),encoding='utf8');raise