建立方向盘项目初始版本
This commit is contained in:
110
scripts/build_shared76_print.py
Normal file
110
scripts/build_shared76_print.py
Normal file
@@ -0,0 +1,110 @@
|
||||
"""First-fit printable PCD76 bracket. Run in FreeCAD GUI via macro.
|
||||
|
||||
Geometry is based on a 6xx/7 mm body standoff reference; no load rating is implied.
|
||||
"""
|
||||
import math,time,traceback
|
||||
from pathlib import Path
|
||||
import FreeCAD as A, FreeCADGui as G, Part, Import, Mesh
|
||||
from pivy import coin
|
||||
R=Path(__file__).resolve().parent.parent
|
||||
O=R/'models'/'encoder_shared76_print_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 add(doc,name,label,s,c,trans=0):
|
||||
o=doc.addObject('PartDesign::Feature',name);o.Label=label;o.Shape=s
|
||||
o.ViewObject.ShapeColor=c;o.ViewObject.LineColor=(.12,.12,.12);o.ViewObject.Transparency=trans;return o
|
||||
def valid(name,s):
|
||||
if s.isNull() or not s.isValid() or len(s.Solids)!=1 or s.Volume<=0:raise RuntimeError('Invalid solid '+name)
|
||||
def hexprism(x,y,z,h):
|
||||
# 7 mm across flats
|
||||
rr=7/math.sqrt(3)
|
||||
pts=[V(x+rr*math.cos(math.radians(30+i*60)),y+rr*math.sin(math.radians(30+i*60)),z) for i in range(6)];pts.append(pts[0])
|
||||
return Part.Face(Part.Wire(Part.makePolygon(pts).Edges)).extrude(V(0,0,h))
|
||||
try:
|
||||
src=A.openDocument(str(R/'models'/'shared76_concept'/'Shared76.FCStd'))
|
||||
keep=['Motor','Holder','Magnet','PCB','Chip','PCBHead1','PCBHead2','PCBHead3']+['Corner%d'%i for i in range(4)]+['Rail%d'%i for i in range(2)]
|
||||
shapes={n:src.getObject(n).Shape.copy() for n in keep}
|
||||
A.closeDocument(src.Name)
|
||||
src=A.openDocument(str(R/'models'/'encoder_print_v1'/'EncoderAssembly_Fit.FCStd'))
|
||||
oldring=src.getObject('Bracket').Shape.copy().common(Part.makeBox(100,100,3.2,V(-50,-50,10.9)))
|
||||
A.closeDocument(src.Name)
|
||||
# PCB rear/base face z=11.3 with one 0.5mm shim on a 7mm standoff.
|
||||
board_shift=.4; oldring.translate(V(0,0,board_shift))
|
||||
for n in ['PCB','Chip','PCBHead1','PCBHead2','PCBHead3']: shapes[n].translate(V(0,0,board_shift))
|
||||
p=38/math.sqrt(2); frame=Part.makeBox(64,64,3.2,V(-32,-32,11.3)).cut(cy(16.8,3.22,z=11.29)).fuse(oldring)
|
||||
# U slots face outward: left opens to -X and right to +X. Both pairs differ in direction.
|
||||
for side in (-1,1):
|
||||
for y in (-p,p):
|
||||
x=side*p; start=x if side>0 else x-10
|
||||
cutter=Part.makeBox(10,4.6,3.24,V(start,y-2.3,11.28)).fuse(cy(2.3,3.24,x,y,11.28))
|
||||
frame=frame.cut(cutter)
|
||||
frame=frame.removeSplitter();valid('frame',frame)
|
||||
shim05=cy(4.5,.5).cut(cy(2.25,.52,z=-.01));valid('shim05',shim05)
|
||||
shim10=cy(4.5,1).cut(cy(2.25,1.02,z=-.01));valid('shim10',shim10)
|
||||
parts=A.newDocument('Shared76PrintParts')
|
||||
local=frame.copy();local.translate(V(0,0,-11.3));valid('flat frame',local)
|
||||
po=[add(parts,'EncoderBracket','PCD76 encoder bracket print face down',local,(.15,.45,.85)),add(parts,'Shim05','M4 shim 0.5mm',shim05,(.75,.75,.75)),add(parts,'Shim10','M4 shim 1mm',shim10,(.75,.75,.75))]
|
||||
parts.recompute(); mesh_lines=[]
|
||||
for ob in po:
|
||||
Mesh.export([ob],str(O/(ob.Name+'.stl')));Import.export([ob],str(O/(ob.Name+'.step')))
|
||||
mesh=Mesh.Mesh(str(O/(ob.Name+'.stl')));bb=mesh.BoundBox
|
||||
mesh_lines.append('%s solid=%s facets=%d volume=%.4f zmin=%.4f'%(ob.Name,mesh.isSolid(),mesh.CountFacets,mesh.Volume,bb.ZMin))
|
||||
if not mesh.isSolid() or mesh.Volume<=0 or abs(bb.ZMin)>.001:raise RuntimeError('mesh validation failed '+ob.Name)
|
||||
(O/'mesh_check.txt').write_text('\n'.join(mesh_lines)+'\n',encoding='utf8');parts.saveAs(str(O/'Shared76_Print.FCStd'))
|
||||
A.closeDocument(parts.Name)
|
||||
doc=A.newDocument('Shared76Fit');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 keep:
|
||||
c=colors.get(n,(.84,.47,.13) if n.startswith('Corner') else (.55,.56,.6) if n.startswith('Rail') else (.8,.8,.8))
|
||||
objs[n]=add(doc,n,n,shapes[n],c,70 if n=='Motor' else 15 if n.startswith('Corner') else 0)
|
||||
objs['Frame']=add(doc,'EncoderBracket','PCD76 bracket printed part',frame,(.15,.45,.85))
|
||||
stand=[];bottomwash=[];adjust=[];topwash=[];tops=[]
|
||||
for side in (-1,1):
|
||||
for y in (-p,p):
|
||||
x=side*p; i=len(stand)
|
||||
# 3mm corner then 0.8 metal washer, then 7mm body; male 8mm long, 4.2mm nominal thread into motor.
|
||||
washer=cy(5,.8,x,y,3).cut(cy(2.15,.82,x,y,2.99))
|
||||
bottomwash.append(add(doc,'MetalWasher%d'%i,'Metal angle washer 0.8 assumed',washer,(.78,.8,.82)))
|
||||
body=hexprism(x,y,3.8,7).cut(cy(2.1,3.52,x,y,7.29))
|
||||
body=body.fuse(cy(1.95,8,x,y,-4.2)).removeSplitter();valid('standoff',body)
|
||||
stand.append(add(doc,'Standoff%d'%i,'M4 male female 7mm body 8mm male reference',body,(.82,.70,.4)))
|
||||
adj=cy(4.5,.5,x,y,10.8).cut(cy(2.25,.52,x,y,10.79))
|
||||
adjust.append(add(doc,'AdjustmentShim%d'%i,'0.5mm nominal adjustment',adj,(.72,.72,.72)))
|
||||
tw=cy(4.5,.8,x,y,14.5).cut(cy(2.15,.82,x,y,14.49))
|
||||
topwash.append(add(doc,'TopWasher%d'%i,'Top M4 washer 9x0.8 assumed',tw,(.8,.8,.8)))
|
||||
# Chosen illustrative thread penetration 3mm (female minimum reference 3.5mm).
|
||||
bolt=cy(1.9,8,x,y,7.3).fuse(cy(3.5,2.5,x,y,15.3)).removeSplitter()
|
||||
tops.append(add(doc,'TopScrew%d'%i,'M4 screw envelope length 8mm verify depth',bolt,(.8,.8,.8)))
|
||||
doc.recompute()
|
||||
# All pairs of assembled hardware/PCB/support are checked; intended face contact has zero volume.
|
||||
group=[objs[k] for k in keep]+[objs['Frame']]+stand+bottomwash+adjust+topwash+tops
|
||||
intersections=[]
|
||||
for i,a in enumerate(group):
|
||||
for b in group[i+1:]:
|
||||
if a.Name.startswith('PCBHead') and b.Name in ['PCB']: continue # heads bear on board face
|
||||
if not a.Shape.BoundBox.intersect(b.Shape.BoundBox):continue
|
||||
volume=a.Shape.common(b.Shape).Volume
|
||||
if volume>0.001:intersections.append('%s / %s %.5f mm3'%(a.Name,b.Name,volume))
|
||||
report=['Nominal gap 1.4mm: 7mm standoff body, 0.8mm motor washer, 0.5mm encoder shim.',
|
||||
'M4 male 8mm; through 3mm angle and 0.8mm metal washer, nominal motor engagement 4.2mm.',
|
||||
'M4 top shank 8mm through 0.8mm top washer + 3.2mm bracket + 0.5mm shim, female entry 3.5mm.',
|
||||
'Intersections above 0.001 mm3, selected complete simplified assembly pairs:']+(intersections or ['none'])
|
||||
(O/'geometry_check.txt').write_text('\n'.join(report)+'\n',encoding='utf8')
|
||||
# No automatic PASS if a real interference exists: keep report and let human evaluate.
|
||||
view=G.activeDocument().activeView()
|
||||
def render(top,name):
|
||||
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(.7);view.fitAll();G.updateGui();time.sleep(.7);view.saveImage(str(O/name),1600,1200,'White')
|
||||
render(False,'assembly.png');render(True,'rear.png')
|
||||
states={o.Name:o.ViewObject.Visibility for o in doc.Objects if hasattr(o,'Shape')}
|
||||
for ob in doc.Objects:
|
||||
if hasattr(ob,'Shape'):ob.ViewObject.Visibility=False
|
||||
objs['Frame'].ViewObject.Visibility=True;render(True,'bracket.png')
|
||||
for ob in doc.Objects:
|
||||
if hasattr(ob,'Shape'):ob.ViewObject.Visibility=states[ob.Name]
|
||||
render(False,'assembly.png');doc.recompute();doc.saveAs(str(O/'Shared76_Assembly.FCStd'))
|
||||
Import.export([o for o in doc.Objects if hasattr(o,'Shape')],str(O/'Shared76_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