Files
wheel/scripts/build_profile_mount_check.py

132 lines
7.0 KiB
Python

"""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