建立方向盘项目初始版本

This commit is contained in:
2026-10-29 22:04:55 +08:00
commit 9962cbfbce
834 changed files with 1519203 additions and 0 deletions

View File

@@ -0,0 +1,103 @@
from pathlib import Path
import os, math, json, time, traceback
import FreeCAD as A, FreeCADGui as G, Part, Import
V = A.Vector
root = Path(r'C:\projects\wheel')
out = root / 'models' / 'fan_direct_145'
out.mkdir(parents=True, exist_ok=True)
try:
d = A.openDocument(str(root / 'models' / 'integer_profiles' / 'Assembly.FCStd'))
d.recompute()
rails = [d.getObject('Rail0'), d.getObject('Rail1')]
xs = [(o.Shape.BoundBox.XMin + o.Shape.BoundBox.XMax) / 2 for o in rails]
spacing = xs[1] - xs[0]
theta = math.degrees(math.acos(spacing / (105.0 * math.sqrt(2.0)))) - 45.0
corners_before = [d.getObject('MotorBaseCorner%d' % i).Shape.copy() for i in range(2)]
old_rails = [o.Shape.copy() for o in rails]
for i, o in enumerate(rails):
o.Placement = A.Placement()
o.Shape = Part.makeBox(20, 145, 20, V(xs[i] - 10, -55, 0))
o.Label = '2020 motor profile - 145 mm'
d.recompute()
src = Part.makeBox(120, 120, 25, V(-60, -60, 20)).cut(Part.makeCylinder(56, 25.04, V(0,0,19.98)))
for x in (-52.5, 52.5):
for y in (-52.5, 52.5):
src = src.cut(Part.makeCylinder(2.2, 25.04, V(x,y,19.98)))
fan = src.removeSplitter()
fan.rotate(V(0,0,0), V(0,0,1), theta)
fan.translate(V(0,28,0))
assert fan.isValid() and len(fan.Solids) == 1
original = [o for o in d.Objects if hasattr(o,'Shape') and not o.Shape.isNull() and o.ViewObject.Visibility]
fan_hits = []
for o in original:
if fan.BoundBox.intersect(o.Shape.BoundBox):
volume = fan.common(o.Shape).Volume
if volume > 1e-4: fan_hits.append([o.Name, volume])
assert not fan_hits, 'fan collisions: ' + str(fan_hits)
extra_hits = []
for i, rail in enumerate(rails):
extra = rail.Shape.cut(old_rails[i])
for o in original:
if o == rail or o in rails: continue
if extra.BoundBox.intersect(o.Shape.BoundBox):
volume = extra.common(o.Shape).Volume
if volume > 1e-4: extra_hits.append([rail.Name, o.Name, volume])
assert old_rails[i].cut(rail.Shape).Volume < 1e-5
assert not extra_hits, 'rail extension collisions: ' + str(extra_hits)
for i in range(2):
assert corners_before[i].cut(d.getObject('MotorBaseCorner%d' % i).Shape).Volume < 1e-7
assert d.getObject('MotorBaseCorner%d' % i).Shape.cut(corners_before[i]).Volume < 1e-7
o = d.addObject('PartDesign::Feature', 'DirectFan')
o.Label = '120 mm fan envelope - rotated, two diagonal fixings; user fan assumed'
o.Shape = fan
o.ViewObject.ShapeColor = (.22,.24,.27)
o.ViewObject.Transparency = 18
alpha = math.radians(theta)
fasteners=[]
holes=[]
for sign in (-1,1):
px = sign * 52.5 * (math.cos(alpha) - math.sin(alpha))
py = 28 + sign * 52.5 * (math.cos(alpha) + math.sin(alpha))
idx=0 if sign<0 else 1
assert abs(px-xs[idx]) < 1e-6
assert -55 < py < 90
shaft=Part.makeCylinder(2,25,V(px,py,20))
head=Part.makeCylinder(3.5,3,V(px,py,45))
screw=shaft.fuse(head).removeSplitter()
assert screw.common(fan).Volume < 1e-5
f=d.addObject('PartDesign::Feature','FanBolt'+str(idx))
f.Shape=screw; f.Label='Illustrative M4 fastener, nut not modeled'; f.ViewObject.ShapeColor=(.65,.66,.68)
fasteners.append(f)
holes.append({'x':px,'y':py,'rail_center_x':xs[idx], 'distance_to_end_mm': min(py+55,90-py)})
d.recompute()
for obj in [o]+fasteners:
assert not obj.Shape.isNull() and obj.Shape.isValid()
d.Label='120 mm fan on 145 mm profiles - 2 diagonal screws, no bracket'
d.saveAs(str(out/'Assembly.FCStd'))
visible=[a for a in d.Objects if hasattr(a,'Shape') and not a.Shape.isNull() and a.ViewObject.Visibility]
Import.export(visible, str(out/'Assembly.step'))
view=G.activeDocument().activeView(); view.viewAxonometric(); G.updateGui();view.fitAll();time.sleep(.5);G.updateGui()
view.saveImage(str(out/'assembly.png'),1800,1300,'White')
vis={a.Name:a.ViewObject.Visibility for a in d.Objects if hasattr(a,'Shape')}
names={'Rail0','Rail1','NewRail0','NewRail1','MotorBaseCorner0','MotorBaseCorner1','DirectFan','FanBolt0','FanBolt1','Motor','OpenPillarBracket','Holder','Magnet','PCB','Chip'}
for a in d.Objects:
if hasattr(a,'Shape'):a.ViewObject.Visibility=a.Name in names
view.viewTop();G.updateGui();view.fitAll();time.sleep(.5);G.updateGui();view.saveImage(str(out/'fan_top.png'),1800,1300,'White')
view.viewAxonometric();G.updateGui();view.fitAll();time.sleep(.5);G.updateGui();view.saveImage(str(out/'fan_detail.png'),1800,1300,'White')
for a in d.Objects:
if hasattr(a,'Shape'):a.ViewObject.Visibility=vis.get(a.Name,False)
d.recompute();d.saveAs(str(out/'Assembly.FCStd'))
expected=[a.Name for a in visible]
A.closeDocument(d.Name)
d2=A.openDocument(str(out/'Assembly.FCStd'));d2.recompute()
assert all(d2.getObject(x) and d2.getObject(x).Shape.isValid() for x in ['Rail0','Rail1','DirectFan','FanBolt0','FanBolt1'])
assert all(abs(d2.getObject('Rail%d'%i).Shape.BoundBox.YLength-145)<1e-7 for i in (0,1))
st=Part.read(str(out/'Assembly.step'));assert st.isValid() and len(st.Solids)>0
data={'motor_2020_length_mm':145,'2020_longitudinal_limits_mm':[-55,90], 'fan_envelope_mm':[120,120,25], 'fan_mount_pitch_mm':105,'fan_center_y_mm':28,'motor_center_y_mm':10,'fan_offset_from_motor_mm':18,'rotation_deg':theta,'fastener_centers':holes,'fan_collisions':fan_hits,'extension_collisions':extra_hits,'original_corners_unchanged':True,'fcstd_reopened':True,'step_valid':True,'assumptions':'Fan solid frame and holes schematic; no blade, hub, wires, real T-slot or nut. Shaft of screw only above extrusion surface.'}
(out/'validation.json').write_text(json.dumps(data,ensure_ascii=False,indent=2),encoding='utf8')
A.closeDocument(d2.Name)
(out/'DONE.txt').write_text('ok',encoding='utf8')
os._exit(0)
except Exception:
(out/'ERROR.txt').write_text(traceback.format_exc(),encoding='utf8')
os._exit(1)