建立方向盘项目初始版本
This commit is contained in:
184
scripts/build_symmetric_corner_18_concept.FCMacro
Normal file
184
scripts/build_symmetric_corner_18_concept.FCMacro
Normal file
@@ -0,0 +1,184 @@
|
||||
from pathlib import Path
|
||||
import FreeCAD as A, FreeCADGui as G, Part, Import, os, math, time, json, traceback
|
||||
from pivy import coin
|
||||
V=A.Vector
|
||||
root=Path(r'C:\projects\wheel'); out=root/'models'/'symmetric_corner_18_concept';out.mkdir(parents=True,exist_ok=True)
|
||||
def box(x,y,z,dx,dy,dz):return Part.makeBox(dx,dy,dz,V(x,y,z))
|
||||
def cyl(r,h,x,y,z,axis=V(0,0,1)):return Part.makeCylinder(r,h,V(x,y,z),axis)
|
||||
def add(doc,n,shape,label,color,trans=0):
|
||||
o=doc.addObject('PartDesign::Feature',n);o.Shape=shape.removeSplitter();o.Label=label;o.ViewObject.ShapeColor=color;o.ViewObject.LineColor=(.12,.12,.12);o.ViewObject.Transparency=trans;return o
|
||||
def vol(a,b):return a.common(b).Volume if a.BoundBox.intersect(b.BoundBox) else 0.
|
||||
def cc(a,b):return {'vol':vol(a,b),'dist':a.distToShape(b)[0]}
|
||||
try:
|
||||
d=A.openDocument(str(root/'models'/'fan_direct_motor_side_140'/'Assembly.FCStd'));d.recompute();d.Label='新电机角码与2mm垫片概念装配'
|
||||
p=38/math.sqrt(2); xinner=p+10.2; xwall=xinner+1.8; xrail=xwall+2; xc=xrail+10
|
||||
# Reference washer sits on bracket's extrusion facing X surface, never between bracket and motor.
|
||||
oldlist=['Corner0','Corner1','Corner2','Corner3','Mount76_0','Mount76_1','Mount76_2','Mount76_3','Washer76_0','Washer76_1','Washer76_2','Washer76_3','OpenPillarBracket','Head50_0','Head50_1','Head50_2','Head50_3','Shaft50_0','Shaft50_1','Shaft50_2','Shaft50_3','AdjustShim0','AdjustShim1','AdjustShim2','AdjustShim3','DirectFan','FanBolt0','FanBolt1','Crossbeam0','Crossbeam1','BaseJoint00','BaseJoint01','BaseJoint10','BaseJoint11','PurchasedAngle00','PurchasedAngle01','PurchasedAngle10','PurchasedAngle11','BoardM5_00','BoardM5_01','BoardM5_10','BoardM5_11','FootHead00','FootHead01','FootHead10','FootHead11']
|
||||
for n in oldlist:
|
||||
o=d.getObject(n)
|
||||
if o:o.ViewObject.Visibility=False
|
||||
# previously purchased profile lengths and axial position, model vertical profiles remain solid envelopes
|
||||
for i,sgn in enumerate((-1,1)):
|
||||
rail=d.getObject('Rail%d'%i); rail.Placement=A.Placement();rail.Shape=box(sgn*xc-10,-55,0,20,140,20);rail.Label='Purchased 2020, 140mm'
|
||||
base=d.getObject('NewRail%d'%i);base.Placement=A.Placement();base.Shape=box(sgn*xc-15,-85,-215,30,30,250);base.Label='Purchased 3030, 250mm'
|
||||
for stem in ['MotorBaseCorner','SwitchCrossJoint']:
|
||||
a=d.getObject(stem+str(i));old_center=(-1 if i==0 else 1)*50.6200577
|
||||
dx=sgn*xc-old_center; s=a.Shape.copy();s.translate(V(dx,0,0));a.Placement=A.Placement();a.Shape=s
|
||||
# Lighting crossbeam 130mm, fixed by supplied stock; no guessed changes to control profile
|
||||
sw=d.getObject('SwitchCross2020');sw.Placement=A.Placement();sw.Shape=box(-65,-55,-142,130,20,20);sw.Label='Purchased 2020 switch 130mm'
|
||||
motor=d.getObject('Motor')
|
||||
# Cut shape: round end at opposite side of bend. Plan view is x-z extrusion directions; y is 16mm width.
|
||||
corners=[];washers=[];studs=[];screws=[]
|
||||
for i,sgn in enumerate((-1,1)):
|
||||
for j,y in enumerate((10-p,10+p)):
|
||||
# +X prototype motor leg. 20 length includes 2mm bend wall, 16 width. Round free end R8.
|
||||
flat=box(xinner-10.2,y-8,0,12,16,1.8)
|
||||
cap=cyl(8,1.8,xinner-10.2,y,0).common(box(xinner-18.2,y-8,0,8,16,1.8))
|
||||
leg=flat.fuse(cap).cut(cyl(2.5,1.9,p,y,-.05))
|
||||
wall=box(xinner,y-8,0,1.8,16,12)
|
||||
cap2=cyl(8,1.8,xinner,y,12,V(1,0,0)).common(box(xinner,y-8,12,1.8,16,8))
|
||||
wall=wall.fuse(cap2).cut(cyl(2.5,1.9,xinner-.05,y,12,V(1,0,0)))
|
||||
corner=leg.fuse(wall).removeSplitter()
|
||||
washer=cyl(4.5,2,xwall,y,12,V(1,0,0)).cut(cyl(2.2,2.1,xwall-.05,y,12,V(1,0,0)))
|
||||
# Motor M4 shank and simple head, countersink depth is unknown
|
||||
motorbolt=cyl(1.95,7,p,y,-5).fuse(cyl(3.5,2.2,p,y,2))
|
||||
if sgn<0:
|
||||
corner=corner.mirror(V(),V(1,0,0));washer=washer.mirror(V(),V(1,0,0));motorbolt=motorbolt.mirror(V(),V(1,0,0))
|
||||
c=add(d,'NewMotorCorner%d%d'%(i,j),corner,'New stainless 20x20x16x1.8 Ø5, round ends assumed',(.9,.55,.13));corners.append(c)
|
||||
w=add(d,'RailWasher%d%d'%(i,j),washer,'2mm total washer BETWEEN motor corner and 2020 rail',(.95,.2,.12));washers.append(w)
|
||||
screws.append(add(d,'NewMotorM4%d%d'%(i,j),motorbolt,'M4 motor shank + head schematic',(.65,.65,.68)))
|
||||
# Encoder bracket as plate-only visualization, 4 hex standoffs per user idea; no claim of finalized printable design
|
||||
old=d.getObject('OpenPillarBracket').Shape
|
||||
plate=old.common(box(-70,-70,10.9,140,150,3.2))
|
||||
bp=add(d,'EncoderPlateConcept',plate,'Encoder panel only; metal standoffs visual concept, not print-ready',(.08,.3,.85))
|
||||
for i in range(4):
|
||||
ang=math.radians(45+90*i);x=25*math.cos(ang);y=10+25*math.sin(ang);Rhex=7/(2*math.cos(math.pi/6))
|
||||
points=[V(x+Rhex*math.cos(math.radians(30+60*k)),y+Rhex*math.sin(math.radians(30+60*k)),0) for k in range(6)];points.append(points[0])
|
||||
hexshape=Part.Face(Part.makePolygon(points)).extrude(V(0,0,10)).cut(cyl(2.2,10.02,x,y,-.01))
|
||||
st=add(d,'EncoderHex%d'%i,hexshape,'M4 7mm AF 10mm stand-off ASSUMED',(.86,.72,.3));studs.append(st)
|
||||
top=cyl(4.5,.9,x,y,10).cut(cyl(2.2,.92,x,y,9.99))
|
||||
add(d,'EncoderSpacer%d'%i,top,'Encoder metal shim .9mm SCHEMATIC',(.72,.72,.75))
|
||||
# Two purchased 70mm beams are centred between the 3030 rails.
|
||||
# The original mounting corners remain at the same ends, translated with the new rails.
|
||||
conflicts=[]; joint_checks=[]
|
||||
for i,z in enumerate((-114.,-179.)):
|
||||
cross=d.getObject('Crossbeam%d'%i); cross.Placement=A.Placement();cross.Shape=box(-35,-85,z,70,30,30);cross.Label='Purchased 3030 crossbeam 70mm';cross.ViewObject.Visibility=True
|
||||
for j,sgn in enumerate((-1,1)):
|
||||
rr=d.getObject('NewRail%d'%j).Shape
|
||||
c=cross.Shape.common(rr)
|
||||
if c.Volume>1e-5: conflicts.append((i,j,c.Volume))
|
||||
name='BaseJoint%d%d'%(i,j); a=d.getObject(name)
|
||||
ss=a.Shape.copy();ss.translate(V(sgn*(xc-50.6200577),0,0));a.Placement=A.Placement();a.Shape=ss;a.ViewObject.Visibility=True
|
||||
joint_checks.append({'name':name,'cross':cc(a.Shape,cross.Shape),'base':cc(a.Shape,rr)})
|
||||
for stem in ('PurchasedAngle','BoardM5_','FootHead'):
|
||||
for j in (0,1):
|
||||
o=d.getObject(stem+str(i)+str(j))
|
||||
if o:
|
||||
o.ViewObject.Visibility=True
|
||||
if stem in ('PurchasedAngle','FootHead'):
|
||||
ss=o.Shape.copy();ss.translate(V(0,2,0));o.Placement=A.Placement();o.Shape=ss
|
||||
# An initial fan frame rotated for new spacing, check conflicts and hide if nonzero.
|
||||
theta=math.degrees(math.acos((2*xc)/(105*math.sqrt(2))))-45;alpha=math.radians(theta)
|
||||
fan=box(-60,-60,20,120,120,25).cut(cyl(56,25.04,0,0,19.98))
|
||||
for x in (-52.5,52.5):
|
||||
for y in (-52.5,52.5):fan=fan.cut(cyl(2.2,25.1,x,y,19.95))
|
||||
fan=fan.removeSplitter();fan.rotate(V(),V(0,0,1),theta);fan.translate(V(0,10,0))
|
||||
f=d.getObject('DirectFan');f.Placement=A.Placement();f.Shape=fan; f.Label='FAN illustrative; rotated to new spacing';f.ViewObject.Visibility=True
|
||||
for sgn,i in [(-1,0),(1,1)]:
|
||||
x=sgn*52.5*(math.cos(alpha)-math.sin(alpha));y=10+sgn*52.5*(math.cos(alpha)+math.sin(alpha))
|
||||
o=d.getObject('FanBolt%d'%i);o.Placement=A.Placement();o.Shape=cyl(2,25,x,y,20).fuse(cyl(3.5,3,x,y,45));o.ViewObject.Visibility=True
|
||||
d.recompute()
|
||||
checks=[]
|
||||
for i,c in enumerate(corners):
|
||||
j=i//2;rr=d.getObject('Rail%d'%j).Shape
|
||||
checks.append({'corner':c.Name,'motor_volume':vol(c.Shape,motor.Shape),'washer_corner':cc(c.Shape,washers[i].Shape),'washer_rail':cc(washers[i].Shape,rr),'hex_volume':sum(vol(c.Shape,s.Shape) for s in studs),'hex_min_distance':min(c.Shape.distToShape(s.Shape)[0] for s in studs)})
|
||||
fanhits=[]
|
||||
for o in d.Objects:
|
||||
if o==f or not hasattr(o,'Shape') or o.Shape.isNull() or not o.ViewObject.Visibility or o.Name.startswith('FanBolt'):continue
|
||||
v=vol(fan,o.Shape)
|
||||
if v>1e-4:fanhits.append([o.Name,v])
|
||||
# hide guessed fan when conflicts (never imply it has been fitted)
|
||||
if fanhits:
|
||||
f.ViewObject.Visibility=False
|
||||
for i in (0,1):d.getObject('FanBolt%d'%i).ViewObject.Visibility=False
|
||||
|
||||
# Replace every visible old bracket, including the two types of long brackets.
|
||||
# New L shape made with round distal ends. Its holes are both Ø5 mm. We model
|
||||
# only sharp-bend nominal geometry; countersink and tool access are unknown.
|
||||
def l_yz(x, yc, zface, upwards_z, foot_direction):
|
||||
# width X=16; foot contacts y=yc, upright contacts z=zface.
|
||||
t=1.8
|
||||
zz=zface if upwards_z>0 else zface-t
|
||||
side=box(x-8,yc,zz,16,12,t).fuse(cyl(8,t,x,yc+12,zz).common(box(x-8,yc+12,zz,16,8,t)))
|
||||
side=side.cut(cyl(2.5,t+.1,x,yc+12,zz-.05))
|
||||
if foot_direction>0:
|
||||
foot=box(x-8,yc,zface,16,t,12).fuse(cyl(8,t,x,yc,zface+12,V(0,1,0)).common(box(x-8,yc,zface+12,16,t,8)))
|
||||
foot=foot.cut(cyl(2.5,t+.1,x,yc-.05,zface+12,V(0,1,0)))
|
||||
else:
|
||||
foot=box(x-8,yc,zface-12,16,t,12).fuse(cyl(8,t,x,yc,zface-12,V(0,1,0)).common(box(x-8,yc,zface-20,16,t,8)))
|
||||
foot=foot.cut(cyl(2.5,t+.1,x,yc-.05,zface-12,V(0,1,0)))
|
||||
return side.fuse(foot).removeSplitter()
|
||||
# Same purchased bracket at connections between each motor 2020 and 3030.
|
||||
for i,sgn in enumerate((-1,1)):
|
||||
old=d.getObject('MotorBaseCorner%d'%i); old.ViewObject.Visibility=False
|
||||
add(d,'NewMotorBase%d'%i,l_yz(sgn*xc,-55,0,-1,-1),'20x20x16x1.8 NEW M4 concept',(.12,.42,.15))
|
||||
# Switch cross-beam connections. Both legs assumed the same end geometry.
|
||||
for i,sgn in enumerate((-1,1)):
|
||||
old=d.getObject('SwitchCrossJoint%d'%i);old.ViewObject.Visibility=False
|
||||
add(d,'NewSwitchBase%d'%i,l_yz(sgn*xc,-55,-122,1,1),'20x20x16x1.8 NEW M4 concept',(.12,.42,.15))
|
||||
# New small brackets on the four 70 mm cross-beam to 3030 joints. Slot and
|
||||
# screw centre alignment checked separately: crossbeam and rail centre Y
|
||||
# differ by 2 mm, larger than the available nominal M4/Ø5 clearance.
|
||||
def l_xz(side, zface, y):
|
||||
t=1.8; inner=side*(xc-15)
|
||||
if side>0:
|
||||
x0=inner-t; direction=-1
|
||||
else:
|
||||
x0=inner; direction=1
|
||||
if direction>0:
|
||||
face=box(inner,y-8,zface,12,16,t).fuse(cyl(8,t,inner+12,y,zface).common(box(inner+12,y-8,zface,8,16,t)))
|
||||
face=face.cut(cyl(2.5,t+.1,inner+12,y,zface-.05))
|
||||
else:
|
||||
face=box(inner-12,y-8,zface,12,16,t).fuse(cyl(8,t,inner-12,y,zface).common(box(inner-20,y-8,zface,8,16,t)))
|
||||
face=face.cut(cyl(2.5,t+.1,inner-12,y,zface-.05))
|
||||
# Plane on inside of rail; legs extend from zface toward the 3030 slot.
|
||||
vert=box(x0,y-8,zface, t,16,12).fuse(cyl(8,t,x0,y,zface+12,V(1,0,0)).common(box(x0,y-8,zface+12,t,16,8)))
|
||||
vert=vert.cut(cyl(2.5,t+.1,x0-.05,y,zface+12,V(1,0,0)))
|
||||
return face.fuse(vert).removeSplitter()
|
||||
for i in range(2):
|
||||
for j,side in enumerate((-1,1)):
|
||||
d.getObject('BaseJoint%d%d'%(i,j)).ViewObject.Visibility=False
|
||||
zface=-84 if i==0 else -179
|
||||
# For the rear bearing make the angle face outward, mirror about zface.
|
||||
sh=l_xz(side,zface,-70)
|
||||
if i==1: sh=sh.mirror(V(0,0,zface),V(0,0,1))
|
||||
add(d,'NewBeamBase%d%d'%(i,j),sh,'20x20x16x1.8: nominal screw-axis alignment',(.12,.42,.15))
|
||||
# Four bearing and two switch long-slot brackets intentionally remain, per
|
||||
# user clarification. Beam top and long-angle feet rise 2mm to align beam and
|
||||
# longitudinal 3030 groove centre lines; plate, shaft, and motor stay fixed.
|
||||
new_joint_checks=[]
|
||||
for i in range(2):
|
||||
for j in range(2):
|
||||
n='NewBeamBase%d%d'%(i,j);a=d.getObject(n)
|
||||
new_joint_checks.append({'name':n,'cross':cc(a.Shape,d.getObject('Crossbeam%d'%i).Shape),'base':cc(a.Shape,d.getObject('NewRail%d'%j).Shape)})
|
||||
d.recompute();d.saveAs(str(out/'Assembly_对称角码装配_概念.FCStd'))
|
||||
selected=[o for o in d.Objects if hasattr(o,'Shape') and not o.Shape.isNull() and o.ViewObject.Visibility]
|
||||
Import.export(selected,str(out/'Assembly_对称角码装配_概念.step'))
|
||||
view=G.activeDocument().activeView();G.activateWorkbench('PartWorkbench');view.viewAxonometric();G.updateGui();view.fitAll();time.sleep(.5);G.updateGui();view.saveImage(str(out/'对称角码_长角码保留_3D.png'),1800,1300,'White')
|
||||
vis={o.Name:o.ViewObject.Visibility for o in d.Objects if hasattr(o,'Shape')}; show={'Motor','Holder','Magnet','PCB','Chip','EncoderPlateConcept','Rail0','Rail1','NewRail0','NewRail1','MotorBaseCorner0','MotorBaseCorner1'}|{o.Name for o in corners+washers+studs+screws}|{'DirectFan','FanBolt0','FanBolt1'}
|
||||
for o in d.Objects:
|
||||
if hasattr(o,'Shape'):o.ViewObject.Visibility=(o.Name in show and (not fanhits or not o.Name.startswith(('DirectFan','FanBolt'))))
|
||||
view.viewTop();G.updateGui();view.fitAll();time.sleep(.5);G.updateGui();view.saveImage(str(out/'电机端_正视.png'),1800,1300,'White')
|
||||
view.viewAxonometric();G.updateGui();view.fitAll();time.sleep(.5);G.updateGui();view.saveImage(str(out/'角码与垫片_3D.png'),1800,1300,'White')
|
||||
for o in d.Objects:
|
||||
if hasattr(o,'Shape'):o.ViewObject.Visibility=vis.get(o.Name,False)
|
||||
d.recompute();d.saveAs(str(out/'Assembly_对称角码装配_概念.FCStd'));A.closeDocument(d.Name)
|
||||
d=A.openDocument(str(out/'Assembly_对称角码装配_概念.FCStd'));d.recompute(); assert d.getObject('Rail0').Shape.isValid(); A.closeDocument(d.Name)
|
||||
step=Part.read(str(out/'Assembly_对称角码装配_概念.step')); assert step.isValid()
|
||||
info={'all_user_lengths_mm':{'3030_long':250,'3030_cross':70,'2020_motor':140,'2020_switch':130},'hole_centres_76':p,'corner_wall_outside_x':xwall,'rail_inside_x':xrail,'washer_thickness':2,'base_joint_checks':new_joint_checks,'rail_centre_x':xc,'motor_track_centres_mm':2*xc,'3030_inside_gap_mm':2*(xc-15),'70_cross_member_length_mm':70,'crossbar_intersections_mm3':conflicts,'angle_rotation_degrees':theta,'corner_checks':checks,'fan_hits':fanhits,'note':'All 12 small angles replaced with nominal 20x20x16x1.8; 6 long-slot angles retained; real T slots, screws and fan hubs unmodelled.'}
|
||||
(out/'验证.json').write_text(json.dumps(info,ensure_ascii=False,indent=2),encoding='utf8');(out/'完成.txt').write_text('ok',encoding='utf8');os._exit(0)
|
||||
except Exception:
|
||||
(out/'错误.txt').write_text(traceback.format_exc(),encoding='utf8');os._exit(1)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user