建立方向盘项目初始版本
This commit is contained in:
131
scripts/build_profile_mount_check.py
Normal file
131
scripts/build_profile_mount_check.py
Normal file
@@ -0,0 +1,131 @@
|
||||
"""2020 corner bracket interference check, not a production drawing."""
|
||||
import math, time
|
||||
from pathlib import Path
|
||||
import FreeCAD as A, FreeCADGui as G, Part, Import
|
||||
from pivy import coin
|
||||
|
||||
ROOT=Path(__file__).resolve().parent.parent
|
||||
OUT=ROOT/'models'/'profile_mount_check'
|
||||
OUT.mkdir(parents=True,exist_ok=True)
|
||||
V=A.Vector
|
||||
src=A.openDocument(str(ROOT/'models'/'encoder_print_v1'/'EncoderAssembly_Fit.FCStd'))
|
||||
base={k:src.getObject(k).Shape.copy() for k in ('Motor','Holder','Magnet','Bracket','PCB','Chip','Shim0','Shim1','Shim2','Shim3','PCBHead1','PCBHead2','PCBHead3')}
|
||||
A.closeDocument(src.Name)
|
||||
|
||||
def cylinder(r,h,x,y,z): return Part.makeCylinder(r,h,V(x,y,z))
|
||||
def corner(x,y,lift,side):
|
||||
# Max outward fit with 3.9mm screw in assumed 5.5mm slot: offset 2.75mm,
|
||||
# leaving only 0.05mm clearance at slot end. Extreme tolerance assumption.
|
||||
start=x-6.0
|
||||
flat=Part.makeBox(20,18,3,V(start,y-9,lift))
|
||||
# slot 9.5 long, width assumed 5.5. Fused 4mm rectangle and 2 end arcs.
|
||||
sc=x+2.75
|
||||
slot=Part.makeBox(4,5.5,3.04,V(sc-2,y-2.75,lift-.02))
|
||||
slot=slot.fuse(cylinder(2.75,3.04,sc-2,y,lift-.02)).fuse(cylinder(2.75,3.04,sc+2,y,lift-.02))
|
||||
flat=flat.cut(slot)
|
||||
wall=Part.makeBox(3,18,20,V(start+17,y-9,lift))
|
||||
hole=Part.makeCylinder(2.75,3.04,V(start+16.98,y,lift+10),V(1,0,0))
|
||||
wall=wall.cut(hole)
|
||||
shape=flat.fuse(wall).removeSplitter()
|
||||
if side<0: shape=shape.mirror(V(0,0,0),V(1,0,0))
|
||||
return shape
|
||||
|
||||
def add(doc,name,label,shape,color,trans=0):
|
||||
obj=doc.addObject('PartDesign::Feature',name); obj.Label=label;obj.Shape=shape
|
||||
obj.ViewObject.ShapeColor=color;obj.ViewObject.LineColor=(.1,.1,.1);obj.ViewObject.Transparency=trans
|
||||
return obj
|
||||
|
||||
def run(title,lift):
|
||||
doc=A.newDocument(title)
|
||||
colors={'Motor':(.3,.32,.35),'Holder':(.98,.55,.1),'Magnet':(.8,.8,.8),'Bracket':(.15,.43,.85),'PCB':(.04,.65,.19),'Chip':(.1,.1,.1)}
|
||||
objects={}
|
||||
for k,shape in base.items():
|
||||
col=colors.get(k,(.75,.75,.75)); objects[k]=add(doc,k,'Encoder existing '+k,shape,col,70 if k=='Motor' else 0)
|
||||
# Encoder M4 heads were absent from original assembly. Their outer diameter
|
||||
# 7 mm and height 3 mm are explicit approximations, not measured hardware.
|
||||
encoderheads=[]
|
||||
for i in range(4):
|
||||
a=math.radians(45+i*90); x,y=25*math.cos(a),25*math.sin(a)
|
||||
shape=cylinder(3.5,3,x,y,2.5)
|
||||
encoderheads.append(add(doc,'EncoderM4Head%d'%i,'Encoder M4 head 7x3 assumed',shape,(.8,.8,.8)))
|
||||
p=38/math.sqrt(2); arms=[]; rails=[]; spacers=[]
|
||||
# Each extrusion face touches the bracket wall at x=start+20. y extends 55
|
||||
# each way, to span both bolts. Solid 20x20 envelope, channels omitted.
|
||||
for side in [-1,1]:
|
||||
for y in [-p,p]:
|
||||
arm=corner(p,y,lift,side)
|
||||
arms.append(add(doc,'Corner%d'%len(arms),'2020 corner L slotted to motor',arm,(.84,.47,.13)))
|
||||
if lift:
|
||||
sp=cylinder(4,lift,side*p,y,0).cut(cylinder(2.2,lift+.02,side*p,y,-.01))
|
||||
spacers.append(add(doc,'Spacer%d'%len(spacers),'M4 spacer 7 mm assumed',sp,(.7,.7,.7)))
|
||||
# M4 fastener shank extends into motor and through spacer+leg.
|
||||
screw=cylinder(1.95,lift+8,side*p,y,-5)
|
||||
head=cylinder(3.5,2.5,side*p,y,lift+3)
|
||||
add(doc,'MountM4_%d'%len(arms),'Motor M4 screw envelope schematic',screw.fuse(head),(.8,.8,.8))
|
||||
x=p-6.0+20
|
||||
box=Part.makeBox(20,110,20,V(x,-55,lift))
|
||||
if side<0:box=box.mirror(V(),V(1,0,0))
|
||||
rails.append(add(doc,'Rail%d'%len(rails),'2020 solid envelope length 110 assumed',box,(.55,.56,.6)))
|
||||
doc.recompute()
|
||||
if any(o.Shape.isNull() or not o.Shape.isValid() for o in doc.Objects if hasattr(o,'Shape')):
|
||||
raise RuntimeError('Invalid shape')
|
||||
checks=[]; intersections=[]
|
||||
targets=[objects['Bracket'],objects['PCB'],objects['Chip'],objects['Holder'],objects['Magnet']]+encoderheads+[objects['Shim%d'%i] for i in range(4)]+[objects['PCBHead%d'%i] for i in range(1,4)]+[objects['Motor']]
|
||||
for i,c in enumerate(arms+rails):
|
||||
for t in targets:
|
||||
if not c.Shape.BoundBox.intersect(t.Shape.BoundBox): continue
|
||||
intersection=c.Shape.common(t.Shape); volume=intersection.Volume
|
||||
if volume>0.0001:
|
||||
checks.append((c.Name,t.Name,volume)); intersections.append((c.Name,t.Name,intersection))
|
||||
# also compare other corner angles and rails, excluding touching intended faces
|
||||
for i,a in enumerate(arms):
|
||||
for b in arms[i+1:]+rails:
|
||||
if not a.Shape.BoundBox.intersect(b.Shape.BoundBox):continue
|
||||
vol=a.Shape.common(b.Shape).Volume
|
||||
if vol>.0001:checks.append((a.Name,b.Name,vol))
|
||||
report=['Layout: 4 corner brackets and 20x20 solid profile envelopes; lift %.1fmm.'%lift,
|
||||
'PCD76=45deg, oval width 5.5mm, slot offset +2.75mm outward, profile length 110mm all assumed.',
|
||||
'M4 shank assumed 3.9mm: extreme position leaves 0.05mm radial clearance.',
|
||||
'Encoder M4 head diameter 7mm height 3mm, 0.5mm bracket shim assumed.',
|
||||
'Positive-volume collisions (>0.0001mm3):']
|
||||
report += ['%s vs %s: %.5f mm3'%x for x in checks] or ['none in the checked part pairs']
|
||||
(OUT/(title+'_check.txt')).write_text('\n'.join(report)+'\n',encoding='utf8')
|
||||
for i,(a,b,shape) in enumerate(intersections):
|
||||
add(doc,'Interference%d'%i,'INTERFERENCE %s / %s'%(a,b),shape,(1.,0.,0.))
|
||||
# Semi-transparent corner brackets reveal separately selectable collision solids.
|
||||
for arm in arms: arm.ViewObject.Transparency=30
|
||||
doc.recompute()
|
||||
view=G.activeDocument().activeView()
|
||||
def render(mode,name):
|
||||
if mode=='top': view.viewTop()
|
||||
else: view.getCameraNode().orientation.setValue(coin.SbRotation(0.880476,0.115917,-0.279848,-0.364705))
|
||||
G.updateGui();time.sleep(.5);G.updateGui();view.fitAll();G.updateGui();time.sleep(.5);G.updateGui()
|
||||
view.saveImage(str(OUT/name),1600,1200,'White')
|
||||
render('top',title+'_rear.png')
|
||||
render('axon',title+'_axon.png')
|
||||
# Focus screenshot: only one corner, original blue bracket and red
|
||||
# exact intersection solids are visible. No objects are repositioned.
|
||||
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
|
||||
objects['Bracket'].ViewObject.Visibility=True
|
||||
arms[0].ViewObject.Visibility=True
|
||||
encoderheads[2].ViewObject.Visibility=True
|
||||
for o in doc.Objects:
|
||||
if o.Name.startswith('Interference') and 'Corner0 /' in o.Label: o.ViewObject.Visibility=True
|
||||
render('top',title+'_detail.png')
|
||||
for o in doc.Objects:
|
||||
if hasattr(o,'Shape'): o.ViewObject.Visibility=states[o.Name]
|
||||
render('top',title+'_rear.png')
|
||||
doc.recompute();doc.saveAs(str(OUT/(title+'.FCStd')))
|
||||
Import.export([o for o in doc.Objects if hasattr(o,'Shape')],str(OUT/(title+'.step')))
|
||||
A.closeDocument(doc.Name)
|
||||
return checks
|
||||
|
||||
try:
|
||||
c1=run('Direct',0)
|
||||
(OUT/'BUILD_OK.txt').write_text('success\n',encoding='utf8')
|
||||
except Exception as ex:
|
||||
import traceback
|
||||
(OUT/'BUILD_ERROR.txt').write_text(traceback.format_exc(),encoding='utf8')
|
||||
raise
|
||||
Reference in New Issue
Block a user