diff --git a/README.md b/README.md index 5256b40..74a5882 100644 --- a/README.md +++ b/README.md @@ -184,7 +184,9 @@ The detail lives in dedicated documents: the 100% record inspection station (line-scan cameras with dark-field and transmitted light, label camera, laser warp sensors, centring turntable), CD-scanner and PRP precedents, smart-camera versus open - NixOS software, specials, prices and a two-station cost estimate + NixOS software, specials, prices and a two-station cost estimate, and + a FreeCAD concept model of the station (aluminium profile frame, + rim-belt conveyor, chuck, sensors) with a sourced bill of materials - [Quality automation](docs/quality-automation.md): stamper metrology (Keyence-class confocal, profiler, XRF), layered pressed-record QC (100% optical inspection, per-cycle press telemetry, sampled automated audio @@ -345,6 +347,8 @@ docs/ │ ├── duplex-cell-layout-sketch.png Duplex cell arrangement (sketch, not to scale) │ ├── galvanics.md Electroforming and stamper prep equipment plan │ ├── inline-optical-qc.md Devices for the record inspection station +│ ├── inline-qc-station/ Concept model of the station (FreeCAD script, +│ │ .FCStd, renders, bill of materials) │ └── pressing-line.md Press fleet strategy and equipment plan ├── finance/ Project cost overviews, revenue model, │ construction RFQ, PEA electricity tariff diff --git a/docs/equipment/inline-optical-qc.md b/docs/equipment/inline-optical-qc.md index 92b00fd..8352457 100644 --- a/docs/equipment/inline-optical-qc.md +++ b/docs/equipment/inline-optical-qc.md @@ -19,8 +19,11 @@ parts: - a servo turntable with a centring chuck; - an edge PC running open-source vision software on NixOS. -The hardware costs about USD 12k to 30k per station (estimate). The launch -plant needs two stations, one for the Duplex cell and one for the Classic. +The hardware costs about USD 16k to 33k per station (bill of materials, +partly estimates). The launch plant needs two stations, one for the Duplex +cell and one for the Classic. A concept model of the station, an aluminium +profile frame with a rim-belt conveyor and all sensors mounted to it, is +in [the station setup](#the-station-setup-frame-conveyor-and-sensors). ## What the station has to catch @@ -133,22 +136,160 @@ each cell needs no conveyor and catches a non-fill streak within a cycle or two. The calculator's iso view draws a conveyor to one optical station; that drawing is schematic. +## The station setup: frame, conveyor and sensors + +A concept model of one station, drawn in FreeCAD from the script +[`build_station.py`](inline-qc-station/build_station.py) (views from +[`render_station.py`](inline-qc-station/render_station.py); model file +[`inline-qc-station.FCStd`](inline-qc-station/inline-qc-station.FCStd)). +It is a layout to take to a machine builder, not a fabrication drawing: +the profiles are drawn as plain 40 mm bars and the bought parts as their +outline boxes. + +![Inline QC station, open: front panels and lid removed](inline-qc-station/inline-qc-station-iso.png) + +*Records run from the infeed (back left, a translucent red special on +the belts) through the light-tight tunnel to the reject pusher and the +outfeed stop (front right). The cabinet sits under the outfeed, the +reject bin behind it.* + +**Frame.** The frame is built from 40 × 40 mm 8-slot aluminium profile +(27 m in 37 pieces) on two 40 × 80 mm base beams, with eight levelling +feet. It is 1.9 m long, 0.65 m wide and 1.5 m high, plus the stack +light. The reject bin adds 0.4 m behind it. Records travel at 900 mm, +a normal working height. + +The middle 0.8 m is a tunnel closed with black panels on all sides +(4.3 m²), because the line-scans need their own light only. The front +panel is a door with a safety switch. The records enter and leave +through low slots in the end walls. The reject zone has a clear polycarbonate guard. +All camera and light brackets clamp into the slots, so heights and angles +can be moved during the lighting trials. + +**Conveyor.** Two 6 mm PU round belts run under the record's rim, 148.5 mm +from the centre line. That is outside the recorded area: a 12" record is +301.6 mm across and its grooves end at 292.6 mm at most (IEC 98, as +[tabulated here](http://www7a.biglobe.ne.jp/~yosh/standards_table.htm)). +The belt therefore touches only the raised edge that records are made to +stand on, never a groove. The centre stays open, so cameras and the chuck +can reach the record from below. + +The belts run on UHMW slide strips and are driven by one shaft at the +outfeed end. Side guides with a flared mouth centre the record at the +infeed. Supported on two rim lines, a warm 180 g record sags about 1 mm +in the middle (beam estimate); the trials should confirm that this does +no harm. For 10" and 7" records the belt spacing moves in on a slide; +the model shows the 12" setting. + +| Station | Position from the infeed end | What happens | +|---|---|---| +| Infeed | 200 mm | the operator (Duplex) or the unloader (Classic) puts the record down; a presence sensor starts the cycle | +| Label gate | 520 mm | both colour cameras flash the labels on the fly through the open centre (exposure about 20 µs, under 10 µm blur at 0.5 m/s); the top camera also reads the dead-wax code that ties the record to its stamper, and so to its press and cycle (readability to test, as in [digital factory](../digital-factory.md)) | +| Inspection | 880 mm | a through-beam on the leading edge stops the belts; the chuck lifts the record 75 mm off them (the pad waits below the belt plane), expands the mandrel in the 7.24 mm hole and turns it once in about 1 s | +| Reject | 1,320 mm | a pneumatic rodless cylinder on a bridge pushes a reject sideways by its edge onto a felt-lined chute at the back and into the bin | +| Outfeed | 1,715 mm | good records wait at a soft end stop; the station holds the next one while the outfeed is full | + +![Inspection zone: chuck, line-scan cameras, lights and lasers](inline-qc-station/inline-qc-station-detail.png) + +![Inspection zone in elevation, the front part cut away](inline-qc-station/inline-qc-station-detail-front.png) + +**Inspection zone.** The record is shown lifted on the chuck. The two +line-scans look at opposite radii along the conveyor axis, because no +belt crosses that line: + +- **Top camera:** reads the downstream radius, from 50 to 155 mm, from + 330 mm above. A coaxial line light sits under it, two dark-field line + lights sit at 20° either side, and a diffuse backlight below the + record gives transmitted light for clear colours. +- **Bottom camera:** reads the upstream radius from below with the same + set of lights. +- **Bottom dark-field lights:** these sit outside the record's footprint, + above the rails, so the conveyed record passes them. They light the + underside at about 8°, true dark-field. +- **Warp lasers:** two lasers under the record trace circles at 90 and + 130 mm radius during the turn. +- **Spindle drive:** the spindle servo's encoder clocks both line-scans + and the lasers. +- **Lift drive:** the lift is a guided pneumatic cylinder, and the + spindle is belt-driven from a servo set to the side. That keeps the + bottom camera's view cone clear, which the elevation shows. +- **Thermal camera (option):** hangs at the tunnel entry. + +**Cycle per record (estimate).** + +| Step | Time | +|---|---| +| Convey to the inspection position | 1.5 to 2.5 s | +| Lift and clamp | 0.5 s | +| One turn, with run-up and stop | 1.3 s | +| Lower | 0.3 s | +| Convey out, with a push if rejected | 1 to 2 s | +| **Total** | **about 5 to 6 s** | + +A Duplex cell delivers a record every 15 to 20 s and the Classic one +every 28 s. The PC finishes the verdict while the record is lowered and +carried to the pusher. + +**Control.** A small PLC (Mitsubishi FX5U or Delta class, both common in +Thailand) runs the conveyor, lift, mandrel, spindle and pusher, plus the +safety circuit (e-stop, door switch, safety relay). The NixOS edge PC +under the outfeed takes the four GigE cameras on PoE, the lasers and the +light controllers. It sends pass or fail with a reason to the PLC, and +the result string and images to the factory system (see +[digital factory](../digital-factory.md)). Utilities are 230 V single +phase at about 1 kW and 6 bar compressed air (estimates). + +![Front elevation, front panels removed](inline-qc-station/inline-qc-station-front.png) + +![Plan view, lid and top bridge removed](inline-qc-station/inline-qc-station-top.png) + +![The station closed, as it stands in the hall](inline-qc-station/inline-qc-station-closed.png) + +**Bill of materials.** The line-by-line list with sources is in +[`inline-qc-station-bom.csv`](inline-qc-station/inline-qc-station-bom.csv). +By group, per station: + +| Group | USD | Main parts | +|---|---|---| +| Frame | 0.7k to 1.5k | 27 m of 40 × 40, 3.8 m of 40 × 80, brackets, feet | +| Enclosure and guards | 0.3k to 0.7k | black panels, polycarbonate, door switch | +| Conveyor | 0.5k to 1.4k | round belts, pulleys, slide strips, drive | +| Handling | 2.0k to 4.9k | guided lift cylinder, 200 W spindle servo, spindle head with mandrel, rodless cylinder, pneumatics | +| Vision | 7.7k to 12.5k | 2 line-scans, 2 colour cameras, lenses, 2 coaxial, 4 dark-field and 2 backlights, 2 ring lights, controllers | +| Sensors | 1.3k to 4.0k | 2 laser distance sensors, presence and stop sensors | +| Control | 3.5k to 7.8k | PLC, edge PC with GPU, HMI, safety, cabinet and wiring | +| **Total** | **16k to 33k (0.53M to 1.08M THB)** | thermal camera (USD 2.1k to 3.0k) not included | + +**Open points for the machine builder.** + +1. **Sag:** confirm that the rim-only support does not mark or bend warm + records. +2. **Static:** fresh PVC attracts dust, so an ionising bar at the infeed + may be needed. Otherwise the station counts dust it attracted itself. +3. **Classic hand-off:** where and how the Classic's unloader sets the + record down. +4. **Belt width:** a quick-change spacing for 10" and 7" records. +5. **Space in the cell:** check that the 1.9 m length fits the 5 × 3 m + Duplex cell next to the trimmer. + ## Where it sits - **Duplex cell.** The station sits after the edge trimmer, in front of the operator, before the spindle. The flow per record: - 1. The operator puts the record on the chuck. - 2. A presence sensor starts the scan. - 3. A stack light shows green or red. - 4. A reject goes into a bin, and a small screen shows the reason. + 1. The operator puts the trimmed record on the infeed conveyor. + 2. A presence sensor starts the cycle. + 3. A stack light and the HMI show green or red. + 4. A reject is pushed into a bin, and the HMI shows the reason; a good + record waits at the outfeed for the spindle. The operator still sees every record, so the visual check stays as it is today. - **Classic.** The Classic unloads by itself onto the spindle stack. Ask - Newbilt whether the unload handling can set the record on an inspection - chuck first, or hand it over to a small pick-and-place unit by I/O. If - neither works, use a fixed area camera over the unload position. That - covers surface and label, and leaves warp and eccentricity to samples. + Newbilt whether the unload handling can set the record down on the + station's infeed instead; a conveyor takes a record dropped within a few + millimetres, which is easier than hitting a chuck. If that does not + work, use a fixed area camera over the unload position. That covers + surface and label, and leaves warp and eccentricity to samples. - **Warm records.** Records leave the press warm and settle in the stack; 24 hours of cooling is the usual practice ([Stamper Discs](https://stamperdiscs.com/horns-and-quality-issues-in-vinyl-record-pressing)). @@ -197,11 +338,11 @@ simulated. | Item | Estimate | |---|---| -| Hardware per station, from the table above | USD 12k to 30k | -| First station: mechanics, lighting trials, software, training data | 2 to 4 person-months, USD 15k to 40k (our own team with a local machine builder) | -| Launch plant: two stations, Duplex cell and Classic | about USD 40k to 100k (1.3M to 3.3M THB) | +| Hardware per station, from the [bill of materials](inline-qc-station/inline-qc-station-bom.csv) | USD 16k to 33k (0.53M to 1.08M THB) | +| First station: frame build and machined parts, lighting trials, software, training data | 2 to 4 person-months, USD 15k to 40k (our own team with a local machine builder) | +| Launch plant: two stations, Duplex cell and Classic | about USD 50k to 105k (1.6M to 3.5M THB) | -All three figures are estimates. They fit inside the existing metrology +All three figures are estimates; the bill of materials names a source for each priced line. They fit inside the existing metrology launch-tier line, USD 170k to 460k for XRF, inline vision and the audio station ([total investment](../finance/total-investment.md)). The plan prices therefore do not change. That line has no published split, so the diff --git a/docs/equipment/inline-qc-station/build_station.py b/docs/equipment/inline-qc-station/build_station.py new file mode 100644 index 0000000..f7b42ab --- /dev/null +++ b/docs/equipment/inline-qc-station/build_station.py @@ -0,0 +1,347 @@ +# Build the CNX Vinyl inline optical QC station (concept) in FreeCAD. +# +# Run inside the FreeCAD GUI (26.3): +# exec(open(r"C:/dev/cnx_vinyl/docs/equipment/inline-qc-station/build_station.py", encoding="utf-8").read()) +# It creates the document "InlineQCStation", saves inline-qc-station.FCStd next +# to this script and leaves the view open for rendering (render_station.py). +# +# Concept, not a fabrication drawing: profiles are drawn as plain 40 mm bars, +# bought parts as their envelope boxes. Sizes follow ../inline-optical-qc.md. +# +# Axes: x = record flow (infeed at x = 0, outfeed at x = L), y = across +# (operator front at -y), z = up from the floor. All values in mm. + +import os +import FreeCAD as App +import Part + +try: + import FreeCADGui as Gui + HAVE_GUI = App.GuiUp +except Exception: + HAVE_GUI = False + +HERE = os.path.dirname(os.path.abspath(__file__)) if "__file__" in dir() else r"C:/dev/cnx_vinyl/docs/equipment/inline-qc-station" +OUT = os.path.join(HERE, "inline-qc-station.FCStd") +V = App.Vector + +# ---------------------------------------------------------------- parameters +L = 1900 # frame length along the flow +YF = 300 # centre of the outer frame profiles, +/- y +P = 40 # profile size (40 x 40 slot profile; 40 x 80 for the base) +ZB = 900 # top of the rim belts = record underside while conveyed +R_REC = 150.8 # 12" record radius (IEC 98: 301.6 mm diameter) +T_REC = 2.0 # record thickness (approx.) +R_LABEL = 50.0 # label radius +R_HOLE = 3.62 # centre hole 7.24 mm +R_REC_AREA = 146.3 # recorded outer radius (292.6 mm max): the rim outside it carries no grooves +Y_BELT = 148.5 # round belts run under the unrecorded rim +ZL = 960 # record underside when the chuck has lifted it +LIFT = 75 # chuck stroke: pad top 885 (below the belts) -> 960 + +X_IN = 200 # infeed: operator or Classic unloader puts the record here +X_LB = 520 # label gate: both label cameras, on the fly with strobe +X_IS = 880 # inspection: lift, clamp, one turn under the line-scans +X_RJ = 1320 # reject pusher +X_OUT = L - 30 - R_REC - 4 # outfeed end stop: good records wait here +XT0, XT1 = 320, 1100 # light-tight tunnel uprights (inner faces 360 / 1100) + +TOP_LINE = (X_IS + 50, X_IS + 155) # top line-scan sees the +x radius, r 50..155 +BOT_LINE = (X_IS - 155, X_IS - 50) # bottom line-scan sees the -x radius +WD_LINE = 330 # 4k x 7 um sensor, 160 mm field, f = 50 mm -> ~330 mm working distance +WD_LABEL = 303 # 2840 px x 2.74 um sensor, 140 mm field, f = 16 mm + +# ---------------------------------------------------------------- colours +ALU = (0.78, 0.80, 0.83) +DARK = (0.18, 0.19, 0.21) +BLACK = (0.04, 0.04, 0.05) +CAM = (0.36, 0.52, 0.70) # camera bodies +LENS = (0.08, 0.08, 0.09) +LIGHT = (0.97, 0.95, 0.80) # emitting faces +LIGHTBODY = (0.25, 0.25, 0.27) +BELT = (0.20, 0.62, 0.34) # green PU round belt +UHMW = (0.95, 0.95, 0.93) +PNEU = (0.70, 0.72, 0.75) +SERVO = (0.12, 0.12, 0.14) +RED = (0.85, 0.10, 0.10) +YELLOW = (0.95, 0.80, 0.10) +GREEN = (0.15, 0.70, 0.25) +AMBER = (0.98, 0.60, 0.10) +PANEL = (0.06, 0.06, 0.07) +CLEAR = (0.75, 0.85, 0.95) +CABINET = (0.72, 0.73, 0.70) +LASER = (1.0, 0.05, 0.05) + +for _d in list(App.listDocuments().values()): + if _d.Name.startswith("InlineQCStation") or _d.FileName.replace("\\", "/").endswith("inline-qc-station.FCStd"): + App.closeDocument(_d.Name) +doc = App.newDocument("InlineQCStation") +doc.Label = "CNX inline optical QC station (concept)" +groups = {} + + +def grp(name): + if name not in groups: + g = doc.addObject("App::DocumentObjectGroup", name) + groups[name] = g + return groups[name] + + +def add(group, name, shape, color=ALU, transp=0): + o = doc.addObject("Part::Feature", name) + o.Shape = shape + grp(group).addObject(o) + if HAVE_GUI: + vo = o.ViewObject + vo.ShapeColor = color + vo.Transparency = transp + vo.LineColor = (0.25, 0.25, 0.25) + return o + + +def box(x0, y0, z0, x1, y1, z1): + return Part.makeBox(x1 - x0, y1 - y0, z1 - z0, V(x0, y0, z0)) + + +def cyl(r, h, base, d=V(0, 0, 1)): + return Part.makeCylinder(r, h, base, d) + + +def prof(group, name, x0, y0, z0, x1, y1, z1): + return add(group, name, box(x0, y0, z0, x1, y1, z1), ALU) + + +def rot(shape, center, axis, deg): + s = shape.copy() + s.rotate(center, axis, deg) + return s + + +def ring(r_out, r_in, h, base): + return cyl(r_out, h, base).cut(cyl(r_in, h + 2, base - V(0, 0, 1))) + + +# ---------------------------------------------------------------- frame +G = "Frame" +for i, (x, y) in enumerate([(20, -YF), (20, YF), (XT0 + 20, -YF), (XT0 + 20, YF), + (XT1 + 20, -YF), (XT1 + 20, YF), (L - 20, -YF), (L - 20, YF)]): + add(G, f"Foot{i}", cyl(30, 12, V(x, y, 0)).fuse(cyl(8, 50, V(x, y, 12))), DARK) +for s in (-1, 1): + prof(G, f"BaseBeam{'F' if s < 0 else 'B'}", 0, s * YF - 20, 60, L, s * YF + 20, 140) # 40 x 80 + for x0, z1, nm in [(0, 870, "End0"), (XT0, 1500, "Tunnel0"), (XT1, 1500, "Tunnel1"), + (L - P, 1080, "End1")]: + prof(G, f"Upright{nm}{'F' if s < 0 else 'B'}", x0, s * YF - 20, 140, x0 + P, s * YF + 20, z1) + prof(G, f"TopBeam{'F' if s < 0 else 'B'}", XT0, s * YF - 20, 1460, XT1 + P, s * YF + 20, 1500) + prof(G, f"LowBeam{'F' if s < 0 else 'B'}", XT0 + P, s * YF - 20, 420, XT1, s * YF + 20, 460) + prof(G, f"RejectBeam{'F' if s < 0 else 'B'}", XT1 + P, s * YF - 20, 1040, L - P, s * YF + 20, 1080) +for x0, nm in [(0, "End0"), (XT0, "Tunnel0"), (XT1, "Tunnel1"), (L - P, "End1")]: + prof(G, f"BaseCross{nm}", x0, -YF + 20, 100, x0 + P, YF - 20, 140) + prof(G, f"ConvCross{nm}", x0, -YF + 20, 790, x0 + P, YF - 20, 830) +# top bridge: label camera and top line-scan +prof(G, "TopCrossLabel", X_LB - 20, -YF + 20, 1460, X_LB + 20, YF - 20, 1500) +xt = sum(TOP_LINE) / 2 +prof(G, "TopCrossLineScan", xt - 20, -YF + 20, 1460, xt + 20, YF - 20, 1500) +prof(G, "TopCrossThermal", XT0 + P, -YF + 20, 1460, XT0 + P + 40, YF - 20, 1500) +prof(G, "TopCrossBacklight", sum(BOT_LINE) / 2 - 38, -YF + 20, 1460, sum(BOT_LINE) / 2 + 2, YF - 20, 1500) +# lower bridge: bottom label camera, bottom line-scan, chuck lift +xb = sum(BOT_LINE) / 2 +prof(G, "LowCrossLabel", X_LB - 20, -YF + 20, 420, X_LB + 20, YF - 20, 460) +prof(G, "LowCrossLineScan", xb - 20, -YF + 20, 420, xb + 20, YF - 20, 460) +prof(G, "LowCrossChuck", X_IS - 20, -YF + 20, 420, X_IS + 20, YF - 20, 460) +prof(G, "RejectBridgeCross", X_RJ - 20, -YF + 20, 1040, X_RJ + 20, YF - 20, 1080) + +# ---------------------------------------------------------------- conveyor +G = "Conveyor" +for s in (-1, 1): + side = "F" if s < 0 else "B" + prof(G, f"Rail{side}", 0, s * 180 - 20, 830, L, s * 180 + 20, 870) + add(G, f"BeltSlide{side}", box(30, s * Y_BELT - 5, 880, L - 30, s * Y_BELT + 5, 894), UHMW) + add(G, f"BeltTop{side}", cyl(3, L - 40, V(20, s * Y_BELT, ZB - 3), V(1, 0, 0)), BELT) + add(G, f"BeltReturn{side}", cyl(3, L - 40, V(20, s * Y_BELT, ZB - 49), V(1, 0, 0)), BELT) + for x, nm in [(20, "Idler"), (L - 20, "Drive")]: + add(G, f"Pulley{nm}{side}", cyl(23, 14, V(x, s * Y_BELT - 7, ZB - 26), V(0, 1, 0)), DARK) + # infeed side guides with a flared mouth, PU strip on the rim height + add(G, f"SideGuide{side}", box(110, s * 157 - 3, ZB + 2, XT0 - 10, s * 157 + 3, ZB + 14), UHMW) + add(G, f"SideGuideFlare{side}", rot(box(40, s * 157 - 3, ZB + 2, 112, s * 157 + 3, ZB + 14), + V(110, s * 157, ZB), V(0, 0, 1), s * 25), UHMW) + add(G, f"GuidePost{side}", box(150, min(s * 160, s * 172), 870, 170, max(s * 160, s * 172), ZB + 14), DARK) +add(G, "DriveShaft", cyl(6, 2 * Y_BELT + 60, V(L - 20, -Y_BELT - 30, ZB - 26), V(0, 1, 0)), DARK) +add(G, "DriveGearmotor", cyl(35, 150, V(L - 20, 210, ZB - 26), V(0, 1, 0)) + .fuse(box(L - 60, 200, ZB - 70, L + 15, 230, ZB + 15)), SERVO) +add(G, "EndStopBar", box(L - 30, -200, ZB + 30, L - 12, 200, ZB + 48), DARK) +add(G, "EndStopBumper", box(L - 32, -40, ZB + 4, L - 26, 40, ZB + 30), (0.9, 0.5, 0.1)) + +# presence sensors (diffuse, looking up through the open centre) and the stop beam +G = "Sensors" +for x, nm in [(X_IN, "Infeed"), (X_LB - 120, "LabelTrigger"), (L - 120, "Outfeed")]: + add(G, f"Presence{nm}", box(x - 15, -120, 850, x + 15, -100, 885), (0.15, 0.35, 0.75)) +xs = X_IS + R_REC +for s in (-1, 1): + add(G, f"StopBeam{'Tx' if s < 0 else 'Rx'}", box(xs - 10, min(s * 200, s * 222), 870, xs + 10, max(s * 200, s * 222), ZB + 25), + (0.15, 0.35, 0.75)) +add(G, "StopBeamRay", cyl(0.8, 400, V(xs, -200, ZB + 8), V(0, 1, 0)), LASER, 40) + +# ---------------------------------------------------------------- label gate +G = "LabelGate" +add(G, "LabelCamTop", box(X_LB - 15, -15, ZB + WD_LABEL + 50, X_LB + 15, 15, ZB + WD_LABEL + 92), CAM) +add(G, "LabelLensTop", cyl(17, 50, V(X_LB, 0, ZB + WD_LABEL)), LENS) +add(G, "LabelRingTop", ring(70, 42, 22, V(X_LB, 0, ZB + 170)), LIGHTBODY) +add(G, "LabelRingTopFace", ring(62, 48, 1, V(X_LB, 0, ZB + 169)), LIGHT) +add(G, "LabelCamTopPost", box(X_LB - 20, 40, ZB + WD_LABEL + 50, X_LB + 20, 80, 1460), ALU) +add(G, "LabelCamTopPlate", box(X_LB - 20, 15, ZB + WD_LABEL + 50, X_LB + 20, 40, ZB + WD_LABEL + 92), DARK) +zl = ZB - WD_LABEL +add(G, "LabelCamBottom", box(X_LB - 15, -15, zl - 92, X_LB + 15, 15, zl - 50), CAM) +add(G, "LabelLensBottom", cyl(17, 50, V(X_LB, 0, zl - 50)), LENS) +add(G, "LabelRingBottom", ring(70, 42, 22, V(X_LB, 0, ZB - 80)), LIGHTBODY) +add(G, "LabelRingBottomFace", ring(62, 48, 1, V(X_LB, 0, ZB - 58)), LIGHT) +add(G, "LabelCamBottomPost", box(X_LB - 20, 40, 460, X_LB + 20, 80, zl - 50), ALU) +add(G, "LabelCamBottomPlate", box(X_LB - 20, 15, zl - 92, X_LB + 20, 40, zl - 50), DARK) + +# ---------------------------------------------------------------- inspection: lift, clamp, one turn +G = "Chuck" +zp = ZL # pad top, lifted (shown lifted) +add(G, "ChuckPad", cyl(40, 10, V(X_IS, 0, zp - 10)), (0.85, 0.45, 0.10)) +add(G, "ChuckMandrel", cyl(3.5, 9, V(X_IS, 0, zp)).fuse(Part.makeCone(3.5, 1.2, 4, V(X_IS, 0, zp + 9))), DARK) +add(G, "SpindleHousing", cyl(25, 110, V(X_IS, 0, zp - 120)), PNEU) +add(G, "LiftPlate", box(X_IS - 40, -40, zp - 132, X_IS + 40, 150, zp - 120), DARK) +add(G, "SpindleServo", cyl(30, 110, V(X_IS, 110, zp - 242)), SERVO) +add(G, "SpindleBeltCover", box(X_IS - 32, -32, zp - 120, X_IS + 32, 142, zp - 100), (0.3, 0.3, 0.32)) +add(G, "LiftCylinderRods", cyl(6, 100, V(X_IS - 30, 0, zp - 232)).fuse(cyl(6, 100, V(X_IS + 30, 0, zp - 232))), PNEU) +add(G, "LiftCylinder", box(X_IS - 50, -25, zp - 322, X_IS + 50, 25, zp - 232), PNEU) +add(G, "LiftPost", box(X_IS - 20, -20, 460, X_IS + 20, 20, zp - 322), ALU) + +G = "LineScanTop" +zr = ZL + T_REC # top face of the lifted record +add(G, "LineCamTop", box(xt - 31, -31, zr + WD_LINE + 60, xt + 31, 31, zr + WD_LINE + 105), CAM) +add(G, "LineLensTop", cyl(25, 60, V(xt, 0, zr + WD_LINE)), LENS) +add(G, "LineCamTopPost", box(xt - 20, 40, zr + 150, xt + 20, 80, 1460), ALU) +add(G, "LightHangerTop", box(xt - 10, -112, zr + 130, xt + 10, 112, zr + 150), DARK) +add(G, "CoaxHangerTop", box(xt - 10, 30, zr + 80, xt + 10, 40, zr + 130), DARK) +add(G, "LineCamTopPlate", box(xt - 20, 31, zr + WD_LINE + 60, xt + 20, 40, zr + WD_LINE + 105), DARK) +x0, x1 = TOP_LINE[0] - 5, TOP_LINE[1] + 5 +add(G, "CoaxLightTop", box(x0, -30, zr + 50, x1, 30, zr + 110), LIGHTBODY) +add(G, "CoaxLightTopFace", box(x0 + 3, -26, zr + 49, x1 - 3, 26, zr + 50), LIGHT) +for s in (-1, 1): + b = box(x0, -15, 0, x1, 15, 30) + b.translate(V(0, s * 100, zr + 15)) + add(G, f"DarkFieldTop{'F' if s < 0 else 'B'}", rot(b, V(0, s * 100, zr + 30), V(1, 0, 0), s * 20), LIGHTBODY) + add(G, f"DarkFieldTopBracket{'F' if s < 0 else 'B'}", box(xt - 10, s * 100 - 5, zr + 48, xt + 10, s * 100 + 5, zr + 130), DARK) +bx0, bx1 = BOT_LINE[0] - 5, BOT_LINE[1] + 5 +add(G, "BacklightForBottomCam", box(bx0, -20, zr + 30, bx1, 20, zr + 52), LIGHTBODY) +add(G, "BacklightForBottomCamFace", box(bx0 + 3, -17, zr + 29, bx1 - 3, 17, zr + 30), LIGHT) +add(G, "BacklightBracketTop", box(xb - 30, -5, zr + 52, xb - 10, 5, 1460), DARK) +add(G, "ScanLineTop", box(TOP_LINE[0], -0.6, zr + 0.05, TOP_LINE[1], 0.6, zr + 0.5), LASER) +add(G, "ThermalPost", box(XT0 + 90, -240, 1430, XT0 + 110, -220, 1460), DARK) +add(G, "ThermalCam", rot(box(XT0 + 70, -260, 1380, XT0 + 130, -200, 1440), V(XT0 + 100, -230, 1410), V(1, 0, 0), -30), (0.95, 0.45, 0.10)) + +G = "LineScanBottom" +zu = ZL # underside of the lifted record +add(G, "LineCamBottom", box(xb - 31, -31, zu - WD_LINE - 105, xb + 31, 31, zu - WD_LINE - 60), CAM) +add(G, "LineLensBottom", cyl(25, 60, V(xb, 0, zu - WD_LINE - 60)), LENS) +add(G, "LineCamBottomPost", box(xb - 20, 40, 460, xb + 20, 80, zu - WD_LINE - 60), ALU) +add(G, "LineCamBottomPlate", box(xb - 20, 31, zu - WD_LINE - 105, xb + 20, 40, zu - WD_LINE - 60), DARK) +add(G, "CoaxLightBottom", box(bx0, -30, ZB - 80, bx1, 30, ZB - 20), LIGHTBODY) +add(G, "CoaxLightBottomFace", box(bx0 + 3, -26, ZB - 20, bx1 - 3, 26, ZB - 19), LIGHT) +for s in (-1, 1): + b = box(bx0, -15, 0, bx1, 15, 30) + b.translate(V(0, s * 175, zu - 40)) + add(G, f"DarkFieldBottom{'F' if s < 0 else 'B'}", rot(b, V(0, s * 175, zu - 25), V(1, 0, 0), -s * 8), LIGHTBODY) + add(G, f"DarkFieldBottomBracket{'F' if s < 0 else 'B'}", box(bx0 + 40, s * 192 - 5, 870, bx0 + 60, s * 192 + 5, zu - 35), DARK) +add(G, "BacklightForTopCam", box(x0, -20, ZB - 40, x1, 20, ZB - 15), LIGHTBODY) +add(G, "BacklightForTopCamFace", box(x0 + 3, -17, ZB - 15, x1 - 3, 17, ZB - 14), LIGHT) +add(G, "ScanLineBottom", box(BOT_LINE[0], -0.6, zu - 0.5, BOT_LINE[1], 0.6, zu - 0.05), LASER) + +G = "WarpLasers" +for r in (90, 130): + add(G, f"Laser_r{r}", box(X_IS - 23, -r - 10, ZB - 55, X_IS + 23, -r + 10, ZB - 15), (0.15, 0.35, 0.75)) + add(G, f"LaserBeam_r{r}", cyl(0.8, zu - (ZB - 15), V(X_IS, -r, ZB - 15)), LASER, 30) + add(G, f"LaserBracket_r{r}", box(X_IS - 10, -r - 10, 830, X_IS + 10, -r + 10, ZB - 55), DARK) +add(G, "LaserRail", box(X_IS - 20, -200, 810, X_IS + 20, -70, 830), ALU) + +# ---------------------------------------------------------------- reject +G = "Reject" +add(G, "RodlessCylinder", box(X_RJ - 30, -300, 1000, X_RJ + 30, 300, 1040), PNEU) +yc = -205 # carriage retracted at the front +add(G, "PusherCarriage", box(X_RJ - 35, yc - 30, 960, X_RJ + 35, yc + 30, 1000), DARK) +add(G, "PusherArm", box(X_RJ - 10, yc - 10, ZB + 26, X_RJ + 10, yc + 10, 960), DARK) +add(G, "PusherPaddle", box(X_RJ - 100, yc + 10, ZB + 4, X_RJ + 100, yc + 22, ZB + 26), (0.9, 0.5, 0.1)) +CX0, CX1 = X_RJ - 170, X_RJ + 170 +chute = box(CX0, 0, 0, CX1, 360, 6) +chute = rot(chute, V(0, 0, 0), V(1, 0, 0), -42) +chute.translate(V(0, 165, ZB - 12)) +add(G, "RejectChute", chute, (0.55, 0.57, 0.60)) +for xw in (CX0 - 6, CX1): + w = box(xw, 0, 0, xw + 6, 360, 80) + w = rot(w, V(0, 0, 0), V(1, 0, 0), -42) + w.translate(V(0, 165, ZB - 12)) + add(G, f"ChuteWall{xw}", w, (0.55, 0.57, 0.60)) +binb = box(CX0 - 20, 380, 0, CX1 + 20, 720, 560).cut(box(CX0 - 8, 392, 12, CX1 + 8, 708, 600)) +add(G, "RejectBin", binb, (0.85, 0.20, 0.15), 20) + +# ---------------------------------------------------------------- enclosure (black, light-tight tunnel) +G = "Enclosure" +slot = box(XT0 - 10, -205, 860, XT1 + P + 10, 205, ZB + 30) +for x, nm in [(XT0 - 3, "Infeed"), (XT1 + P, "Outfeed")]: + up = box(x, -YF - 22, 870, x + 3, YF + 22, 1500).cut(slot) + add(G, f"EndWallUpper{nm}", up, PANEL, 55) + lo = box(x, -YF - 22, 140, x + 3, YF + 22, 870).cut(box(x - 1, -205, 815, x + 4, 205, 880)) + add(G, f"EndWallLower{nm}", lo, PANEL, 55) +for s in (-1, 1): + side = "Front" if s < 0 else "Back" + y0 = s * (YF + 20) if s > 0 else -YF - 23 + add(G, f"Panel{side}Upper", box(XT0, y0, 870, XT1 + P, y0 + 3, 1500), PANEL, 55) + add(G, f"Panel{side}Lower", box(XT0, y0, 140, XT1 + P, y0 + 3, 870), PANEL, 55) +add(G, "PanelTop", box(XT0, -YF - 23, 1500, XT1 + P, YF + 23, 1503), PANEL, 55) +add(G, "PanelBottom", box(XT0, -YF - 20, 140, XT1 + P, YF + 20, 143), PANEL, 55) +add(G, "DoorHandle", box(700, -YF - 45, 1150, 860, -YF - 23, 1175), DARK) +add(G, "GuardRejectFront", box(XT1 + P, -YF - 23, 870, L, -YF - 20, 1100), CLEAR, 75) +add(G, "GuardRejectTop", box(XT1 + P, -YF - 23, 1100, L, YF + 23, 1103), CLEAR, 75) + +# ---------------------------------------------------------------- electrical and operator +G = "Electrical" +add(G, "Cabinet", box(1180, -200, 160, 1640, 170, 770), CABINET) +add(G, "CabinetHandle", box(1600, -215, 400, 1615, -200, 520), DARK) +add(G, "StackLightPole", cyl(10, 120, V(XT1 + 20, -YF, 1503)), DARK) +for i, c in enumerate([GREEN, AMBER, RED]): + add(G, f"StackLight{i}", cyl(25, 55, V(XT1 + 20, -YF, 1623 + i * 57)), c) +add(G, "HMIArm", box(XT1 + 50, -YF - 80, 1200, XT1 + 70, -YF - 20, 1220), DARK) +add(G, "HMI", rot(box(XT1 + 70, -YF - 100, 1130, XT1 + 330, -YF - 80, 1300), V(XT1 + 200, -YF - 90, 1215), V(1, 0, 0), -15), (0.10, 0.10, 0.12)) +add(G, "EStopBox", box(XT1 + 60, -YF - 60, 930, XT1 + 140, -YF - 20, 1010), YELLOW) +add(G, "EStop", cyl(22, 25, V(XT1 + 100, -YF - 60, 970), V(0, -1, 0)), RED) +add(G, "ValveTerminal", box(XT0 + 80, YF + 25, 300, XT0 + 330, YF + 75, 380), PNEU) +add(G, "FRL", box(XT0 + 360, YF + 25, 280, XT0 + 440, YF + 85, 420), (0.2, 0.4, 0.8)) + +# ---------------------------------------------------------------- records +G = "Records" + + +def record(name, x, y, z, label_color, body=BLACK, transp=0, tilt=None): + disc = cyl(R_REC, T_REC, V(x, y, z)).cut(cyl(R_HOLE, T_REC + 2, V(x, y, z - 1))) + lab = cyl(R_LABEL, 0.3, V(x, y, z + T_REC)).cut(cyl(R_HOLE, 2, V(x, y, z + T_REC - 0.5))) + labb = cyl(R_LABEL, 0.3, V(x, y, z - 0.3)).cut(cyl(R_HOLE, 2, V(x, y, z - 1))) + if tilt: + c, ax, deg = tilt + disc, lab, labb = rot(disc, c, ax, deg), rot(lab, c, ax, deg), rot(labb, c, ax, deg) + add(G, name, disc, body, transp) + add(G, name + "LabelA", lab, label_color) + add(G, name + "LabelB", labb, label_color) + + +record("RecordInfeed", X_IN, 0, ZB, (0.95, 0.85, 0.75), body=(0.80, 0.12, 0.16), transp=25) # translucent red special +record("RecordInspection", X_IS, 0, ZL, (0.20, 0.45, 0.80)) +record("RecordOutfeed", X_OUT, 0, ZB, (0.95, 0.60, 0.15)) +import math +sc, a = 230, math.radians(42) +yr = 165 + math.cos(a) * sc + 6 * math.sin(a) +zr2 = ZB - 12 - math.sin(a) * sc + 6 * math.cos(a) +record("RecordReject", X_RJ, yr, zr2, (0.6, 0.6, 0.6), tilt=(V(X_RJ, yr, zr2), V(1, 0, 0), -42)) + +doc.recompute() +doc.saveAs(OUT) +n = len([o for o in doc.Objects if o.TypeId == "Part::Feature"]) +bb = None +for o in doc.Objects: + if o.TypeId == "Part::Feature": + bb = o.Shape.BoundBox if bb is None else bb.united(o.Shape.BoundBox) +print("objects", n, "bbox", round(bb.XMin), round(bb.XMax), round(bb.YMin), round(bb.YMax), round(bb.ZMin), round(bb.ZMax)) +print("saved", OUT) diff --git a/docs/equipment/inline-qc-station/inline-qc-station-bom.csv b/docs/equipment/inline-qc-station/inline-qc-station-bom.csv new file mode 100644 index 0000000..38c572b --- /dev/null +++ b/docs/equipment/inline-qc-station/inline-qc-station-bom.csv @@ -0,0 +1,34 @@ +group,item,qty,example,unit_usd_low,unit_usd_high,line_usd_low,line_usd_high,source +Frame,"40x40 slot profile, cut to length",27 m (37 pieces),"8-slot 4040 (Misumi HFS8-4040 class, or Thai/Chinese generic)",11,21,297,567,per m; Shopee Thailand listing from THB 356; aluxprofile EUR 19 to 24 per m +Frame,"40x80 slot profile, base beams",3.8 m (2 pieces),8-slot 4080,22,42,84,160,"per m; estimate, about twice the 4040 price" +Frame,"Angle brackets, T-nuts, screws, end caps",1 lot (about 120 joints),,250,600,250,600,estimate +Frame,Levelling feet M16,8,,8,25,64,200,each; estimate +Enclosure,"Black panels, 4 mm ACM or PP, light-tight tunnel",4.3 m2,,25,60,108,258,per m2; estimate +Enclosure,"Clear polycarbonate guard, reject zone",1.4 m2,,30,70,42,98,per m2; estimate +Enclosure,"Door hinges, handle, safety door switch",1 lot,,120,350,120,350,estimate +Conveyor,"PU round belt 6 mm, welded endless",2 x 3.8 m,,15,40,30,80,"each; PU round belt listings (RS, Amazon) plus welding" +Conveyor,"Round-belt pulleys, shaft, bearings, UHMW slide strips, side guides",1 lot,,250,700,250,700,"pulleys USD 5 to 11 each (ServoCity, REV); rest estimate" +Conveyor,"Conveyor drive: small gear motor with drive, or servo",1,,250,600,250,600,estimate +Handling,"Guided lift cylinder, 20 mm bore, 75 mm stroke",1,SMC MGPM20-75,330,450,330,450,SMC MGPM20-30 USD 322 (Misumi US) to 390 (Motion); 75 mm stroke a little more +Handling,Spindle servo 200 W with drive (encoder triggers the line-scans),1,Delta ASDA-B3 class,300,700,300,700,drive USD 119 to 200 (listings); motor and cable estimate +Handling,"Spindle head: bearing housing, timing belt, label pad, pneumatic expanding mandrel",1,machined parts,600,1800,600,1800,estimate +Handling,"Rodless cylinder 25 mm, 350 mm stroke, with paddle",1,SMC MY1B / CY3B class,400,900,400,900,estimate +Handling,"Valve terminal, FRL, tubing, fittings",1 lot,,400,1000,400,1000,estimate +Vision,"4k monochrome line-scan camera, GigE",2,Basler racer 2 r2L4096 / Hikrobot MV-CL042-91GM,857,1088,1714,2176,Basler web shop via price listing +Vision,"Line-scan lens, 50 mm",2,,300,800,600,1600,estimate +Vision,Coaxial line light,2,,295,446,590,892,made-in-china listing USD 295 to 349; Vision Datum USD 420; light186 from USD 446 +Vision,"Dark-field line light, about 150 mm",4,Vision Datum LiN+ class,250,400,1000,1600,Vision Datum LiN+ USD 380 +Vision,Diffuse line backlight,2,,200,400,400,800,estimate +Vision,"Colour area camera, 8 MP, GigE",2,Basler ace 2 a2A2840-14gcPRO,1119,1119,2238,2238,Basler web shop via price listing +Vision,Lens 16 mm,2,,150,400,300,800,estimate +Vision,Ring light,2,,150,400,300,800,estimate +Vision,Strobe and light controllers,2,,300,800,600,1600,estimate +Sensors,"Laser distance sensor, 50 mm range",2,Micro-Epsilon optoNCDT 1420,520,1600,1040,3200,"list from EUR 470 (Micro-Epsilon); ILD1420-25 about USD 1,600 (Radwell)" +Sensors,Photoelectric presence sensors and one through-beam,5,,50,150,250,750,each; estimate +Sensors,"Thermal camera (option, not in the total)",0 (1 optional),Optris Xi 400,2100,3000,0,0,"from EUR 1,950 (Optris); USD 2,995 (moviTHERM)" +Control,PLC for sequence and safety I/O,1,Mitsubishi FX5U-32MT or Delta class,500,1096,500,1096,"FX5U-32MT/ES USD 1,096 (Mitsubishi US shop); resale from about USD 470" +Control,"Edge PC with GPU, 4 GigE PoE ports, NixOS",1,,1500,3200,1500,3200,"qscompute buying guides 2026: USD 1,490 to 3,200" +Control,"HMI touch panel, 10 inch",1,,400,900,400,900,estimate +Control,"Safety relay, e-stop, door interlock wiring",1 lot,,300,600,300,600,estimate +Control,"Cabinet, power supply, breakers, terminals, wiring, stack light",1 lot,,800,2000,800,2000,estimate +Total,"Hardware per station, thermal camera excluded",,,,,16056,32715,sum of the lines above diff --git a/docs/equipment/inline-qc-station/inline-qc-station-closed.png b/docs/equipment/inline-qc-station/inline-qc-station-closed.png new file mode 100644 index 0000000..0c71462 Binary files /dev/null and b/docs/equipment/inline-qc-station/inline-qc-station-closed.png differ diff --git a/docs/equipment/inline-qc-station/inline-qc-station-detail-front.png b/docs/equipment/inline-qc-station/inline-qc-station-detail-front.png new file mode 100644 index 0000000..20522d7 Binary files /dev/null and b/docs/equipment/inline-qc-station/inline-qc-station-detail-front.png differ diff --git a/docs/equipment/inline-qc-station/inline-qc-station-detail.png b/docs/equipment/inline-qc-station/inline-qc-station-detail.png new file mode 100644 index 0000000..33648be Binary files /dev/null and b/docs/equipment/inline-qc-station/inline-qc-station-detail.png differ diff --git a/docs/equipment/inline-qc-station/inline-qc-station-front.png b/docs/equipment/inline-qc-station/inline-qc-station-front.png new file mode 100644 index 0000000..3f4aabb Binary files /dev/null and b/docs/equipment/inline-qc-station/inline-qc-station-front.png differ diff --git a/docs/equipment/inline-qc-station/inline-qc-station-iso.png b/docs/equipment/inline-qc-station/inline-qc-station-iso.png new file mode 100644 index 0000000..2088061 Binary files /dev/null and b/docs/equipment/inline-qc-station/inline-qc-station-iso.png differ diff --git a/docs/equipment/inline-qc-station/inline-qc-station-top.png b/docs/equipment/inline-qc-station/inline-qc-station-top.png new file mode 100644 index 0000000..d92dd6c Binary files /dev/null and b/docs/equipment/inline-qc-station/inline-qc-station-top.png differ diff --git a/docs/equipment/inline-qc-station/inline-qc-station.FCStd b/docs/equipment/inline-qc-station/inline-qc-station.FCStd new file mode 100644 index 0000000..ee8f502 Binary files /dev/null and b/docs/equipment/inline-qc-station/inline-qc-station.FCStd differ diff --git a/docs/equipment/inline-qc-station/render_station.py b/docs/equipment/inline-qc-station/render_station.py new file mode 100644 index 0000000..3c18e13 --- /dev/null +++ b/docs/equipment/inline-qc-station/render_station.py @@ -0,0 +1,135 @@ +# 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")