User, 18 Oct 2026: create a setup for the inline optical QC, an aluminium extrusion frame with all sensors and a conveyor mounted to it. - docs/equipment/inline-qc-station/: FreeCAD script (build_station.py) and model (.FCStd) of one station, 1.9 x 0.65 x 1.5 m on 27 m of 40x40 and 3.8 m of 40x80 profile: rim-belt conveyor (two 6 mm PU round belts under the unrecorded rim, open centre), label gate with top and bottom colour cameras on the fly, inspection position with a lift and rotate chuck (75 mm lift, expanding mandrel, belt-driven servo), top and bottom 4k line-scans on opposite radii with coaxial, dark-field and backlights, two warp lasers, optional thermal camera, light-tight black tunnel with door switch, rodless-cylinder reject pusher with chute and bin, outfeed stop, cabinet, PLC, NixOS edge PC, HMI, stack light. - render_station.py renders six views (open and closed iso, front, plan, inspection detail and elevation via clipped temporary documents). - inline-qc-station-bom.csv: 32 lines with sources; USD 16k to 33k per station without the thermal camera. - inline-optical-qc.md: new section "The station setup" (flow table, inspection zone, cycle ~5 to 6 s, control, BOM by group, open points); cost table updated (two launch stations ~USD 50k to 105k, still inside the metrology launch tier); Duplex/Classic flow now via the conveyor. - README bullet and tree.
136 lines
4.9 KiB
Python
136 lines
4.9 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)
|
|
|
|
|
|
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)()
|
|
v.fitAll()
|
|
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)
|
|
print("done")
|