User, 18 Oct 2026: use real 40x40 profiles, make a render with text pointing out the parts to show the investor; the ring lights looked like they were floating. - build_station.py: frame drawn from the real 8-slot 40 series section (profile_shape: slots, centre bore, corner cavities; 40x80 with centre cavity) and 24 cast angle brackets; top beams now butt between the uprights; label-camera posts moved beside the cameras so both ring lights clamp straight to them; top line-scan on a side post with visible camera plate and light hangers; slide strips on carrier angles, pulley axles, end-stop posts, presence-sensor brackets, backlight post, cabinet on base crosses, chute on two supports and clear of the rail, pneumatics rail, stack light on the back upright, touch screen on the front end upright. A connectivity check at the end lists any part that does not reach the floor through touching parts: none. - render_annotated.py (FreeCAD): front three-quarter view, camera backed off so nothing is clipped, no navigation cube or preselection, label positions projected from the camera and checked with marker renders. - annotate_render.py (Pillow): title, 16 labels in two columns, leaders assigned so none cross -> inline-qc-station-annotated.png. - render_station.py: same camera and preselection fixes; all six views re-rendered. - BOM: 32 m of 40x40 in 45 pieces; total still USD 16k to 33k. - inline-optical-qc.md and README updated; .gitignore for the render intermediates.
153 lines
5.6 KiB
Python
153 lines
5.6 KiB
Python
# Render the inline QC station views (run in the FreeCAD GUI after build_station.py).
|
|
# exec(open(r"C:/dev/cnx_vinyl/docs/equipment/inline-qc-station/render_station.py", encoding="utf-8").read())
|
|
# Detail views are rendered from a temporary document holding the model clipped
|
|
# to a box (Part common), because coin clip planes did not reach the render here.
|
|
import os
|
|
import FreeCAD as App
|
|
import FreeCADGui as Gui
|
|
import Part
|
|
|
|
OUTDIR = r"C:/dev/cnx_vinyl/docs/equipment/inline-qc-station"
|
|
doc = App.getDocument("InlineQCStation")
|
|
|
|
_vp = App.ParamGet("User parameter:BaseApp/Preferences/View")
|
|
_navi_before = _vp.GetBool("ShowNaviCube", True)
|
|
_vp.SetBool("ShowNaviCube", False)
|
|
_anim_before = _vp.GetBool("UseNavigationAnimations", True)
|
|
_vp.SetBool("UseNavigationAnimations", False)
|
|
_presel_before = _vp.GetBool("EnablePreselection", True)
|
|
_vp.SetBool("EnablePreselection", False)
|
|
Gui.Selection.clearPreselection()
|
|
Gui.Selection.clearSelection()
|
|
from pivy import coin
|
|
|
|
|
|
def prep(d):
|
|
Gui.setActiveDocument(d.Name)
|
|
v = Gui.getDocument(d.Name).ActiveView
|
|
try:
|
|
v.setCameraType("Orthographic")
|
|
except Exception:
|
|
pass
|
|
for m in ("setNavigationCubeVisible", "setCornerCrossVisible"):
|
|
if hasattr(v, m):
|
|
getattr(v, m)(False)
|
|
return v
|
|
|
|
|
|
def members(group):
|
|
g = doc.getObject(group)
|
|
return list(g.Group) if g else []
|
|
|
|
|
|
def show_all():
|
|
for o in doc.Objects:
|
|
if o.TypeId == "Part::Feature":
|
|
o.ViewObject.Visibility = True
|
|
|
|
|
|
def hide(groups=(), names=()):
|
|
for gname in groups:
|
|
for o in members(gname):
|
|
o.ViewObject.Visibility = False
|
|
for n in names:
|
|
o = doc.getObject(n)
|
|
if o:
|
|
o.ViewObject.Visibility = False
|
|
|
|
|
|
def shoot(v, fname, orient, w=3200, h=2400):
|
|
getattr(v, orient)()
|
|
Gui.updateGui()
|
|
v.fitAll()
|
|
Gui.updateGui()
|
|
# move the orthographic camera well back so no part sits behind the near plane
|
|
c = v.getCameraNode()
|
|
d = c.orientation.getValue().multVec(coin.SbVec3f(0, 0, -1))
|
|
c.position.setValue(c.position.getValue() - d * 8000)
|
|
c.nearDistance.setValue(100)
|
|
c.farDistance.setValue(32000)
|
|
Gui.updateGui()
|
|
path = os.path.join(OUTDIR, fname)
|
|
v.saveImage(path, w, h, "White")
|
|
print("saved", path)
|
|
|
|
|
|
def clipped_doc(name, clipbox, skip_groups=()):
|
|
skip = set()
|
|
for gname in skip_groups:
|
|
skip.update(o.Name for o in members(gname))
|
|
d = App.newDocument(name)
|
|
for o in doc.Objects:
|
|
if o.TypeId != "Part::Feature" or o.Name in skip:
|
|
continue
|
|
s = o.Shape.common(clipbox)
|
|
if s.isNull() or s.Volume < 1e-3:
|
|
continue
|
|
n = d.addObject("Part::Feature", o.Name)
|
|
n.Shape = s
|
|
n.ViewObject.ShapeColor = o.ViewObject.ShapeColor
|
|
n.ViewObject.Transparency = o.ViewObject.Transparency
|
|
n.ViewObject.LineColor = o.ViewObject.LineColor
|
|
d.recompute()
|
|
return d
|
|
|
|
|
|
view = prep(doc)
|
|
enclosure_tops = ["PanelTop", "PanelFrontUpper", "PanelFrontLower", "GuardRejectFront", "GuardRejectTop", "DoorHandle"]
|
|
|
|
# 1 open iso: front panels and lid off, the rest of the enclosure translucent
|
|
show_all()
|
|
hide(names=enclosure_tops)
|
|
shoot(view, "inline-qc-station-iso.png", "viewIsometric")
|
|
|
|
# 2 closed iso: as it stands in the hall
|
|
show_all()
|
|
for o in members("Enclosure"):
|
|
o.ViewObject.Transparency = 15 if o.Name not in ("GuardRejectFront", "GuardRejectTop") else 70
|
|
shoot(view, "inline-qc-station-closed.png", "viewIsometric")
|
|
for o in members("Enclosure"):
|
|
o.ViewObject.Transparency = 55 if o.Name not in ("GuardRejectFront", "GuardRejectTop") else 75
|
|
|
|
# 3 front elevation without the front panels
|
|
show_all()
|
|
hide(names=enclosure_tops)
|
|
shoot(view, "inline-qc-station-front.png", "viewFront")
|
|
|
|
# 4 plan view without the lid and the top bridge
|
|
show_all()
|
|
hide(names=enclosure_tops + ["TopBeamF", "TopBeamB", "TopCrossLabel", "TopCrossLineScan", "TopCrossThermal",
|
|
"TopCrossBacklight", "RejectBridgeCross", "RodlessCylinder", "PusherCarriage",
|
|
"LabelCamTop", "LabelCamTopPost", "LabelCamTopPlate", "LineCamTop", "LineCamTopPost",
|
|
"LineCamTopPlate", "LightHangerTop", "CoaxHangerTop", "CoaxLightTop", "CoaxLightTopFace",
|
|
"BacklightForBottomCam", "BacklightForBottomCamFace", "BacklightBracketTop",
|
|
"LabelRingTop", "LabelRingTopFace", "ThermalCam", "ThermalPost",
|
|
"DarkFieldTopF", "DarkFieldTopB", "DarkFieldTopBracketF", "DarkFieldTopBracketB",
|
|
"StackLight0", "StackLight1", "StackLight2", "StackLightPole", "HMI", "HMIArm"])
|
|
shoot(view, "inline-qc-station-top.png", "viewTop")
|
|
show_all()
|
|
hide(names=enclosure_tops)
|
|
view.viewIsometric()
|
|
view.fitAll()
|
|
doc.save()
|
|
|
|
# 5 inspection elevation: x 600..1120, everything in front of y = -150 cut away
|
|
d5 = clipped_doc("QCDetailFront", Part.makeBox(520, 500, 1100, App.Vector(600, -150, 400)),
|
|
skip_groups=("Enclosure", "Electrical", "Reject"))
|
|
v5 = prep(d5)
|
|
shoot(v5, "inline-qc-station-detail-front.png", "viewFront", w=2400, h=2400)
|
|
App.closeDocument(d5.Name)
|
|
|
|
# 6 inspection zone, 3/4 view: x 600..1120, z 400..1500
|
|
d6 = clipped_doc("QCDetailIso", Part.makeBox(520, 700, 1100, App.Vector(600, -350, 400)),
|
|
skip_groups=("Enclosure", "Electrical", "Reject"))
|
|
v6 = prep(d6)
|
|
shoot(v6, "inline-qc-station-detail.png", "viewIsometric", w=2400, h=2400)
|
|
App.closeDocument(d6.Name)
|
|
|
|
Gui.setActiveDocument(doc.Name)
|
|
_vp.SetBool("ShowNaviCube", _navi_before)
|
|
_vp.SetBool("UseNavigationAnimations", _anim_before)
|
|
_vp.SetBool("EnablePreselection", _presel_before)
|
|
print("done")
|