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