建立方向盘项目初始版本
This commit is contained in:
128
scripts/build_pcd50_open_pillar.py
Normal file
128
scripts/build_pcd50_open_pillar.py
Normal 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
|
||||
Reference in New Issue
Block a user