diff --git a/CATALOG.md b/CATALOG.md index d52996fb..5e43a510 100644 --- a/CATALOG.md +++ b/CATALOG.md @@ -1107,6 +1107,50 @@ Generated by `swift run -q realityhd catalog`. Do not edit by hand. | [`fiber-cleaner`](docs/assets/fiber-cleaner.png) | prop, handheld | 1080 | 1200 | `plastic.matte:2A6BD6` `plastic.matte:8B8E92` `plastic.matte:0C0D0F` `metal.steel` `plastic.matte:EDEEF0` `plastic.matte:0B1B3A` `plastic.matte:7FA3E0` `plastic.matte:3F7BE0` | realityhd | Push-type one-click LC/SC fiber cleaning pen, blue ridged body, gray tip cap and pocket clip, standing upright. | | [`cable-label`](docs/assets/cable-label.png) | prop | 226 | 600 | `plastic.matte:FFFFFF` `glass.polycarbonate` `plastic.matte:111214` `plastic.matte:D9DCE0` `plastic.matte:5A5D61` `plastic.matte:ECEEF0` `plastic.matte:E2E4E7` | realityhd | White self-laminating cable wrap label printed with ID A07-09-PA and a clear laminate tail. | | [`velcro-tie`](docs/assets/velcro-tie.png) | prop | 380 | 400 | `fabric.nylon:1A1A1A` `fabric.nylon:0E0E0F` `plastic.matte:222325` `plastic.matte:050506` `plastic.matte:2E2F31` | realityhd | Black hook-and-loop cable tie, 0.2 m strap with a slotted buckle, free end curled into a loop. | +| [`foup`](docs/assets/foup.png) | prop, semiconductor, handheld, articulated | 4688 | 5000 | `plastic.matte:3A3C40` `metal.aluminum-brushed` `plastic.polycarbonate-tint` `plastic.matte:E8E4D8` `silicon.thinfilm` `plastic.matte:2A2C2F` `plastic.matte:101010` `plastic.matte:EEEEEA` | realityhd | 25-slot front-opening pod for 300 mm wafers with top flange, side handles and door. | +| [`oht-vehicle`](docs/assets/oht-vehicle.png) | prop, semiconductor, articulated | 1608 | 6000 | `plastic.gloss:E4E6E8` `metal.aluminum-brushed` `rubber.tubing` `metal.steel` `glass.lamp` `plastic.matte:2A2C2F` `plastic.matte:3A3A3C` | realityhd | Overhead hoist transport vehicle with four-wheel carriage and belt hoist gripper. | +| [`wafer-300mm`](docs/assets/wafer-300mm.png) | prop, semiconductor, handheld | 1282 | 2000 | `silicon.thinfilm` `silicon.bare` `silicon.mark` `silicon.die-a` `silicon.die-b` `silicon.die-c` `silicon.die-d` | realityhd | 300 mm silicon wafer with notch, selectable process state. | +| [`euv-scanner-highna`](docs/assets/euv-scanner-highna.png) | prop, semiconductor, articulated | 14820 / 6770 | 15000 | `metal.grate-iron` `plastic.matte:222426` `metal.powdercoat:34373C` `metal.powdercoat:E9E9E4` `metal.anodized` `metal.steel` `plastic.yellow` `metal.aluminum-brushed` `rubber.tubing` `metal.powdercoat:8A8D92` `plastic.polycarbonate-tint` `glass.lamp:40E060` `plastic.matte:3A3C40` `silicon.thinfilm` `glass.lamp:E03020` `glass.lamp:F0A020` `glass.lamp:30C050` `glass.lamp:3080F0` `plastic.matte:2A2C2F` | realityhd | High-NA EUV lithography scanner: modular white enclosure, handlers, source duct, status tower, removable covers and wafer stage. | +| [`litho-track`](docs/assets/litho-track.png) | prop, semiconductor, articulated | 13254 / 6332 | 14000 | `metal.grate-iron` `plastic.matte:1E2023` `metal.powdercoat:34373C` `metal.powdercoat:EDEDE9` `metal.powdercoat:9EA3A8` `glass.lamp:AEB8BF` `metal.powdercoat:C4C8CC` `glass.lamp:F4F8FF` `metal.stainless` `silicon.thinfilm` `metal.anodized` `metal.powdercoat:C9CBCC` `metal.powdercoat:B9BBBB` `plastic.matte:C8C8C2` `plastic.matte:BCBCB6` `plastic.matte:CFCFC9` `plastic.yellow` `plastic.matte:EEEEEA` `metal.powdercoat:1A1C1F` `glass.lamp:40E060` `glass.lamp:F0A020` `plastic.polycarbonate-tint` `plastic.matte:2A2C2F` `plastic.matte:3A3C40` `glass.lamp:2A3A30` `rubber.tubing` `glass.lamp:E03020` `glass.lamp:30C050` `plastic.matte:15171A` `plastic.matte:0B2236` `plastic.matte:1F5FA8` `plastic.matte:2E8B57` `plastic.matte:24507A` `plastic.matte:3F4A58` `glass.window-tint` | realityhd | Coater/developer track: white paneled modules, tinted service windows with spin-coat bowls, 4 FOUP load ports, status tower, touchscreen. | +| [`plasma-etch-cluster`](docs/assets/plasma-etch-cluster.png) | prop, semiconductor, articulated | 13588 / 5484 | 14000 | `metal.grate-iron` `metal.powdercoat:34373C` `metal.satin-aluminum` `metal.anodized` `glass.window-tint` `metal.stainless` `plastic.matte:1E2023` `metal.powdercoat:EDEDE9` `metal.powdercoat:9EA3A8` `plastic.matte:2A2C2F` `rubber.tubing` `metal.powdercoat:1A1C1F` `glass.lamp:40E060` `glass.lamp:F0A020` `plastic.polycarbonate-tint` `plastic.matte:3A3C40` `silicon.thinfilm` `glass.lamp:E03020` `glass.lamp:30C050` `glass.lamp:F4F8FF` `plastic.yellow` `plastic.matte:EEEEEA` `plastic.matte:C8C8C2` `metal.powdercoat:C9CBCC` `metal.brass` | realityhd | Etch cluster: hex transfer chamber, 4 bead-blasted aluminum chambers with RF match boxes, 2 load locks, EFEM, gas sticks, status tower. | +| [`cvd-ald-cluster`](docs/assets/cvd-ald-cluster.png) | prop, semiconductor, articulated | 13676 / 5972 | 14000 | `metal.grate-iron` `metal.powdercoat:34373C` `metal.satin-aluminum` `metal.anodized` `glass.window-tint` `metal.stainless` `plastic.matte:1E2023` `metal.powdercoat:E8EAE6` `metal.powdercoat:9EA3A8` `metal.powdercoat:D4D6D2` `metal.powdercoat:C2C5C2` `plastic.matte:2A2C2F` `glass.lamp:40E060` `metal.brass` `plastic.matte:C9963A` `metal.powdercoat:1A1C1F` `glass.lamp:F0A020` `plastic.polycarbonate-tint` `plastic.matte:3A3C40` `silicon.thinfilm` `glass.lamp:E03020` `glass.lamp:30C050` `glass.lamp:F4F8FF` `plastic.yellow` `plastic.matte:EEEEEA` `plastic.matte:C8C8C2` | realityhd | CVD/ALD cluster: hex transfer chamber, 4 chambers with heated showerhead lids, precursor ampoule cabinets, 8 gas sticks each, EFEM. | +| [`pvd-cluster`](docs/assets/pvd-cluster.png) | prop, semiconductor, articulated | 13828 / 6268 | 14000 | `metal.grate-iron` `metal.powdercoat:34373C` `metal.satin-aluminum` `metal.anodized` `glass.window-tint` `metal.stainless` `plastic.matte:1E2023` `metal.powdercoat:E4E6E8` `metal.powdercoat:9EA3A8` `metal.powdercoat:1A1C1F` `glass.lamp:40E060` `glass.lamp:F0A020` `plastic.polycarbonate-tint` `plastic.matte:2A2C2F` `plastic.matte:3A3C40` `silicon.thinfilm` `glass.lamp:E03020` `glass.lamp:30C050` `glass.lamp:F4F8FF` `plastic.yellow` `plastic.matte:EEEEEA` `plastic.matte:C8C8C2` `metal.powdercoat:2A2C2F` `plastic.matte:E8E4D8` `metal.brass` `rubber.tubing` | realityhd | PVD cluster: hex transfer chamber, 4 sputter chambers with magnetron cathode lids, tall cryopumps, load locks, EFEM. | +| [`cmp-tool`](docs/assets/cmp-tool.png) | prop, semiconductor, articulated | 11094 / 3636 | 12000 | `metal.grate-iron` `metal.powdercoat:34373C` `metal.powdercoat:A8ACB0` `metal.powdercoat:EDEDE9` `metal.powdercoat:C4C8CC` `glass.window-tint` `metal.anodized` `metal.powdercoat:D8D9D5` `plastic.matte:1E2023` `plastic.yellow` `metal.stainless` `metal.powdercoat:2A2C2F` `silicon.thinfilm` `plastic.pad` `plastic.matte:E4E2DA` `metal.powdercoat:1A1C1F` `glass.lamp:40E060` `glass.lamp:F0A020` `plastic.polycarbonate-tint` `plastic.matte:2A2C2F` `plastic.matte:3A3C40` `plastic.matte:EEEEEA` `glass.lamp:E03020` `glass.lamp:30C050` `glass.lamp:F4F8FF` `plastic.matte:C8C8C2` `rubber.tubing` `plastic.matte:4C4E50` `plastic.matte:6A6B68` | realityhd | CMP tool: white enclosure with large window, 3 spinning platens with pads, slurry arms, carrier heads, cleaner module, load ports. | +| [`ion-implanter`](docs/assets/ion-implanter.png) | prop, semiconductor, articulated | 5871 / 2852 | 14000 | `metal.grate-iron` `metal.powdercoat:34373C` `metal.powdercoat:EDEDE9` `metal.anodized` `metal.powdercoat:C4C8CC` `metal.stainless` `plastic.matte:1E2023` `plastic.matte:E8E4D8` `plastic.yellow` `metal.copper` `metal.powdercoat:A8ACB0` `plastic.matte:2A2C2F` `rubber.tubing` `metal.powdercoat:C9CBCC` `glass.window-tint` `metal.powdercoat:1A1C1F` `glass.lamp:40E060` `glass.lamp:F0A020` `plastic.polycarbonate-tint` `plastic.matte:3A3C40` `silicon.thinfilm` `glass.lamp:E03020` `glass.lamp:30C050` `glass.lamp:F4F8FF` `plastic.matte:EEEEEA` `plastic.matte:C8C8C2` `metal.powdercoat:E6E7E3` | realityhd | Ion implanter: HV source housing with warning labels, beamline enclosure, analyzer magnet bulge, end station, load ports, hinged panels. | +| [`diffusion-furnace`](docs/assets/diffusion-furnace.png) | prop, semiconductor, articulated | 9526 / 3114 | 10000 | `metal.grate-iron` `metal.powdercoat:34373C` `metal.powdercoat:A8ACB0` `metal.powdercoat:C9CCCE` `metal.powdercoat:2A2C2F` `metal.anodized` `plastic.matte:1E2023` `glass.window-tint` `metal.satin-aluminum` `metal.stainless` `metal.powdercoat:C86A28` `quartz` `metal.brass` `plastic.matte:EEEEEA` `glass.lamp:2A6AC0` `glass.lamp:E03020` `glass.lamp:F0A020` `glass.lamp:30C050` `glass.lamp:F4F8FF` `rubber.tubing` `plastic.yellow` `silicon.bare` | realityhd | Vertical diffusion furnace: powdercoat tower, quartz wafer boat on elevator, heater jacket, gas panel, exhaust duct. | +| [`wet-bench`](docs/assets/wet-bench.png) | prop, semiconductor, articulated | 4334 / 2878 | 10000 | `metal.powdercoat:34373C` `plastic.pvdf` `plastic.matte:1E2023` `metal.stainless` `plastic.matte:8EA2AC` `plastic.matte:3A6A8A` `metal.anodized` `quartz` `plastic.matte:8A6A3A` `plastic.matte:5A8A6A` `plastic.matte:7A5A8A` `glass.clear` `plastic.matte:E8E4D8` `silicon.thinfilm` `plastic.matte:EEEEEA` `rubber.tubing` `glass.lamp:2A6AC0` `plastic.matte:3A3C40` `glass.lamp:E03020` `plastic.matte:C8B070` `plastic.matte:9A7040` `plastic.matte:B8B4A8` `plastic.matte:7A9A8A` `plastic.matte:C0A860` `plastic.matte:A0B8C8` `plastic.matte:E8C21A` `plastic.matte:E03020` `plastic.matte:2F8FC0` `plastic.yellow` `glass.window-tint` | realityhd | Wet bench: white PVDF deck with recessed baths, DI rinse tanks, clear sliding sash, exhaust plenum, control panel. | +| [`oht-rail`](docs/assets/oht-rail.png) | prop, semiconductor | 600 | 800 | `metal.aluminum-brushed` `metal.copper` `plastic.matte:2A2C2F` `metal.stainless` | realityhd | OHT ceiling track: extruded aluminum inverted-U rail with power bar, splice plate and hanger rods. | +| [`ffu-ceiling-tile`](docs/assets/ffu-ceiling-tile.png) | prop, semiconductor | 1196 | 1200 | `metal.powdercoat:F4F4F2` `filter.hepa` `metal.aluminum-brushed` `metal.stainless` | realityhd | Fan filter unit: white sheet-metal plenum box, HEPA pleat pack behind a slotted grille, fan collar. | +| [`cleanroom-floor-grate`](docs/assets/cleanroom-floor-grate.png) | prop, semiconductor | 420 | 800 | `metal.powdercoat:8C9196` `metal.stainless` `metal.powdercoat:C8B070` | realityhd | Raised-floor grate panel: epoxy-coated steel frame with bearing bars and cross rods. | +| [`fab-yellow-light-panel`](docs/assets/fab-yellow-light-panel.png) | prop, semiconductor | 360 | 400 | `metal.powdercoat:F4F4F2` `emissive.amber` `metal.aluminum-brushed` | realityhd | Yellow-room lighting panel: amber diffuser in a white metal frame with back housing. | +| [`ebeam-metrology`](docs/assets/ebeam-metrology.png) | prop, semiconductor, articulated | 7506 / 4718 | 10000 | `metal.powdercoat:34373C` `metal.stainless` `plastic.matte:1E2023` `metal.anodized` `metal.powdercoat:ECECE8` `metal.powdercoat:C4C8CC` `plastic.matte:A9ADAA` `plastic.matte:B9BDB8` `metal.powdercoat:D6D8D8` `rubber.tubing` `plastic.yellow` `metal.powdercoat:C9CBCC` `metal.powdercoat:1A1C1F` `glass.lamp:40E060` `glass.lamp:F0A020` `plastic.polycarbonate-tint` `plastic.matte:2A2C2F` `plastic.matte:3A3C40` `silicon.thinfilm` `glass.lamp:E03020` `glass.lamp:30C050` `glass.lamp:F4F8FF` `plastic.matte:16283C` `plastic.matte:B0B8BC` `glass.window-tint` | realityhd | E-beam metrology tool: isolated base, chamber cabinet, e-beam column dome, EFEM with hinged door. | +| [`probe-station`](docs/assets/probe-station.png) | prop, semiconductor, articulated | 6616 / 4856 | 9000 | `metal.powdercoat:B9BDBF` `plastic.matte:1E2023` `metal.stainless` `metal.powdercoat:34373C` `plastic.matte:A9ADAA` `plastic.matte:B9BDB8` `metal.powdercoat:E8E9E5` `metal.powdercoat:5A5D60` `metal.anodized` `silicon.thinfilm` `metal.powdercoat:1E2023` `glass.lamp:F4F8FF` `plastic.matte:16283C` `plastic.matte:B0B8BC` `plastic.yellow` `plastic.matte:2E6B46` `metal.brass-aged` | realityhd | Probe station: isolation table, chuck with wafer, manipulators, probe card lift, microscope head. | +| [`dicing-saw`](docs/assets/dicing-saw.png) | prop, semiconductor, articulated | 6134 / 4674 | 8000 | `plastic.matte:1E2023` `metal.powdercoat:ECECE8` `metal.powdercoat:C4C8CC` `metal.stainless` `plastic.matte:A9ADAA` `plastic.matte:B9BDB8` `metal.anodized` `glass.lamp:E03020` `glass.lamp:F0A020` `glass.lamp:30C050` `glass.lamp:F4F8FF` `plastic.matte:16283C` `plastic.matte:B0B8BC` `metal.powdercoat:34373C` `metal.stainless:D8DADC` `plastic.matte:3C70B4` `silicon.thinfilm-patterned` `glass.window-tint` `plastic.yellow` `plastic.matte:A8B0B4` | realityhd | Dicing saw: cabinet, wet deck, spindle with blade, tape-frame wafer, hinged lid with window. | +| [`hybrid-bonder`](docs/assets/hybrid-bonder.png) | prop, semiconductor, articulated | 7044 / 5182 | 10000 | `plastic.matte:1E2023` `metal.powdercoat:EDEDE9` `metal.powdercoat:D6D8D6` `metal.anodized` `metal.powdercoat:C4C8CC` `metal.powdercoat:34373C` `metal.stainless` `plastic.matte:A9ADAA` `plastic.matte:B9BDB8` `plastic.matte:16283C` `plastic.matte:B0B8BC` `plastic.yellow` `glass.lamp:E03020` `glass.lamp:F0A020` `glass.lamp:30C050` `glass.lamp:F4F8FF` `silicon.thinfilm-patterned` `silicon.thinfilm` `silicon.thinfilm-metal` `glass.window-tint` `plastic.matte:A8B0B4` | realityhd | Hybrid bonder: stepped-roof process module, side cabinet, hinged door with window onto bond head and chucks. | +| [`tool-status-tower`](docs/assets/tool-status-tower.png) | prop, semiconductor | 864 | 600 | `metal.stainless` `metal.anodized` `plastic.matte:1E2023` `plastic.matte:2A2C2F` `plastic.translucent:E03020` `plastic.translucent:F0A020` `glass.lamp:30C050` `plastic.translucent:30C050` `plastic.translucent:2A70E8` | realityhd | Tool status tower: mounting bracket, pole, four stacked translucent lamps, buzzer cap, lamp state knob. | +| [`foup-stocker`](docs/assets/foup-stocker.png) | prop, semiconductor, articulated | 10356 / 8744 | 12000 | `metal.powdercoat:34373C` `metal.powdercoat:F5F5F2` `metal.powdercoat:C4C8CC` `metal.anodized` `glass.window-tint` `plastic.matte:E8E8E4` `plastic.matte:B0B8BC` `metal.powdercoat:B8BCC0` `metal.stainless` `plastic.polycarbonate-tint` `plastic.matte:1E2023` `glass.lamp:F4F8FF` `glass.lamp:30C050` `metal.powdercoat:C9CDD0` `metal.powdercoat:ECECE8` `glass.lamp:E03020` `glass.lamp:F0A020` `metal.powdercoat:1A1C1F` `glass.lamp:40E060` `plastic.matte:2A2C2F` `plastic.matte:3A3C40` `silicon.thinfilm` `metal.powdercoat:9FA3A7` `glass.lamp:6FC8F0` `metal.powdercoat:E8C21A` `plastic.matte:A9ADAA` `plastic.matte:B9BDB8` `metal.aluminum-brushed` | realityhd | Automated FOUP stocker: white enclosure, glass front showing FOUP shelves, internal stacker robot, OHT ports on top, I/O ports at floor. | +| [`efem-robot`](docs/assets/efem-robot.png) | prop, semiconductor, articulated | 3484 / 1942 | 8000 | `metal.anodized` `plastic.matte:EDEEEC` `plastic.matte:1E2023` `glass.lamp:30C050` `metal.powdercoat:9FA3A7` `metal.stainless` `plastic.matte:2A2C2F` `plastic.matte:3A3C40` `metal.aluminum-brushed` `glass.lamp:F0A020` `rubber.tubing` `silicon.thinfilm` | realityhd | SCARA wafer-handling robot with column, two-link arm, fork end-effector and cable carrier. | +| [`euv-light-source`](docs/assets/euv-light-source.png) | prop, semiconductor, articulated | 13488 / 2820 | 14000 | `metal.grate-iron` `metal.powdercoat:34373C` `metal.powdercoat:E9E9E4` `metal.stainless` `plastic.matte:A9ADAA` `plastic.matte:B9BDB8` `plastic.matte:1E2023` `glass.lamp:40E060` `plastic.matte:C8C8C2` `metal.anodized` `glass.lamp:E03020` `glass.lamp:F0A020` `glass.lamp:30C050` `glass.lamp:F4F8FF` `metal.steel` `metal.powdercoat:9A9EA3` `metal.chrome` `glass.lamp:A8E0FF` `metal.powdercoat:5A5E63` `metal.copper` `metal.anodized-black` `plastic.matte:E8C21A` `metal.powdercoat:A8ACB0` `metal.powdercoat:E8601A` | realityhd | EUV light source: CO2 laser drive cabinet, tin vessel with collector mirror, cutaway cover, copper and steel plumbing. | +| [`duv-immersion-scanner`](docs/assets/duv-immersion-scanner.png) | prop, semiconductor, articulated | 4974 / 2190 | 14000 | `metal.grate-iron` `metal.powdercoat:34373C` `metal.powdercoat:EDEDE9` `metal.powdercoat:C4C8CC` `glass.window-tint` `metal.anodized` `metal.aluminum-brushed` `metal.stainless` `metal.anodized-black` `silicon.thinfilm` `rubber.tubing` `plastic.matte:B0B8BC` `metal.powdercoat:D8D9D5` `metal.powdercoat:1A1C1F` `glass.lamp:40E060` `glass.lamp:F0A020` `plastic.polycarbonate-tint` `plastic.matte:2A2C2F` `plastic.matte:3A3C40` `glass.lamp:E03020` `glass.lamp:30C050` `glass.lamp:F4F8FF` `plastic.matte:1E2023` `plastic.matte:C8C8C2` `plastic.matte:A9ADAA` `plastic.matte:B9BDB8` | realityhd | DUV immersion scanner: white modular enclosure, removable covers, immersion hood, handlers. | +| [`package-substrate`](docs/assets/package-substrate.png) | prop, electronics, articulated | 2088 | 2500 | `laminate.abf` `metal.gold` `metal.solder` `plastic.underfill` | realityhd | Bare organic package substrate: ABF laminate, gold die-land pads, perimeter pads, fiducials, edge capacitors. | +| [`chip-package-final`](docs/assets/chip-package-final.png) | prop, chiplab, electronics, articulated | 12164 | 12500 | `laminate.abf` `metal.copper` `metal.gold` `plastic.underfill` `metal.solder` `glass.substrate` `silicon.die-edge` `silicon.die` `metal.nickel` | realityhd | AI accelerator package: nickel lid with laser mark, compute dies and HBM on glass interposer, ABF substrate, BGA balls; explodes. | +| [`hbm-stack`](docs/assets/hbm-stack.png) | prop, chiplab, electronics, articulated | 9512 | 12000 | `silicon.die-edge` `silicon.die` `metal.copper` `plastic.underfill:6B5230` `plastic.underfill:16120E` `plastic.underfill` | realityhd | HBM5 stack: base logic die, 16 thinned DRAM dies with edge sheen, underfill fillets, copper microbump rows and molded sidewalls; explodes. | +| [`die-blank-reticle`](docs/assets/die-blank-reticle.png) | prop, chiplab, electronics, articulated | 1888 | 2000 | `silicon.die-edge` `silicon.die` `silicon.die:343A52` `metal.gold` `metal.nickel` `silicon.die:3A4468` `silicon.die:334E66` `metal.solder` `silicon.die:5A4A8C` `silicon.die:2F6A80` `silicon.die:3A5A9A` | realityhd | Blank reticle-limit die: mirror silicon backside, interference-sheen front, seal ring, scribe remnant, C4 bump field, named block slot sockets. | +| [`interposer-glass`](docs/assets/interposer-glass.png) | prop | 1996 | 2000 | `glass.substrate-clear` `metal.copper` `metal.gold` `metal.solder` | realityhd | Glass-core interposer with TGV copper grid, RDL traces, die and HBM landing footprints. | +| [`holo-table`](docs/assets/holo-table.png) | prop, furniture | 6000 | 6000 | `rubber` `metal.chrome` `metal.anodized-worn` `metal.anodized-black` `glass.display` `emissive.holo-grid` | realityhd | 2035 holo table: anodized aluminum pedestal frame, glass top with emissive grid, cable port, leveling feet. | +| [`die-block-tile`](docs/assets/die-block-tile.png) | prop, chiplab, electronics, articulated | 588 | 600 | `silicon.die-edge` `silicon.die` `silicon.die:4A3A7A` `emissive.die-tensor` `silicon.die:7A7A98` `metal.gold` | realityhd | Parametric die block tile (tensor, sram, noc, hbmPhy, io): silicon body, type-specific surface pattern, emissive tint, named snap pin sockets. | +| [`design-workbench-2035`](docs/assets/design-workbench-2035.png) | prop, chiplab, furniture, articulated | 6734 | 10000 | `laminate.white:B9BDC0` `metal.aluminum-brushed` `metal.anodized-black` `rubber` `plastic.black` `emissive.holo-grid` `glass.display` `screen.ui` `emissive.holo-plane` | realityhd | 2035 design workbench: laminate top, anodized frame, three curved glass displays, keyboard deck, emissive holo plane, cable spine. | +| [`transistor-gaa-cutaway`](docs/assets/transistor-gaa-cutaway.png) | prop, chiplab, micro-scale, articulated | 5696 | 8000 | `metal.copper` `silicon.die-edge` `metal.tungsten` `oxide.lowk` `silicon.die` `oxide.hafnium` | realityhd | GAA nanosheet FET cutaway at 1e6x scale: 3 nanosheets wrapped by HfO2 and tungsten gate, epi source/drain, spacers, contacts, backside power rail; explodes by layer. | +| [`interconnect-stack-cutaway`](docs/assets/interconnect-stack-cutaway.png) | prop, chiplab, micro-scale, articulated | 3444 | 8000 | `silicon.die-edge` `oxide.lowk` `metal.copper` `metal.tungsten` | realityhd | BEOL interconnect stack cutaway at 1e6x scale: 12 copper metal layers with vias in low-k dielectric, pitch widening upward; explodes by layer. | +| [`wafer-display-stand`](docs/assets/wafer-display-stand.png) | prop, chiplab | 8364 | 8500 | `metal.chrome` `rubber` `metal.stainless` `plastic.matte:101014` `silicon.die-edge` `silicon.die:6B7088` `silicon.thinfilm:A96F44` `silicon.thinfilm:AD7449` `silicon.thinfilm:B24E4E` `silicon.thinfilm:B44F7A` `silicon.thinfilm:B04C77` `silicon.thinfilm:AD7448` `silicon.thinfilm:B75252` `silicon.thinfilm:BD5984` `silicon.thinfilm:BF5AB0` `silicon.thinfilm:BA56AC` `silicon.thinfilm:B24D79` `silicon.thinfilm:B37A4F` `silicon.thinfilm:BF5B86` `silicon.thinfilm:C764B9` `silicon.thinfilm:AD66CA` `silicon.thinfilm:A760C4` `silicon.thinfilm:B955AB` `silicon.thinfilm:AD4974` `silicon.thinfilm:AA7146` `silicon.thinfilm:B85353` `silicon.thinfilm:C5618C` `silicon.thinfilm:D16FC3` `silicon.thinfilm:7381D4` `silicon.thinfilm:8568CB` `silicon.thinfilm:A25ABE` `silicon.thinfilm:B14C77` `silicon.thinfilm:AB4747` `silicon.thinfilm:B95580` `silicon.thinfilm:AB64C7` `silicon.thinfilm:9074D5` `silicon.thinfilm:7D8BDC` `silicon.thinfilm:886CCE` `silicon.thinfilm:C05CB2` `silicon.thinfilm:AA4646` `silicon.thinfilm:B8537E` `silicon.thinfilm:A961C5` `silicon.thinfilm:6F7DD1` `silicon.thinfilm:B973D4` `silicon.thinfilm:CB68BD` `silicon.thinfilm:BE5A85` `silicon.thinfilm:B14C4C` `silicon.thinfilm:B34F7A` `silicon.thinfilm:A25BBF` `silicon.thinfilm:CA66BB` `silicon.thinfilm:C4608B` `silicon.thinfilm:B95555` `silicon.thinfilm:AD4874` `silicon.thinfilm:B752A8` `silicon.thinfilm:BD59AF` `silicon.thinfilm:BF5A85` `silicon.thinfilm:BA5656` `silicon.thinfilm:B2794D` `silicon.thinfilm:B24E79` `silicon.thinfilm:B44F4F` `silicon.thinfilm:B0774C` `metal.gold` `plastic.acrylic` | realityhd | Chrome base and clear acrylic cradle holding a 300 mm patterned wafer with iridescent die grid and an engraved plaque. | +| [`bond-wire-macro`](docs/assets/bond-wire-macro.png) | prop, chiplab | 10112 | 10500 | `metal.anodized-black` `silicon.die-edge` `silicon.die` `laminate.abf` `metal.aluminum-brushed` `metal.gold` | realityhd | Parametric gold bond wire loops between die pads and substrate fingers with ball and wedge bonds at macro scale. | +| [`orbital-dc-module`](docs/assets/orbital-dc-module.png) | prop, space, datacenter, metal | 12168 | 15000 | `thermal.radiator-white:DDDDD7` `metal.aluminum-brushed` `mli.blanket` `metal.titanium` `plastic.matte:1B1D22` `metal.steel` `composite.carbon` `plastic.matte:2A6BD6` `plastic.matte:D62A1E` `metal.powdercoat:C9CBCC` `metal.stainless` | realityhd | Pressurized orbital compute module: Whipple-shield hull in a square truss, gold MLI end cones, docking ports, radiator mounts and external compute boxes. | +| [`space-radiator-panel`](docs/assets/space-radiator-panel.png) | prop, articulated, space, metal, datacenter | 2840 | 3000 | `metal.aluminum-brushed` `metal.titanium` `metal.steel` `mli.blanket` `plastic.matte:2A6BD6` `metal.casework` `thermal.radiator-white:D9D9D3` `thermal.radiator-white:E7E7E1` `thermal.radiator-white:E3E2DB` `thermal.radiator-white:EAEAE5` `thermal.radiator-white:DDDCD4` `thermal.radiator-white:E6E5DE` `thermal.radiator-white:E1E1DA` `metal.stainless` `solar.kapton` | realityhd | Deployable two-panel spacecraft radiator: white-coated honeycomb tiles, coolant tubes that glow when hot, hinge brackets with MLI-wrapped hoses. | +| [`solar-array-wing`](docs/assets/solar-array-wing.png) | prop, articulated, space, solar, datacenter | 3000 | 4000 | `metal.titanium` `metal.aluminum-brushed` `plastic.matte:2A2D33` `mli.blanket` `composite.carbon` `solar.cell` `solar.kapton` `metal.steel` | realityhd | Z-fold solar array wing: ten hinged cell-grid panels with carbon trays, edge frames, knuckles and a root drive spar. | +| [`lunar-dc-habitat`](docs/assets/lunar-dc-habitat.png) | prop, space, datacenter, ground | 14928 | 15000 | `regolith.lunar-fine` `thermal.radiator-white:DDDDD7` `metal.aluminum-brushed` `mli.silver` `plastic.matte:2A2D33` `metal.titanium` `metal.stainless` `fabric.canvas:8D887C` `thermal.radiator-white:E3E3DD` `thermal.radiator-glow` `thermal.radiator-white` `regolith.lunar` | realityhd | Lunar data center habitat: modules under a sandbag regolith berm, radiator fins, airlock and grey regolith skirt. | +| [`mars-dc-dome`](docs/assets/mars-dc-dome.png) | prop, space, datacenter, ground | 11352 | 15000 | `dust.mars` `glass.tinted` `metal.aluminum-brushed` `thermal.radiator-white:E6E6E0` `thermal.radiator-white:DEDDD6` `thermal.radiator-white:E2E2DB` `thermal.radiator-white:E9E9E4` `dust.mars-film` `thermal.radiator-white:D8C4B2` `concrete.smooth` `thermal.radiator-white:E3E3DD` `plastic.matte:2A2D33` `metal.titanium` `thermal.radiator-white:E0DDD6` `thermal.radiator-glow` `thermal.radiator-white` `rock.basalt` | realityhd | Mars data center: geodesic dome with red dust on lower panels, pressurized annex and radiators. | +| [`ground-station-dish`](docs/assets/ground-station-dish.png) | prop, articulated, space, metal, datacenter | 11270 | 10000 | `concrete.rough` `concrete.smooth` `metal.painted:D3D7D9` `metal.galvanized` `metal.steel` `metal.painted:C9CED1` `metal.painted:E0B000` `plastic.black` `metal.casework` `metal.titanium` `metal.painted:2F3438` `metal.painted:E9EBEC` `metal.aluminum-brushed` `plastic.matte:D8D4C6` `metal.painted:C7CBCD` `emissive.signal-red` | realityhd | Satellite ground station dish: panelled parabolic reflector, back truss, quadripod feed horn, az-el pedestal on a concrete foundation, slewing dish. | +| [`earth-site-pin`](docs/assets/earth-site-pin.png) | prop, space, datacenter, metal | 1212 | 500 | `metal.stainless` `metal.aluminum-brushed` `emissive.signal-orange` `plastic.clear` | realityhd | Tabletop map marker pin: brushed metal shaft, collar and a glowing emissive head knob. | ## Structures @@ -1432,6 +1476,33 @@ Live with `RealityHD.articulated(id)` or `scene.addLive`; bake one state with `b | [`quick-disconnect`](docs/assets/quick-disconnect.png) | uncoupled, coupled | `sleeve` 0...0.008 m | 1480-1480 | | [`immersion-tank`](docs/assets/immersion-tank.png) | closed, open | `tank-lid` 0...100° | 11046-11046 | | [`switch-tray-cpo`](docs/assets/switch-tray-cpo.png) | closed, open | `tray-slide` 0...0.35 m | 7504-7504 | +| [`euv-scanner-highna`](docs/assets/euv-scanner-highna.png) | closed, covers-off, doors-open, stage-exposed | `cover-hood` 0...2.2 m, `cover-source` 0...1.6 m, `cover-handler` 0...1.2 m, `door-service-a` -110...0°, `door-handler-l` -110...0°, `door-handler-r` 0...110°, `wafer-stage` -0.8...0.8 m | 14820-14820 | +| [`litho-track`](docs/assets/litho-track.png) | closed, windows-open, panels-off | `panel-end-l` 0...0.4 m, `panel-end-r` 0...0.4 m, `panel-roof-a` 0...0.5 m, `panel-roof-b` 0...0.5 m, `panel-window-front-l` -110...0°, `panel-window-back-1` 0...110°, `panel-window-back-2` 0...110°, `panel-window-back-3` 0...110° | 13254-13254 | +| [`plasma-etch-cluster`](docs/assets/plasma-etch-cluster.png) | closed, lids-open | `lid-1` -105...0°, `lid-2` -105...0°, `lid-3` -105...0°, `lid-4` -105...0° | 13588-13588 | +| [`cvd-ald-cluster`](docs/assets/cvd-ald-cluster.png) | closed, lids-open | `lid-1` -105...0°, `lid-2` -105...0°, `lid-3` -105...0°, `lid-4` -105...0° | 13676-13676 | +| [`pvd-cluster`](docs/assets/pvd-cluster.png) | closed, lids-open | `lid-1` -105...0°, `lid-2` -105...0°, `lid-3` -105...0°, `lid-4` -105...0° | 13828-13828 | +| [`cmp-tool`](docs/assets/cmp-tool.png) | idle, spin | `platen-1` -360...360°, `platen-2` -360...360°, `platen-3` -360...360° | 11094-11094 | +| [`ion-implanter`](docs/assets/ion-implanter.png) | closed, panels-open | `panel-1` -110...0°, `panel-2` -110...0°, `panel-3` -110...0° | 5871-5871 | +| [`diffusion-furnace`](docs/assets/diffusion-furnace.png) | loaded, raised | `boat-elevator` 0...1 m | 9526-9526 | +| [`wet-bench`](docs/assets/wet-bench.png) | closed, open | `sash` 0...0.75 m | 4334-4334 | +| [`ebeam-metrology`](docs/assets/ebeam-metrology.png) | closed, open | `door` -110...0° | 7506-7506 | +| [`probe-station`](docs/assets/probe-station.png) | down, lifted | `probe-card-lift` 0...0.12 m | 6616-6616 | +| [`dicing-saw`](docs/assets/dicing-saw.png) | closed, open | `lid` -100...0° | 6134-6134 | +| [`hybrid-bonder`](docs/assets/hybrid-bonder.png) | closed, open | `door` -110...0° | 7044-7044 | +| [`foup-stocker`](docs/assets/foup-stocker.png) | idle, fetch | `robot-carriage` -2.6...2.6 m, `robot-lift` -1.4...1.2 m, `robot-arm` 0...0.5 m | 10356-10356 | +| [`efem-robot`](docs/assets/efem-robot.png) | idle, reach | `column-lift` 0...0.2 m, `shoulder` -150...150°, `elbow` -150...150°, `wrist` -170...170° | 3484-3484 | +| [`euv-light-source`](docs/assets/euv-light-source.png) | closed, cutaway | `cutaway-cover` 0...1.8 m | 13488-13488 | +| [`duv-immersion-scanner`](docs/assets/duv-immersion-scanner.png) | closed, covers-off, service | `cover-top` 0...1.2 m, `cover-side` 0...1 m, `cover-handler` 0...1 m, `door-service` -110...0° | 4974-4974 | +| [`chip-package-final`](docs/assets/chip-package-final.png) | lid-off, assembled, exploded | `balls` 0...0.03 m, `interposer` 0...0.03 m, `dies` 0...0.04 m, `lift` 0...0.07 m, `lid` -120...0° | 12164-12164 | +| [`hbm-stack`](docs/assets/hbm-stack.png) | assembled, exploded | `base-die` 0...0.002 m, `dram-01` 0...0.019 m, `dram-02` 0...0.019 m, `dram-03` 0...0.019 m, `dram-04` 0...0.019 m, `dram-05` 0...0.019 m, `dram-06` 0...0.019 m, `dram-07` 0...0.019 m, `dram-08` 0...0.019 m, `dram-09` 0...0.019 m, `dram-10` 0...0.019 m, `dram-11` 0...0.019 m, `dram-12` 0...0.019 m, `dram-13` 0...0.019 m, `dram-14` 0...0.019 m, `dram-15` 0...0.019 m, `dram-16` 0...0.019 m, `mold-left` 0...0.006 m, `mold-right` 0...0.006 m | 9512-9512 | +| [`die-blank-reticle`](docs/assets/die-blank-reticle.png) | assembled | | 1888-1888 | +| [`die-block-tile`](docs/assets/die-block-tile.png) | assembled | | 588-588 | +| [`design-workbench-2035`](docs/assets/design-workbench-2035.png) | assembled | | 6734-6734 | +| [`transistor-gaa-cutaway`](docs/assets/transistor-gaa-cutaway.png) | assembled, exploded | `rail` 0...0.12 m, `substrate` 0...0.06 m, `nanosheet-1` 0...0.06 m, `nanosheet-2` 0...0.12 m, `nanosheet-3` 0...0.18 m, `gate-oxide` 0...0.3 m, `gate-metal` 0...0.5 m, `spacers` 0...0.4 m, `epi-source` 0...0.2 m, `epi-drain` 0...0.2 m, `contacts` 0...0.7 m | 5696-5696 | +| [`interconnect-stack-cutaway`](docs/assets/interconnect-stack-cutaway.png) | assembled, exploded | `substrate` 0...0.15 m, `m01` 0...0.045 m, `m02` 0...0.09 m, `m03` 0...0.14 m, `m04` 0...0.18 m, `m05` 0...0.23 m, `m06` 0...0.27 m, `m07` 0...0.31 m, `m08` 0...0.36 m, `m09` 0...0.41 m, `m10` 0...0.45 m, `m11` 0...0.5 m, `m12` 0...0.54 m | 3444-3444 | +| [`space-radiator-panel`](docs/assets/space-radiator-panel.png) | deployed, half, stowed, deployed-hot | `deploy` 0...90° | 2840-2840 | +| [`solar-array-wing`](docs/assets/solar-array-wing.png) | deployed, half, stowed | `deploy` 0...90° | 3000-3000 | +| [`ground-station-dish`](docs/assets/ground-station-dish.png) | track, park, horizon | `dish-slew` -120...120°, `dish-elevate` -35...50° | 11270-11270 | | [`office-door`](docs/assets/office-door.png) | closed, ajar, open, unlatched | `leaf` -100...0°, `handle` -45...0° | 12760-12760 | | [`glass-door`](docs/assets/glass-door.png) | closed, ajar, open, open-in | `leaf` -95...95° | 6228-6228 | | [`elevator-doors`](docs/assets/elevator-doors.png) | closed, open, called, arriving | `left` 0...0.55 m | 8152-8152 | @@ -2421,7 +2492,57 @@ Keys accept a hex tint suffix, e.g. `metal.painted:1F4E8C`. | `plastic.jacket` | plastic | 0.05 | 256 | opaque | - | | | `fluid.dielectric` | scalar | 1 | - | transparent | - | | | `glass.fiber` | plastic | 0.02 | 256 | opaque | - | | +| `plastic.polycarbonate-tint` | scalar | 1 | - | transparent | - | | +| `glass.window-tint` | scalar | 1 | - | transparent | - | | +| `silicon.thinfilm` | polishedMetal | 0.3 | 1024 | opaque | - | metallic map | +| `silicon.thinfilm-oxide` | polishedMetal | 0.3 | 1024 | opaque | - | metallic map | +| `silicon.thinfilm-patterned` | polishedMetal | 0.1 | 1024 | opaque | - | metallic map | +| `silicon.thinfilm-metal` | polishedMetal | 0.3 | 1024 | opaque | - | metallic map | +| `silicon.die-a` | polishedMetal | 0.026 | 1024 | opaque | - | metallic map | +| `silicon.die-b` | polishedMetal | 0.026 | 1024 | opaque | - | metallic map | +| `silicon.die-c` | polishedMetal | 0.026 | 1024 | opaque | - | metallic map | +| `silicon.die-d` | polishedMetal | 0.026 | 1024 | opaque | - | metallic map | +| `silicon.bare` | polishedMetal | 0.3 | 1024 | opaque | - | metallic map | +| `silicon.mark` | polishedMetal | 0.05 | 1024 | opaque | - | metallic map | +| `plastic.pad` | plastic | 0.12 | 512 | opaque | - | | +| `quartz` | scalar | 1 | - | transparent | - | | +| `plastic.pvdf` | plastic | 0.3 | 512 | opaque | - | | +| `filter.hepa` | plastic | 0.1 | 512 | opaque | - | | +| `plastic.translucent` | scalar | 1 | - | transparent | - | | +| `emissive.amber` | scalar | 1 | - | emissive | - | | +| `silicon.die` | brushedMetal | 0.01 | 1024 | opaque | - | metallic map | +| `silicon.die-edge` | brushedMetal | 0.01 | 1024 | opaque | - | metallic map | +| `laminate.abf` | plastic | 0.02 | 512 | opaque | - | | +| `metal.gold` | polishedMetal | 0.02 | 1024 | opaque | - | metallic map | +| `metal.nickel` | brushedMetal | 0.03 | 1024 | opaque | - | metallic map | +| `metal.solder` | brushedMetal | 0.005 | 1024 | opaque | - | metallic map | +| `plastic.underfill` | plastic | 0.01 | 512 | opaque | - | | +| `glass.substrate` | brushedMetal | 0.01 | 1024 | opaque | - | metallic map | +| `glass.substrate-clear` | scalar | 1 | - | transparent | - | | +| `glass.display` | scalar | 1 | - | opaque | - | | +| `emissive.holo-grid` | scalar | 1 | - | emissive | - | | +| `emissive.die-tensor` | scalar | 1 | - | emissive | - | | +| `emissive.die-sram` | scalar | 1 | - | emissive | - | | +| `emissive.die-noc` | scalar | 1 | - | emissive | - | | +| `emissive.die-hbmphy` | scalar | 1 | - | emissive | - | | +| `emissive.die-io` | scalar | 1 | - | emissive | - | | +| `emissive.holo-plane` | scalar | 1 | - | emissive | - | | +| `oxide.hafnium` | brushedMetal | 0.01 | 1024 | opaque | - | metallic map | +| `metal.tungsten` | brushedMetal | 0.01 | 1024 | opaque | - | metallic map | +| `oxide.lowk` | plastic | 0.02 | 512 | opaque | - | | +| `silicon.thinfilm` | brushedMetal | 0.02 | 1024 | opaque | - | metallic map | +| `plastic.acrylic` | scalar | 1 | - | transparent | - | | +| `thermal.radiator-white` | paintedMetal | 0.6 | 1024 | opaque | - | metallic map | +| `thermal.radiator-glow` | scalar | 1 | - | emissive | - | | +| `mli.blanket` | foilCrinkle | 0.6 | 1024 | opaque | - | metallic map | +| `mli.silver` | foilCrinkle | 0.5 | 1024 | opaque | - | metallic map | +| `solar.cell` | solarCell | 0.64 | 1024 | opaque | - | metallic map | +| `solar.kapton` | plastic | 0.3 | 512 | opaque | - | | +| `regolith.lunar` | gravel | 1.2 | 2048 | opaque | - | anti-tile | +| `regolith.lunar-fine` | sand | 1.5 | 2048 | opaque | - | anti-tile | +| `dust.mars` | sand | 2.2 | 2048 | opaque | - | anti-tile | +| `dust.mars-film` | sand | 0.8 | 1024 | opaque | - | | ## Texture programs -`barkOak` `barkBirch` `barkPine` `leafBroad` `leafNeedle` `grassBlades` `rockGranite` `forestFloor` `woodPlank` `paintedMetal` `rustMetal` `concrete` `asphalt` `plastic` `brick` `grassBlade` `grassCard` `flowers` `fernFrond` `leafLitter` `moss` `fungus` `plantStem` `barkSmooth` `barkAspen` `barkDead` `leafPalmate` `leafLanceolate` `plywood` `galvanized` `straw` `paintedWood` `jute` `gravel` `sand` `mud` `snow` `cobblestone` `dirtPath` `barkScotsPine` `leafPine` `leafFir` `leafCypress` `barkPalm` `leafPalm` `cactusRibs` `leafAgave` `strataRock` `rockSlate` `rockRiver` `fabricWeave` `woodEndGrain` `charcoal` `water` `leather` `brushedMetal` `polishedMetal` `ceramicGlaze` `caneWeave` `carpetTile` `carpetPile` `acousticTile` `paintedWall` `woodVeneer` `screenUI` `pageEdge` `marble` `terrazzo` `pavers` `sansevieria` `laminate` `chairMesh` `pottingSoil` `turf` `infieldClay` `chainLink` `sheetVinyl` `nonwoven` `wallTile` `vitalsUI` `medLabel` `boneCortical` `cartilageHyaline` `muscleFiber` `tendonFiber` `vesselWall` `nerveFascicle` `pegboard` `tapeRule` `pantryFill` `subwayTile` `plankFloor` `gasFlame` `quartzSlab` `fruitSkin` `papery` `rootSkin` `radialFlesh` `poultryFlesh` `foodSmooth` `foodCrumb` `butcherBlock` `seasonedIron` `heatTint` `bakedPatina` `paverHerringbone` `barkMulch` `foodTissue` `chitin` `insectFur` `compoundEye` `insectWing` `insectVeins` `caterpillarBands` `petal` `plumageBody` `plumageHead` `plumageFeather` `furCoat` `quillCoat` `carbonWeave` `foilCrinkle` `barkSycamore` `fishSkin` `fishFin` `fishEye` `perforatedSheet` +`barkOak` `barkBirch` `barkPine` `leafBroad` `leafNeedle` `grassBlades` `rockGranite` `forestFloor` `woodPlank` `paintedMetal` `rustMetal` `concrete` `asphalt` `plastic` `brick` `grassBlade` `grassCard` `flowers` `fernFrond` `leafLitter` `moss` `fungus` `plantStem` `barkSmooth` `barkAspen` `barkDead` `leafPalmate` `leafLanceolate` `plywood` `galvanized` `straw` `paintedWood` `jute` `gravel` `sand` `mud` `snow` `cobblestone` `dirtPath` `barkScotsPine` `leafPine` `leafFir` `leafCypress` `barkPalm` `leafPalm` `cactusRibs` `leafAgave` `strataRock` `rockSlate` `rockRiver` `fabricWeave` `woodEndGrain` `charcoal` `water` `leather` `brushedMetal` `polishedMetal` `ceramicGlaze` `caneWeave` `carpetTile` `carpetPile` `acousticTile` `paintedWall` `woodVeneer` `screenUI` `pageEdge` `marble` `terrazzo` `pavers` `sansevieria` `laminate` `chairMesh` `pottingSoil` `turf` `infieldClay` `chainLink` `sheetVinyl` `nonwoven` `wallTile` `vitalsUI` `medLabel` `boneCortical` `cartilageHyaline` `muscleFiber` `tendonFiber` `vesselWall` `nerveFascicle` `pegboard` `tapeRule` `pantryFill` `subwayTile` `plankFloor` `gasFlame` `quartzSlab` `fruitSkin` `papery` `rootSkin` `radialFlesh` `poultryFlesh` `foodSmooth` `foodCrumb` `butcherBlock` `seasonedIron` `heatTint` `bakedPatina` `paverHerringbone` `barkMulch` `foodTissue` `chitin` `insectFur` `compoundEye` `insectWing` `insectVeins` `caterpillarBands` `petal` `plumageBody` `plumageHead` `plumageFeather` `furCoat` `quillCoat` `carbonWeave` `foilCrinkle` `barkSycamore` `fishSkin` `fishFin` `fishEye` `perforatedSheet` `solarCell` diff --git a/Sources/RealLibrary/Props/ChipLab/BondWireMacro.swift b/Sources/RealLibrary/Props/ChipLab/BondWireMacro.swift new file mode 100644 index 00000000..81129745 --- /dev/null +++ b/Sources/RealLibrary/Props/ChipLab/BondWireMacro.swift @@ -0,0 +1,82 @@ +import simd +import Foundation + +/// Bond wire macro display, about 160x scale, 0.4 x 0.1 x 0.2 m: black plinth, silicon die with aluminium pad rows on the +/// right edge, ABF substrate with gold finger rows, and parametric gold wire loops with squashed ball bonds (neck) on the +/// die pads and wedge bonds (foot and tail) on the fingers. Alternating wires use the far pad and finger row. +public struct BondWireMacro: RealAsset { + public static let id = "bond-wire-macro" + public static let summary = "Parametric gold bond wire loops between die pads and substrate fingers with ball and wedge bonds at macro scale." + public static let tags = ["prop", "chiplab"] + public static let budget = 10500 + public static let author = "realityhd" + public static let preview = PreviewHint(azimuth: 20, elevation: 25, distance: 0.8, studio: true) + + /// Overall size in meters. + public var size = V3(0.4, 0.1, 0.2) + /// Wire count. + public var wires = 8 + /// Wire radius (m). + public var wireRadius: Float = 0.0020 + /// Loop height above the pads (m). + public var loopHeight: Float = 0.050 + public var wire: MaterialKey = "metal.gold" + public var pad: MaterialKey = "metal.aluminum-brushed" + public var die: MaterialKey = "silicon.die" + public var dieEdge: MaterialKey = "silicon.die-edge" + public var substrate: MaterialKey = "laminate.abf" + public var plinth: MaterialKey = "metal.anodized-black" + public init() {} + + func flat(_ w: Float, _ d: Float, _ t: Float, _ mat: MaterialKey) -> Surface { + Prim.extrude(Shape2D.rect(w, d), depth: t, bevel: 0.0003, bevelSegments: 1, material: mat) + } + + public func build(seed: UInt64) -> LODModel { + var rng = SeededRNG(seed: seed) + var m = Model(name: Self.id) + let W = size.x, D = size.z + let pl: Float = 0.012 + m.add(Prim.roundedBox(V3(W, pl, D), radius: 0.003, bevelSegments: 2, material: plinth), Xform(translation: V3(0, pl / 2, 0))) + let dieT: Float = 0.016, subT: Float = 0.008 + let dieX0: Float = -W / 2 + 0.012, dieX1: Float = -0.045 + m.add(Prim.roundedBox(V3(dieX1 - dieX0, dieT, D - 0.03), radius: 0.0012, bevelSegments: 2, material: dieEdge), + Xform(translation: V3((dieX0 + dieX1) / 2, pl + dieT / 2, 0))) + let dTop = pl + dieT + m.add(flat(dieX1 - dieX0 - 0.002, D - 0.032, 0.0005, die), Xform(translation: V3((dieX0 + dieX1) / 2, dTop + 0.00025, 0), rotation: simd_quatf(degrees: 90, axis: V3(1, 0, 0)))) + let subX0: Float = -0.02, subX1: Float = W / 2 - 0.012 + m.add(Prim.roundedBox(V3(subX1 - subX0, subT, D - 0.03), radius: 0.0012, bevelSegments: 2, material: substrate), + Xform(translation: V3((subX0 + subX1) / 2, pl + subT / 2, 0))) + let sTop = pl + subT + let pitch = (D - 0.07) / Float(wires - 1) + let xr = simd_quatf(degrees: 90, axis: V3(1, 0, 0)) + for i in 0.. Surface { + r < 0.0001 ? cuboid(s, material: mat) : Prim.roundedBox(s, radius: r, bevelSegments: 1, material: mat) + } + + public func rig(seed: UInt64) -> Rig { + var rng = SeededRNG(seed: seed) + var rig = Rig(name: Self.id, lods: 1) + let W = edge + let ballH: Float = 0.0016, subT: Float = 0.0016, ipT: Float = 0.0004 + let subBot = ballH - 0.0002, subTop = subBot + subT // 0.0014 ... 0.0030 + let ipTop = subTop + ipT + let dieH: Float = 0.0007, hbmH: Float = 0.0008 + let lidWall: Float = 0.002, lidOuter: Float = 0.084, lidT: Float = 0.0012 + let lidTop: Float = 0.006, lidBot = lidTop - lidT + + // ---- base: substrate, caps, pads + var base = Model(name: Self.id) + base.add(box(V3(W, subT, W), 0.0002, substrateMaterial), Xform(translation: V3(0, subBot + subT / 2, 0))) + // Solder-mask-edge copper-clad strip along the underside rim. + base.add(box(V3(W - 0.001, 0.00012, W - 0.001), 0.00003, "metal.copper"), Xform(translation: V3(0, subBot + 0.00006 - 0.00002, 0))) + // Gold bond-pad rows outside the lid footprint. + for k in 0..<2 { + let o = W / 2 - 0.0016 - Float(k) * 0.0018 + base.add(box(V3(W - 0.012, 0.00012, 0.0009), 0.00002, "metal.gold"), Xform(translation: V3(0, subTop + 0.00004, o))) + base.add(box(V3(W - 0.012, 0.00012, 0.0009), 0.00002, "metal.gold"), Xform(translation: V3(0, subTop + 0.00004, -o))) + } + // Edge decoupling capacitors (0402 class): ceramic body with solder terminals. + func cap(_ x: Float, _ z: Float, _ rot: Float) { + let q = simd_quatf(degrees: rot, axis: V3(0, 1, 0)) + base.add(box(V3(0.0011, 0.0005, 0.0006), 0.00004, "plastic.underfill"), Xform(translation: V3(x, subTop + 0.00025, z), rotation: q)) + for s in [Float(-1), 1] { + let d = q.act(V3(s * 0.00046, 0, 0)) + base.add(box(V3(0.00022, 0.00052, 0.00062), 0.00004, "metal.solder"), Xform(translation: V3(x + d.x, subTop + 0.00026, z + d.z), rotation: q)) + } + } + // Outside the lid: two rows on each side of the package. + for k in 0..<14 { + let t = (Float(k) - 6.5) * 0.0042 + rng.float(-0.0001...0.0001) + let o = W / 2 - 0.0058 + for row in 0..<1 { + _ = row + cap(t, o, 0); cap(t, -o, 0); cap(o, t, 90); cap(-o, t, 90) + } + } + // Inside the lid: rows between interposer and lid wall (visible with the lid off). + for k in 0..<12 { + let t = (Float(k) - 5.5) * 0.0042 + cap(t, 0.0305, 0); cap(t, -0.0305, 0) + } + for k in 0..<4 { + let t = (Float(k) - 1.5) * 0.0042 + cap(0.0408, t, 90); cap(-0.0408, t, 90) + } + // Fiducials and pin-1 triangle. + for (sx, sz) in [(Float(-1), Float(-1)), (1, -1), (1, 1)] { + base.add(Prim.lathe([V2(0.0007, 0), V2(0.0007, 0.00012), V2(0, 0.00012)], segments: 10, seamTile: 1, material: "metal.gold"), + Xform(translation: V3(sx * (W / 2 - 0.0018), subTop, sz * (W / 2 - 0.0018)))) + } + base.add(Prim.extrude([V2(0, 0), V2(0.003, 0), V2(0, 0.003)], depth: 0.00012, bevel: 0.00002, bevelSegments: 1, material: "metal.gold"), + Xform(translation: V3(-W / 2 + 0.0012, subTop + 0.00006, W / 2 - 0.0012), rotation: simd_quatf(degrees: -90, axis: V3(1, 0, 0)))) + rig.base[0] = base + + // ---- balls (BGA): own part so the grid can drop away. + _ = rig.part("balls", pivot: V3(0, 0, 0), joint: .slide(axis: V3(0, -1, 0), 0...0.03, duration: 0.8)) + let pitch: Float = 0.0092 + for i in 0.. Surface { + let a = asin(chord / 2 / radius), n = 7 + var pts: [V2] = [] + for i in 0...n { let th = -a + 2 * a * Float(i) / Float(n); pts.append(V2((radius + thick / 2) * sin(th), radius - (radius + thick / 2) * cos(th))) } + for i in (0...n).reversed() { let th = -a + 2 * a * Float(i) / Float(n); pts.append(V2((radius - thick / 2) * sin(th), radius - (radius - thick / 2) * cos(th))) } + return Prim.extrude(pts, depth: h, bevel: bevel, bevelSegments: 1, material: mat) + } + + func place(_ x: Float, _ y: Float, _ z: Float, yaw: Float, h: Float) -> Xform { + Xform(translation: V3(x, y + h / 2, z), rotation: simd_quatf(angle: yaw, axis: V3(0, 1, 0)) * simd_quatf(degrees: 90, axis: V3(1, 0, 0))) + } + + public func rig(seed: UInt64) -> Rig { + var rng = SeededRNG(seed: seed) + var rig = Rig(name: Self.id, lods: 1) + var m = Model(name: Self.id) + let W = size.x, D = size.z, dh = deskHeight, topT: Float = 0.04 + let y0 = dh - topT + // Laminate top with rounded corners, aluminum edge lip underneath. + m.add(Prim.extrude(Shape2D.roundedRect(W, D, radius: 0.06), depth: topT, bevel: 0.004, bevelSegments: 1, material: top), + Xform(translation: V3(0, y0 + topT / 2, 0), rotation: simd_quatf(degrees: 90, axis: V3(1, 0, 0)))) + m.add(Prim.extrude(Shape2D.roundedRect(W - 0.02, D - 0.02, radius: 0.055), depth: 0.006, bevel: 0.001, bevelSegments: 1, material: trim), + Xform(translation: V3(0, y0 - 0.003, 0), rotation: simd_quatf(degrees: 90, axis: V3(1, 0, 0)))) + // Frame: end plates, foot bars, leveling feet, rails and rear cross beam. + for sx: Float in [-1, 1] { + let x = sx * (W / 2 - 0.16) + box(&m, V3(0.05, y0 - 0.05 - 0.01, D - 0.3), 0.012, V3(x, 0.05 + (y0 - 0.06) / 2, 0), frame) + box(&m, V3(0.09, 0.04, D - 0.1), 0.012, V3(x, 0.03 + 0.01, 0), frame) + for sz: Float in [-1, 1] { + m.add(Prim.cylinder(radius: 0.025, height: 0.02, bevel: 0.004, segments: 12, material: foot), Xform(translation: V3(x, 0, sz * (D / 2 - 0.15)))) + } + // Machined reveal on the end plate. + box(&m, V3(0.006, y0 - 0.3, 0.03), 0.002, V3(x + sx * 0.026, 0.05 + (y0 - 0.06) / 2, 0), trim) + } + box(&m, V3(W - 0.4, 0.06, 0.04), 0.01, V3(0, y0 - 0.05, -D / 2 + 0.16), frame) + box(&m, V3(W - 0.4, 0.04, 0.04), 0.01, V3(0, y0 - 0.04, D / 2 - 0.16), frame) + box(&m, V3(0.04, 0.04, D - 0.36), 0.01, V3(0, y0 - 0.04, 0), frame) + // Keyboard deck: palm rest, base, keycaps in rows, scroll dial, status LED strip on the front edge. + let kz: Float = 0.34 + box(&m, V3(0.56, 0.012, 0.3), 0.004, V3(0, dh + 0.006, kz + 0.03), frame) + box(&m, V3(0.46, 0.014, 0.15), 0.004, V3(0, dh + 0.012, kz - 0.04), frame) + let cols = 12, rows = 4, pitch: Float = 0.0345 + let capGeo = Prim.extrude(Shape2D.rect(0.028, 0.028), depth: 0.008, bevel: 0.0012, bevelSegments: 1, material: keys) + for r in 0.. 3 && c < 8 { continue } + m.add(capGeo, Xform(translation: V3(x, dh + 0.023, z), rotation: simd_quatf(degrees: 90, axis: V3(1, 0, 0)))) + } } + m.add(Prim.extrude(Shape2D.rect(0.14, 0.028), depth: 0.008, bevel: 0.0012, bevelSegments: 1, material: keys), + Xform(translation: V3(0, dh + 0.023, kz - 0.095 + 3 * pitch + 0.004), rotation: simd_quatf(degrees: 90, axis: V3(1, 0, 0)))) + m.add(Prim.cylinder(radius: 0.03, height: 0.022, bevel: 0.003, segments: 20, material: trim), Xform(translation: V3(0.38, dh, kz - 0.02))) + m.add(Prim.extrude(Shape2D.rect(0.5, 0.004), depth: 0.002, bevel: 0, bevelSegments: 1, material: "emissive.holo-grid"), + Xform(translation: V3(0, dh - 0.02, D / 2 - 0.014), rotation: simd_quatf(degrees: 90, axis: V3(1, 0, 0)))) + // Desk mat under the keyboard deck, slightly worn rubber. + box(&m, V3(1.0, 0.004, 0.42), 0.002, V3(0.05, dh + 0.002, kz + 0.02), "rubber") + // Story detail: desk-mounted stylus lying on the laminate. + m.add(Prim.cylinder(radius: 0.004, height: 0.14, bevel: 0.002, segments: 8, material: trim), + Xform(translation: V3(-0.5, dh + 0.004, 0.38), rotation: simd_quatf(degrees: 90, axis: V3(0, 0, 1)) * simd_quatf(degrees: 20, axis: V3(1, 0, 0)))) + rig.base[0] = m + + // Displays: glass shell plus emissive screen band, slim stands, fanned on an arc behind the keyboard. + let specs: [(String, Float, Float, Float)] = [("display-left", -0.66, -0.2, 0.42), ("display-center", 0, -0.3, 0), ("display-right", 0.66, -0.2, -0.42)] + let dispH: Float = 0.26, dispY: Float = dh + 0.015 + for (name, x, z, yaw) in specs { + _ = rig.part(name, pivot: V3(x, dispY, z), joint: .fixed) + rig.add(arc(0.64, 1.3, 0.012, dispH, bevel: 0.002, glass), place(x, dispY, z, yaw: yaw, h: dispH), to: name) + rig.add(arc(0.62, 1.3, 0.0016, dispH - 0.024, bevel: 0, screen), place(x, dispY + 0.012, z + 0.0065, yaw: yaw, h: dispH - 0.024), to: name) + let stand = V3(x - 0.0, 0, z - 0.04) + rig.add(Prim.roundedBox(V3(0.18, 0.008, 0.12), radius: 0.003, bevelSegments: 1, material: frame), Xform(translation: V3(x, dh + 0.004, z - 0.02)), to: name) + rig.add(Prim.roundedBox(V3(0.04, 0.035, 0.012), radius: 0.003, bevelSegments: 1, material: frame), Xform(translation: V3(stand.x, dh + 0.024, stand.z + 0.015)), to: name) + } + // Holo plane: floating translucent emissive sheet with a bright frame line, right of the keyboard. + _ = rig.part("holo-plane", pivot: V3(0.85, 0.86, 0.2), joint: .fixed) + let hq = simd_quatf(angle: -0.6, axis: V3(0, 1, 0)) * simd_quatf(degrees: -8, axis: V3(1, 0, 0)) + rig.add(Prim.extrude(Shape2D.roundedRect(0.42, 0.2, radius: 0.01), depth: 0.0015, bevel: 0, bevelSegments: 1, material: holo), Xform(translation: V3(0.85, 0.86, 0.2), rotation: hq), to: "holo-plane") + rig.add(Prim.extrude(Shape2D.roundedRect(0.3, 0.012, radius: 0.004), depth: 0.002, bevel: 0, bevelSegments: 1, material: "emissive.holo-grid"), + Xform(translation: V3(0.85, 0.89, 0.2) + hq.act(V3(0, 0, 0.002)), rotation: hq), to: "holo-plane") + // Cable spine: trunk under the rear rail, riser along the right end plate and three drooping cables to the displays. + _ = rig.part("cable-spine", pivot: V3(0, y0 - 0.1, -D / 2 + 0.12), joint: .fixed) + rig.add(Prim.roundedBox(V3(W - 0.5, 0.05, 0.07), radius: 0.015, bevelSegments: 2, material: frame), Xform(translation: V3(0, y0 - 0.1, -D / 2 + 0.12)), to: "cable-spine") + rig.add(Prim.roundedBox(V3(0.07, y0 - 0.12, 0.07), radius: 0.015, bevelSegments: 2, material: frame), Xform(translation: V3(W / 2 - 0.3, (y0 - 0.1) / 2 + 0.04, -D / 2 + 0.12)), to: "cable-spine") + for (i, x) in [Float(-0.66), 0, 0.66].enumerated() { + let sag = rng.float(0.02...0.05) + let path = catmull([V3(x, dh + 0.04, -0.3 - 0.04), V3(x, dh + 0.01, -0.36), V3(x + 0.03 * Float(i - 1), dh - 0.02 - sag, -0.5), V3(x + 0.03 * Float(i - 1), y0 - 0.1, -D / 2 + 0.14)], per: 5) + rig.add(Prim.tube(path, radii: path.map { _ in 0.006 }, sides: 6, seamTile: 0.05, material: "rubber", capEnd: true), to: "cable-spine") + } + rig.states = [RigState("assembled")] + return rig + } +} diff --git a/Sources/RealLibrary/Props/ChipLab/DieBlankReticle.swift b/Sources/RealLibrary/Props/ChipLab/DieBlankReticle.swift new file mode 100644 index 00000000..c66fb215 --- /dev/null +++ b/Sources/RealLibrary/Props/ChipLab/DieBlankReticle.swift @@ -0,0 +1,122 @@ +import simd +import Foundation + +/// Blank reticle-limit die, 26 x 33 mm, 0.8 mm thick: mirror-polished silicon backside (underside), chamfered +/// diced edges, front face with faint violet and teal metal-layer interference bands, seal ring inset from the +/// edge, scribe-line remnant with test-pad dashes, C4 solder bump field and a 3 x 4 grid of block slot pads. +/// Parts: slot-A1 ... slot-C4 (named sockets for die-block-tile, fixed joints, columns A-C, rows 1-4). +public struct DieBlankReticle: RealArticulated { + public static let id = "die-blank-reticle" + public static let summary = "Blank reticle-limit die: mirror silicon backside, interference-sheen front, seal ring, scribe remnant, C4 bump field, named block slot sockets." + public static let tags = ["prop", "chiplab", "electronics", "articulated"] + public static let budget = 2000 + public static let author = "realityhd" + public static let preview = PreviewHint(azimuth: 28, elevation: 38, distance: 1.0, studio: true) + + /// Die footprint (x width, z depth) in meters. + public var footprint = V2(0.026, 0.033) + /// Die thickness in meters. + public var thickness: Float = 0.00074 + /// Block slot size (m) and pitch (m) for the 3 x 4 grid. + public var slotSize = V2(0.0062, 0.0062) + public var slotPitch = V2(0.0074, 0.0076) + /// C4 bump grid pitch (m) and bump radius (m). + public var bumpPitch: Float = 0.0023 + public var bumpRadius: Float = 0.00014 + public var edge: MaterialKey = "silicon.die-edge" + public var mirror: MaterialKey = "silicon.die" + public var face: MaterialKey = "silicon.die:343A52" + public var sealMaterial: MaterialKey = "metal.nickel" + public var bump: MaterialKey = "metal.solder" + public init() {} + + func flat(_ w: Float, _ d: Float, _ t: Float, _ mat: MaterialKey) -> Surface { + Prim.extrude(Shape2D.rect(w, d), depth: t, bevel: 0, bevelSegments: 1, material: mat) + } + + /// Flat plate lying in XZ, bottom at y0, thickness t, centered at (cx, cz). + func plate(_ m: inout Model, _ w: Float, _ d: Float, y0: Float, t: Float, cx: Float = 0, cz: Float = 0, _ mat: MaterialKey) { + m.add(flat(w, d, t, mat), Xform(translation: V3(cx, y0 + t / 2, cz), rotation: simd_quatf(degrees: 90, axis: V3(1, 0, 0)))) + } + + public func rig(seed: UInt64) -> Rig { + var rng = SeededRNG(seed: seed) + var rig = Rig(name: Self.id, lods: 1) + let W = footprint.x, D = footprint.y, T = thickness + var m = Model(name: Self.id) + + // Body: chamfered diced silicon edge, mirror plate on the underside, passivated front face. + m.add(Prim.roundedBox(V3(W, T - 0.000005, D), radius: 0.00006, bevelSegments: 2, material: edge), + Xform(translation: V3(0, 0.000005 + (T - 0.000005) / 2, 0))) + plate(&m, W - 0.00020, D - 0.00020, y0: 0, t: 0.00001, mirror) + let top = T + plate(&m, W - 0.00024, D - 0.00024, y0: top - 0.000004, t: 0.000008, face) + + // Scribe-line remnant: 0.35 mm diced-edge band with test-structure dashes and a gold fiducial. + let sb: Float = 0.00035 + let seal = sb + 0.00012 + plate(&m, W - 0.00030, 0.00003, y0: top + 0.000002, t: 0.000004, cz: D / 2 - 0.00022, edge) + plate(&m, W - 0.00030, 0.00003, y0: top + 0.000002, t: 0.000004, cz: -D / 2 + 0.00022, edge) + for k in 0..<10 { + let x = (Float(k) - 4.5) * 0.0023 + let len = rng.float(0.0004...0.0009) + plate(&m, len, 0.00008, y0: top + 0.000002, t: 0.000005, cx: x, cz: D / 2 - 0.00022, k % 5 == 0 ? "metal.gold" : edge) + plate(&m, len, 0.00008, y0: top + 0.000002, t: 0.000005, cx: x + 0.0004, cz: -D / 2 + 0.00022, k % 4 == 0 ? "metal.gold" : edge) + } + + // Seal ring: closed nickel-colored band, 0.12 mm wide, raised 8 um, with a dark gap row outside. + let sw: Float = 0.00012, sy = top + 0.000002 + let ox = W - 2 * seal, oz = D - 2 * seal + plate(&m, ox, sw, y0: sy, t: 0.000008, cz: oz / 2 - sw / 2, sealMaterial) + plate(&m, ox, sw, y0: sy, t: 0.000008, cz: -oz / 2 + sw / 2, sealMaterial) + plate(&m, sw, oz - 2 * sw, y0: sy, t: 0.000008, cx: ox / 2 - sw / 2, sealMaterial) + plate(&m, sw, oz - 2 * sw, y0: sy, t: 0.000008, cx: -ox / 2 + sw / 2, sealMaterial) + + // Metal-layer interference sheen: three stepped passivation bands inside the ring, violet then teal then blue. + let inner = ox - 2 * sw - 0.0003 + plate(&m, inner, oz - 2 * sw - 0.0003, y0: sy, t: 0.000004, "silicon.die:3A4468") + plate(&m, inner - 0.0010, oz - 2 * sw - 0.0013, y0: sy + 0.000004, t: 0.000003, "silicon.die:334E66") + + // Slot pads (named sockets), alternating tint so the interference bands read as a field. + let cols = ["A", "B", "C"] + let tints = ["silicon.die:5A4A8C", "silicon.die:2F6A80", "silicon.die:3A5A9A"] + var slotCenters: [(Float, Float)] = [] + for (ci, c) in cols.enumerated() { + for r in 0..<4 { + let cx = (Float(ci) - 1) * slotPitch.x + let cz = (Float(r) - 1.5) * slotPitch.y + slotCenters.append((cx, cz)) + let name = "\(c)\(r + 1)" + _ = rig.part("slot-\(name)", pivot: V3(cx, top, cz), joint: .fixed) + let mat = tints[(ci + r) % 3] + rig.add(flat(slotSize.x, slotSize.y, 0.000008, mat), + Xform(translation: V3(cx, sy + 0.000008, cz), rotation: simd_quatf(degrees: 90, axis: V3(1, 0, 0))), to: "slot-\(name)") + // Gold alignment tick at the slot's pin-1 corner. + rig.add(flat(0.00045, 0.00045, 0.000006, "metal.gold"), + Xform(translation: V3(cx - slotSize.x / 2 + 0.0004, sy + 0.000015, cz - slotSize.y / 2 + 0.0004), rotation: simd_quatf(degrees: 90, axis: V3(1, 0, 0))), to: "slot-\(name)") + } + } + + // C4 bump field: global grid in the corridors and margins, skipping slot pads. + let h = bumpRadius * 0.8 + let bumpGeo = Prim.lathe([V2(bumpRadius, 0), V2(bumpRadius * 0.8, h * 0.7), V2(0, h)], segments: 5, seamTile: 1, material: bump) + var pts: [V2] = [] + let lenZ = 4 * slotPitch.y - 0.0004, lenX = 3 * slotPitch.x - 0.0004 + let nz = Int(lenZ / bumpPitch), nx = Int(lenX / bumpPitch) + for sx: Float in [-1, 1] { + for j in 0.. Rig { + var rng = SeededRNG(seed: seed) + var rig = Rig(name: Self.id, lods: 1) + let W = footprint.x, D = footprint.y, T = thickness + var m = Model(name: Self.id) + m.add(Prim.roundedBox(V3(W, T, D), radius: 0.00004, bevelSegments: 1, material: edge), Xform(translation: V3(0, T / 2, 0))) + // Polished front face and a keep-out border. + let fw = W - 0.00024, fd = D - 0.00024 + slab(&m, fw, fd, 0.000006, 0, 0, T, body) + let y = T + 0.000006, t: Float = 0.000005 + let iw = W - 0.0009, id = D - 0.0009 // active area + switch type { + case .tensor: + let n = 4, cw = iw / Float(n) - 0.00018, cd = id / Float(n) - 0.00018 + for r in 0.. Surface { + Prim.roundedBox(s, radius: r, bevelSegments: seg, material: mat) + } + + public func rig(seed: UInt64) -> Rig { + let rng = SeededRNG(seed: seed) + var rig = Rig(name: Self.id, lods: 1) + let W = footprint.x, D = footprint.y + let baseT: Float = 0.00014, dieT: Float = 0.00008, gap: Float = 0.00005, topT: Float = 0.00012 + let dW = W - 0.0014, dD = D - 0.0016 + let pitch = dieT + gap + let n = layers + + rig.base[0] = Model(name: Self.id) + let stackTop = baseT + Float(n - 1) * pitch + gap + topT + let spread = Float(n) * 0.0011 + 0.001 + + // Base logic die: edge body, polished face inset, pin-1 triangle, SoC-side copper pad rows at the margin. + _ = rig.part("base-die", pivot: V3(0, 0, 0), joint: .slide(axis: V3(0, -1, 0), 0...0.002, duration: 0.8)) + rig.add(box(V3(W, baseT, D), 0.000012, 2, dieEdge), Xform(translation: V3(0, baseT / 2, 0)), to: "base-die") + rig.add(box(V3(W - 0.00014, 0.000012, D - 0.00014), 0.000004, 1, dieFace), Xform(translation: V3(0, baseT + 0.000002, 0)), to: "base-die") + for sz: Float in [-1, 1] { + for k in 0..<14 { + let x = (Float(k) - 6.5) * 0.00070 + rig.add(Prim.lathe([V2(0.000085, 0), V2(0.000085, 0.00002)], segments: 6, seamTile: 1, material: bump), + Xform(translation: V3(x, baseT + 0.000008, sz * (D / 2 - 0.0003))), to: "base-die") + } + } + rig.add(Prim.extrude([V2(0, 0), V2(0.0004, 0), V2(0, 0.0004)], depth: 0.000012, bevel: 0.000002, bevelSegments: 1, material: bump), + Xform(translation: V3(-W / 2 + 0.0003, baseT + 0.000012, D / 2 - 0.0007), rotation: simd_quatf(degrees: -90, axis: V3(1, 0, 0))), to: "base-die") + + // DRAM dies: each part carries its die, the fillet and microbumps in the bond line under it. + for i in 0.. LODModel { + var rng = SeededRNG(seed: seed) + var m = Model(name: Self.id) + let W = size.x, H = size.y, D = size.z + let footH: Float = 0.05, baseT: Float = 0.04, topT: Float = 0.075, glassT: Float = 0.012 + let topY = H - topT + // Leveling feet: threaded stem, rubber pad, under the base plate. + let baseW = W - 0.5, baseD = D - 0.3 + for sx: Float in [-1, 1] { for sz: Float in [-1, 1] { + let x = sx * (baseW / 2 - 0.08), z = sz * (baseD / 2 - 0.08) + m.add(Prim.cylinder(radius: 0.045, height: 0.02, bevel: 0.006, segments: 16, material: foot), Xform(translation: V3(x, 0, z))) + m.add(Prim.cylinder(radius: 0.011, height: 0.03, bevel: 0.002, segments: 12, material: "metal.chrome"), Xform(translation: V3(x, 0.02, z))) + m.add(Prim.cylinder(radius: 0.022, height: 0.008, bevel: 0.002, segments: 6, material: trim), Xform(translation: V3(x, 0.045, z))) + } } + // Base plate with machined step. + box(&m, V3(baseW, baseT, baseD), 0.012, V3(0, footH + baseT / 2, 0), frame) + box(&m, V3(baseW - 0.04, 0.006, baseD - 0.04), 0.002, V3(0, footH + baseT + 0.003, 0), trim) + // Pedestal: central column with tapered side wings and a recessed trim reveal. + let colH = topY - footH - baseT + box(&m, V3(0.84, colH, 0.46), 0.025, V3(0, footH + baseT + colH / 2, 0), frame) + for sx: Float in [-1, 1] { + box(&m, V3(0.06, colH - 0.1, 0.56), 0.02, V3(sx * 0.45, footH + baseT + colH / 2, 0), frame) + for sz: Float in [-1, 1] { + box(&m, V3(0.008, colH - 0.2, 0.012), 0.003, V3(sx * 0.483, footH + baseT + colH / 2, sz * 0.2), trim) + } + } + // Horizontal reveal grooves on the front and back. + for sz: Float in [-1, 1] { + for k in 0..<3 { + let y = footH + baseT + 0.12 + Float(k) * 0.2 + box(&m, V3(0.76, 0.006, 0.004), 0.0015, V3(0, y, sz * 0.231), trim) + } + } + // Cable port on the back: recessed trim plate with a dark socket and cable grommet. + let py = footH + baseT + 0.22 + box(&m, V3(0.16, 0.07, 0.01), 0.008, V3(0.2, py, -0.233), trim) + box(&m, V3(0.12, 0.04, 0.012), 0.012, V3(0.2, py, -0.236), "rubber") + m.add(Prim.cylinder(radius: 0.012, height: 0.014, bevel: 0.003, segments: 16, material: "metal.chrome"), + Xform(translation: V3(0.17, py, -0.238), rotation: simd_quatf(angle: -Float.pi / 2, axis: V3(1, 0, 0)))) + // Recessed shadow step under the top plus socket-head bolts on the wings and base plate. + box(&m, V3(W - 0.5, 0.03, D - 0.3), 0.008, V3(0, topY - 0.015, 0), frame) + for sx: Float in [-1, 1] { for k in 0..<3 { + let z = (Float(k) - 1) * 0.14 + m.add(Prim.cylinder(radius: 0.007, height: 0.004, bevel: 0, segments: 6, material: "metal.chrome"), + Xform(translation: V3(sx * 0.48, footH + baseT + colH - 0.06, z), rotation: simd_quatf(angle: sx * -Float.pi / 2, axis: V3(0, 0, 1)))) + m.add(Prim.cylinder(radius: 0.007, height: 0.004, bevel: 0, segments: 6, material: "metal.chrome"), + Xform(translation: V3(sx * 0.48, footH + baseT + 0.06, z), rotation: simd_quatf(angle: sx * -Float.pi / 2, axis: V3(0, 0, 1)))) + } } + // Top: anodized bezel tray, then the glass panel inset 2 mm proud of it. + box(&m, V3(W, topT, D), 0.018, V3(0, topY + topT / 2, 0), frame) + box(&m, V3(W - 0.01, 0.004, D - 0.01), 0.002, V3(0, topY - 0.002, 0), trim) + let gW = W - 0.07, gD = D - 0.07 + m.add(Prim.roundedBox(V3(gW, glassT, gD), radius: 0.004, bevelSegments: 2, material: glass), + Xform(translation: V3(0, H - glassT / 2, 0)).jittered(&rng)) + let gy = H + 0.0006 + // Emissive grid, thin strips just above the glass surface. + let nx = Int((gW - 0.1) / gridPitch / 2), nz = Int((gD - 0.1) / gridPitch / 2) + for i in -nx...nx { + strip(&m, 0.0012, gD - 0.1, V3(Float(i) * gridPitch, gy, 0)) + } + for j in -nz...nz { + strip(&m, gW - 0.1, 0.0012, V3(0, gy, Float(j) * gridPitch)) + } + // Emissive edge line framing the grid, and corner ticks. + for sz: Float in [-1, 1] { strip(&m, gW - 0.06, 0.002, V3(0, gy, sz * (gD / 2 - 0.03))) } + for sx: Float in [-1, 1] { strip(&m, 0.002, gD - 0.06, V3(sx * (gW / 2 - 0.03), gy, 0)) } + // Status LED on the front bezel. + box(&m, V3(0.05, 0.003, 0.002), 0.001, V3(0, H - 0.03, D / 2 + 0.0005), grid) + groundAO(&m) + return LODModel(m) + } +} diff --git a/Sources/RealLibrary/Props/ChipLab/InterconnectStackCutaway.swift b/Sources/RealLibrary/Props/ChipLab/InterconnectStackCutaway.swift new file mode 100644 index 00000000..70ddc23d --- /dev/null +++ b/Sources/RealLibrary/Props/ChipLab/InterconnectStackCutaway.swift @@ -0,0 +1,123 @@ +import simd +import Foundation + +/// Back-end-of-line interconnect stack at 1e6x scale (0.5 x 1.0 x 0.5 m): silicon substrate with tungsten contacts under +/// twelve copper metal layers in low-k dielectric. Wire pitch and thickness widen from m01 to m12, routing alternates +/// along x and z, vias bridge the layers, and each layer steps back from the front so every wiring plane is exposed. +/// Parts: substrate, m01 ... m12; state "exploded" lifts the layers apart. +public struct InterconnectStackCutaway: RealArticulated { + public static let id = "interconnect-stack-cutaway" + public static let summary = "BEOL interconnect stack cutaway at 1e6x scale: 12 copper metal layers with vias in low-k dielectric, pitch widening upward; explodes by layer." + public static let tags = ["prop", "chiplab", "micro-scale", "articulated"] + public static let budget = 8000 + public static let author = "realityhd" + public static let preview = PreviewHint(azimuth: 38, elevation: 22, distance: 2.6, studio: true) + + /// Metal layer count. + public var layers = 12 + public var wire: MaterialKey = "metal.copper" + public var plug: MaterialKey = "metal.tungsten" + public var dielectric: MaterialKey = "oxide.lowk" + public var silicon: MaterialKey = "silicon.die-edge" + public init() {} + + /// Flat-faced prism, 12 triangles. + func box(_ s: V3, _ r: Float, _ mat: MaterialKey) -> Surface { + let hx = s.x / 2, hy = s.y / 2 + return Prim.extrude([V2(-hx, -hy), V2(hx, -hy), V2(hx, hy), V2(-hx, hy)], depth: s.z, bevel: 0, bevelSegments: 1, material: mat) + } + + public func rig(seed: UInt64) -> Rig { + var rig = Rig(name: Self.id, lods: 1) + rig.base[0] = Model(name: Self.id) + let W: Float = 0.5, D: Float = 0.5, subT: Float = 0.06 + let n = layers + // Layer heights grow 1.0x to 2.6x; total fills the 1 m height with a thin passivation cap. + let weights = (0.. LODModel { + var rng = SeededRNG(seed: seed) + var m = Model(name: Self.id) + let W = size.x, D = size.z + let gT: Float = 0.0009, film: Float = 0.00004, cu: Float = 0.00008 + // Glass core with ground, slightly chamfered edges. + m.add(Prim.roundedBox(V3(W, gT, D), radius: 0.00008, bevelSegments: 1, material: glass), + Xform(translation: V3(0, gT / 2, 0))) + // Polyimide build-up film, inset from the edge so the glass edge and vias stay visible. + let fx = W - 0.006, fz = D - 0.006 + m.add(Prim.roundedBox(V3(fx, film, fz), radius: 0.00002, bevelSegments: 1, material: glass), + Xform(translation: V3(0, gT + film / 2, 0))) + let top = gT + film + // Through-glass vias seen as copper columns in the front and right polished edges. + let nF = Int((W - 0.008) / viaPitch) + for k in 0.. LODModel { + var rng = SeededRNG(seed: seed) + var m = Model(name: Self.id) + let W = size.x, T = size.y, D = size.z + // Core with satin mask skin; sits on the bottom pad layer. + let core: Float = 0.00235, y0: Float = T - core + m.add(Prim.roundedBox(V3(W, core, D), radius: 0.00003, bevelSegments: 1, material: laminate), + Xform(translation: V3(0, y0 + core / 2, 0))) + let top = y0 + core + let land = Float(landCount - 1) * landPitch + // Die land: gold microbump pads. + for i in 0.. Surface { + Prim.roundedBox(s, radius: r, bevelSegments: seg, material: mat) + } + + public func rig(seed: UInt64) -> Rig { + var rig = Rig(name: Self.id, lods: 1) + rig.base[0] = Model(name: Self.id) + func part(_ n: String, _ axis: V3, _ d: Float) { + _ = rig.part(n, pivot: V3(0, 0, 0), joint: .slide(axis: axis, 0...d, duration: 0.9)) + } + func at(_ x: Float, _ y: Float, _ z: Float) -> Xform { Xform(translation: V3(x, y, z)) } + + // Vertical stack (m): rail 0..0.05, substrate 0.05..0.15, gate bottom fill to 0.20, sheets pitch 0.065. + let t: Float = 0.026, pitch: Float = 0.072, y0: Float = 0.215 + let hf: Float = 0.012 + let gx: Float = 0.085 // half gate length + let zc: Float = 0.030 // gate cut plane (front) + let zb: Float = -0.20 // gate back + let sheetZ: Float = 0.15 // half sheet width + let topSheet = y0 + Float(sheets - 1) * pitch + t + let gateTop = topSheet + hf + 0.04 + + part("rail", V3(0, -1, 0), 0.12) + rig.add(box(V3(0.34, 0.05, 0.12), 0.006, wireMat, seg: 2), at(-0.08, 0.025, 0), to: "rail") + rig.add(box(V3(0.34, 0.012, 0.12), 0.004, wireMat, seg: 1), at(-0.08, 0.001, 0), to: "rail") + + part("substrate", V3(0, -1, 0), 0.06) + rig.add(box(V3(0.5, 0.10, 0.50), 0.006, bodyMat, seg: 2), at(0, 0.10, 0), to: "substrate") + // Backside via through the substrate under the source epi. + rig.add(Prim.lathe([V2(0.032, 0), V2(0.032, 0.11)], segments: 16, seamTile: 1, material: gateMat), at(-0.145, 0.05, 0), to: "substrate") + // Oxide-lined trench stripe along the cut face. + rig.add(box(V3(0.5, 0.014, 0.02), 0.003, spacerMat), at(0, 0.143, 0.23), to: "substrate") + + for i in 0.. String { + let i = Int(h * 6) % 6, f = h * 6 - floor(h * 6) + let p = v * (1 - s), q = v * (1 - f * s), t = v * (1 - (1 - f) * s) + let (r, g, b): (Float, Float, Float) = [(v, t, p), (q, v, p), (p, v, t), (p, q, v), (t, p, v), (v, p, q)][i] + return String(format: "%02X%02X%02X", Int(r * 255), Int(g * 255), Int(b * 255)) + } + + func flat(_ w: Float, _ h: Float, _ t: Float, _ mat: MaterialKey) -> Surface { + Prim.extrude(Shape2D.rect(w, h), depth: t, bevel: 0, bevelSegments: 1, material: mat) + } + + public func build(seed: UInt64) -> LODModel { + var rng = SeededRNG(seed: seed) + var m = Model(name: Self.id) + let W = size.x, D = size.z + let baseH: Float = 0.032 + // Weighted chrome base: rounded slab with a beveled top edge, rubber feet beneath. + m.add(Prim.roundedBox(V3(W, baseH - 0.004, D), radius: 0.008, bevelSegments: 3, material: chrome), + Xform(translation: V3(0, 0.004 + (baseH - 0.004) / 2, 0))) + for sx: Float in [-1, 1] { for sz: Float in [-1, 1] { + m.add(Prim.cylinder(radius: 0.011, height: 0.004, bevel: 0.001, segments: 14, material: foot), + Xform(translation: V3(sx * (W / 2 - 0.03), 0, sz * (D / 2 - 0.025)))) + } } + // Engraved plaque on the front of the base top: stainless plate with engraved text bars and border. + let pw: Float = 0.24, pd: Float = 0.07, pz = D / 2 - 0.045 + m.add(Prim.roundedBox(V3(pw, 0.002, pd), radius: 0.0007, bevelSegments: 1, material: plaque), + Xform(translation: V3(0, baseH + 0.0003, pz))) + let py = baseH + 0.0014 + for (k, (len, dz)) in [(0.17, -0.019), (0.12, -0.003), (0.15, 0.012), (0.07, 0.026)].enumerated() { + let h: Float = k == 0 ? 0.0036 : 0.0022 + var x: Float = -Float(len) / 2 + while x < Float(len) / 2 - 0.004 { + let w = rng.float(0.002...0.0065) + m.add(flat(min(w, Float(len) / 2 - x), h, 0.0004, engraving), + Xform(translation: V3(x + w / 2, py, pz + Float(dz)), rotation: simd_quatf(degrees: 90, axis: V3(1, 0, 0)))) + x += w + rng.float(0.0015...0.004) + } + } + let b: Float = 0.0006 + for sz: Float in [-1, 1] { m.add(flat(pw - 0.008, b, 0.0004, engraving), Xform(translation: V3(0, py, pz + sz * (pd / 2 - 0.004)), rotation: simd_quatf(degrees: 90, axis: V3(1, 0, 0)))) } + for sx: Float in [-1, 1] { m.add(flat(b, pd - 0.008, 0.0004, engraving), Xform(translation: V3(sx * (pw / 2 - 0.004), py, pz), rotation: simd_quatf(degrees: 90, axis: V3(1, 0, 0)))) } + + // Wafer assembly in local frame: disc faces +Z, centered at origin; then tilted back and lifted. + let R = waferDiameter / 2, T = waferThickness + let tiltQ = simd_quatf(degrees: -tilt, axis: V3(1, 0, 0)) + let centerZ: Float = -0.012 + let cy = baseH + 0.15 + (R + 0.012) * cos(tilt * .pi / 180) + let place = { (z: Float) -> Xform in + Xform(translation: V3(0, cy, centerZ) + tiltQ.act(V3(0, 0, z)), rotation: tiltQ) + } + // Wafer body: mirror silicon blank with beveled rim, lighter diced edge band. + m.add(Prim.cylinder(radius: R, height: T, bevel: 0.0004, segments: 48, material: "silicon.die-edge"), + Xform(translation: V3(0, cy, centerZ) + tiltQ.act(V3(0, 0, -T / 2)), rotation: tiltQ * simd_quatf(degrees: 90, axis: V3(1, 0, 0)))) + m.add(Prim.cylinder(radius: R - 0.0007, height: 0.00008, bevel: 0, segments: 48, material: "silicon.die:6B7088"), + Xform(translation: V3(0, cy, centerZ) + tiltQ.act(V3(0, 0, T / 2 - 0.00004)), rotation: tiltQ * simd_quatf(degrees: 90, axis: V3(1, 0, 0)))) + // Die grid: one plate per full die inside the 3 mm edge exclusion, hue by radius and angle (thin-film interference). + let usable = R - 0.003 + let ox = diePitch.x * 0.5, oz = diePitch.y * 0.25 + let cell = Prim.extrude(Shape2D.rect(diePitch.x - 0.0009, diePitch.y - 0.0009), depth: 0.00012, bevel: 0, bevelSegments: 1, material: wafer) + for i in -6...6 { for j in -5...5 { + let cx = Float(i) * diePitch.x + ox * 0, cz = Float(j) * diePitch.y + oz + let hw = diePitch.x / 2, hh = diePitch.y / 2 + let corners = [(cx - hw, cz - hh), (cx + hw, cz - hh), (cx - hw, cz + hh), (cx + hw, cz + hh)] + if corners.contains(where: { $0.0 * $0.0 + $0.1 * $0.1 > usable * usable }) { continue } + let r = sqrt(cx * cx + cz * cz) / R, a = atan2(cz, cx) + let hue = (0.62 + 0.5 * r + 0.08 * sin(a * 2) + rng.float(-0.012...0.012)).truncatingRemainder(dividingBy: 1) + let mat = MaterialKey(stringLiteral: "silicon.thinfilm:" + hex(h: (round((hue < 0 ? hue + 1 : hue) * 14) / 14), s: 0.42 + 0.2 * r, v: 0.88 - 0.25 * r)) + var c = cell; c.material = mat + m.add(c, Xform(translation: V3(0, cy, centerZ) + tiltQ.act(V3(cx, cz, T / 2 + 0.00006)), rotation: tiltQ)) + } } + // Flat marker: notch dot at the wafer bottom edge and laser ID bar. + m.add(Prim.roundedBox(V3(0.006, 0.0003, 0.0022), radius: 0.0001, bevelSegments: 1, material: "metal.gold"), + Xform(translation: V3(0, cy, centerZ) + tiltQ.act(V3(0.0, -R + 0.008, T / 2 + 0.0002)), rotation: tiltQ * simd_quatf(degrees: 90, axis: V3(1, 0, 0)))) + + // Acrylic cradle: back plate behind the wafer, grooved front retaining ring, chrome posts and clamp blocks. + let ringMajor = R + 0.0045 + m.add(Prim.cylinder(radius: R + 0.011, height: 0.006, bevel: 0.0012, segments: 48, material: acrylic), + Xform(translation: V3(0, cy, centerZ) + tiltQ.act(V3(0, 0, -T / 2 - 0.0063)), rotation: tiltQ * simd_quatf(degrees: 90, axis: V3(1, 0, 0)))) + m.add(Prim.torus(major: ringMajor + 0.003, minor: 0.0045, segments: 48, sides: 8, material: acrylic), + place(T / 2 + 0.0035).withRot(tiltQ * simd_quatf(degrees: 90, axis: V3(1, 0, 0)))) + m.add(Prim.torus(major: ringMajor - 0.002, minor: 0.0016, segments: 48, sides: 8, material: acrylic), + place(T / 2 + 0.0015).withRot(tiltQ * simd_quatf(degrees: 90, axis: V3(1, 0, 0)))) + // Stem: chrome post with acrylic saddle under the ring. + let ringBottom = V3(0, cy, centerZ) + tiltQ.act(V3(0, -(R + 0.011), -0.002)) + for sx: Float in [-1, 1] { + let top = V3(sx * 0.05, ringBottom.y + 0.003, ringBottom.z) + let bot = V3(sx * 0.05, baseH - 0.002, ringBottom.z) + m.add(Prim.tube([bot, top], radii: [0.007, 0.007], sides: 24, seamTile: 0.05, material: chrome)) + m.add(Prim.cylinder(radius: 0.011, height: 0.003, bevel: 0.0008, segments: 24, material: chrome), Xform(translation: V3(sx * 0.05, baseH - 0.001, ringBottom.z))) + } + m.add(Prim.roundedBox(V3(0.13, 0.014, 0.012), radius: 0.004, bevelSegments: 2, material: acrylic), + Xform(translation: V3(0, ringBottom.y + 0.006, ringBottom.z))) + groundAO(&m) + return LODModel(m) + } +} + +private extension Xform { + func withRot(_ q: simd_quatf) -> Xform { var x = self; x.rotation = q; return x } +} diff --git a/Sources/RealLibrary/Props/DataCenterSite/EarthSitePin.swift b/Sources/RealLibrary/Props/DataCenterSite/EarthSitePin.swift new file mode 100644 index 00000000..f5216f8d --- /dev/null +++ b/Sources/RealLibrary/Props/DataCenterSite/EarthSitePin.swift @@ -0,0 +1,39 @@ +import simd +import Foundation + +/// Tabletop map marker pin, 0.05 x 0.15 x 0.05 m: brushed stainless needle with a tapered tip, knurled collar and neck, a +/// brushed cap ring and a glowing orange emissive head knob inside a thin clear shell. A bent-tip scuff marks the needle. +/// Base y = 0 at the needle tip; the head sits at the top. +public struct EarthSitePin: RealAsset { + public static let id = "earth-site-pin" + public static let summary = "Tabletop map marker pin: brushed metal shaft, collar and a glowing emissive head knob." + public static let tags = ["prop", "space", "datacenter", "metal"] + public static let budget = 500 + public static let author = "realityhd" + public static let preview = PreviewHint(azimuth: 30, elevation: 20, distance: 1.0, studio: true) + + /// Head knob radius (m). + public var headRadius: Float = 0.025 + public var metal: MaterialKey = "metal.aluminum-brushed" + public var needle: MaterialKey = "metal.stainless" + /// Glow of the head knob (swap for any emissive key). + public var glow: MaterialKey = "emissive.signal-orange" + public init() {} + + public func build(seed: UInt64) -> LODModel { + var m = Model(name: Self.id) + let hr = headRadius, hc = 0.15 - hr - 0.001 + // Needle: tip, shaft, shoulder. + m.add(Prim.lathe([V2(0.0001, 0), V2(0.0006, 0.004), V2(0.0014, 0.03), V2(0.0018, 0.07), V2(0.0018, 0.078), V2(0.0001, 0.078)], segments: 10, seamTile: 0.05, material: needle), .identity) + // Collar: stepped turned base with a chamfer, then neck. + m.add(Prim.lathe([V2(0.0001, 0.074), V2(0.0085, 0.074), V2(0.0095, 0.076), V2(0.0095, 0.084), V2(0.0075, 0.086), V2(0.0045, 0.09), V2(0.0042, 0.108), V2(0.0001, 0.108)], segments: 16, seamTile: 0.05, material: metal), .identity) + // Knurl ring. + m.add(Prim.torus(major: 0.0095, minor: 0.0012, segments: 16, sides: 4, material: "metal.aluminum-brushed"), Xform(translation: V3(0, 0.08, 0))) + // Cap ring under the head, then head shell and emissive core. + m.add(Prim.lathe([V2(0.0001, 0.103), V2(0.0115, 0.103), V2(0.0125, 0.105), V2(0.0125, 0.11), V2(0.0085, 0.114), V2(0.0001, 0.114)], segments: 16, seamTile: 0.05, material: metal), .identity) + m.add(Prim.cubeSphere(subdivisions: 5, material: glow) { d in d * (hr * 0.82) }, Xform(translation: V3(0, hc, 0))) + m.add(Prim.cubeSphere(subdivisions: 5, material: "plastic.clear") { d in d * hr }, Xform(translation: V3(0, hc, 0))) + groundAO(&m, height: 0.02, floor: 0.9) + return LODModel(m) + } +} diff --git a/Sources/RealLibrary/Props/DataCenterSite/GroundStationDish.swift b/Sources/RealLibrary/Props/DataCenterSite/GroundStationDish.swift new file mode 100644 index 00000000..351a00f2 --- /dev/null +++ b/Sources/RealLibrary/Props/DataCenterSite/GroundStationDish.swift @@ -0,0 +1,176 @@ +import simd +import Foundation + +/// Satellite ground station dish, 8 x 10 x 8 m: 7.5 m segmented aluminum reflector (24 gores, 2 seam rings, brushed seam strips), +/// back truss with hub, spokes, ring and diagonals, quadripod feed horn with LNA box, steel az-el yoke on a slew bearing, +/// tapered pedestal tower with door, ladder and bolted flange, and a square concrete foundation with anchor bolts. +/// Parts: base foundation and tower, dish-slew (azimuth, yoke), dish-elevate (reflector, truss, feed; child of dish-slew). +/// States: park (zenith-ish), track (default), horizon. +public struct GroundStationDish: RealArticulated { + public static let id = "ground-station-dish" + public static let summary = "Satellite ground station dish: panelled parabolic reflector, back truss, quadripod feed horn, az-el pedestal on a concrete foundation, slewing dish." + public static let tags = ["prop", "articulated", "space", "metal", "datacenter"] + public static let budget = 10000 + public static let author = "realityhd" + public static let preview = PreviewHint(azimuth: 35, elevation: 18, distance: 1.0, studio: true) + + /// Reflector radius (m). + public var dishRadius: Float = 3.95 + /// Focal length over diameter. + public var focalRatio: Float = 0.38 + /// Gores (radial panels). + public var gores = 24 + /// Resting boresight elevation above the horizon (degrees). + public var elevation: Float = 40 + public var reflector: MaterialKey = "metal.painted:E9EBEC" + public var seam: MaterialKey = "metal.aluminum-brushed" + public var truss: MaterialKey = "metal.galvanized" + public var tower: MaterialKey = "metal.painted:D3D7D9" + public var steel: MaterialKey = "metal.steel" + public var pad: MaterialKey = "concrete.rough" + public init() {} + + public func rig(seed: UInt64) -> Rig { + var rng = SeededRNG(seed: seed) + var rig = Rig(name: Self.id, lods: 1) + let R = dishRadius, f = 2 * R * focalRatio * 2 / 2 * 1.0 // f = D * ratio + let focal = 2 * R * focalRatio + _ = f + let depth = R * R / (4 * focal) + let H: Float = 5.1 // elevation axis height + let off: Float = 1.05 // vertex distance above the elevation axis along the boresight + let tilt = simd_quatf(degrees: -(90 - elevation), axis: V3(0, 0, 1)) + func para(_ r: Float) -> Float { r * r / (4 * focal) + off } + func B(_ s: V3, _ c: V3, _ mat: MaterialKey, r: Float = 0.004, to part: String? = nil) { + let (a, x) = OK.box(s, c, mat, r: r) + if let part { rig.add(a, x, to: part) } else { rig.base[0].add(a, x) } + } + + // Foundation: square pad with chamfer, plinth, anchor bolts, grout. + rig.base[0].add(Prim.roundedBox(V3(7.8, 0.6, 7.8), radius: 0.04, bevelSegments: 2, material: pad), Xform(translation: V3(0, 0.3, 0))) + rig.base[0].add(Prim.cylinder(radius: 1.35, height: 0.22, bevel: 0.02, segments: 32, bevelSegments: 1, material: "concrete.smooth"), Xform(translation: V3(0, 0.6, 0))) + // Tower: tapered tube, base flange with anchor bolts, door, ladder, cable tray. + var prof: [V2] = [V2(0, 0.82), V2(1.1, 0.82), V2(1.1, 0.9), V2(0.98, 0.9), V2(0.98, 1.0), V2(0.9, 1.0), + V2(0.9, 1.1), V2(0.84, 2.6), V2(0.74, 2.6), V2(0, 2.6)] + prof = prof.map { $0 } + rig.base[0].add(Prim.lathe(prof, segments: 28, seamTile: 1.2, material: tower), .identity) + var bolts = Model(name: "anchors") + for k in 0..<16 { + let a = Float(k) / 16 * 2 * .pi + OK.bolt(&bolts, at: V3(cos(a) * 1.04, 0.9, sin(a) * 1.04), size: 0.05, steel) + } + for k in 0..<4 { let a = (Float(k) + 0.5) / 4 * 2 * .pi + B(V3(0.5, 0.05, 0.5), V3(cos(a) * 3.0, 0.62, sin(a) * 3.0), "metal.galvanized", r: 0.003) // leveling plates + OK.bolt(&bolts, at: V3(cos(a) * 3.0, 0.67, sin(a) * 3.0), size: 0.05, steel) } + rig.base[0].add(bolts) + // Door (faces +Z), with frame, handle and hazard plate. + B(V3(0.62, 1.25, 0.03), V3(0, 1.75, 0.915), "metal.painted:C9CED1", r: 0.003) + B(V3(0.7, 1.33, 0.02), V3(0, 1.75, 0.90), "metal.galvanized", r: 0.003) + B(V3(0.05, 0.16, 0.03), V3(0.2, 1.7, 0.95), steel, r: 0.003) + B(V3(0.2, 0.2, 0.01), V3(0, 2.25, 0.935), "metal.painted:E0B000", r: 0.002) + // Ladder on -X side. + for sz: Float in [-0.2, 0.2] { rig.base[0].add(OK.rod(V3(-0.97, 0.9, sz), V3(-0.88, 2.55, sz), r: 0.018, "metal.galvanized", sides: 6)) } + for k in 0..<9 { let y = 1.05 + Float(k) * 0.17 + rig.base[0].add(OK.rod(V3(-0.96 + (y - 0.9) * 0.054, y, -0.2), V3(-0.96 + (y - 0.9) * 0.054, y, 0.2), r: 0.012, "metal.galvanized", sides: 4)) } + // Cable tray with cables running up the +X side and into the foundation. + B(V3(0.18, 1.7, 0.08), V3(0.93, 1.75, -0.2), "metal.galvanized", r: 0.003) + rig.base[0].add(Prim.tube([V3(0.95, 0.3, -0.2), V3(1.6, 0.35, -0.5), V3(3.3, 0.05, -0.6)], radii: [0.05, 0.05, 0.05], sides: 8, seamTile: 0.3, material: "plastic.black")) + + // Azimuth: slew ring, deck, yoke arms with gussets, bearing caps. + rig.part("dish-slew", pivot: V3(0, 2.6, 0), joint: .hinge(axis: .up, -120...120, duration: 10)) + rig.add(Prim.cylinder(radius: 0.95, height: 0.14, bevel: 0.01, segments: 28, bevelSegments: 1, material: steel), Xform(translation: V3(0, 2.6, 0)), to: "dish-slew") + rig.add(Prim.cylinder(radius: 1.0, height: 0.08, bevel: 0.01, segments: 28, bevelSegments: 1, material: "metal.casework"), Xform(translation: V3(0, 2.74, 0)), to: "dish-slew") + var sb = Model(name: "slewbolts") + for k in 0..<16 { let a = Float(k) / 16 * 2 * .pi + OK.bolt(&sb, at: V3(cos(a) * 0.88, 2.74, sin(a) * 0.88), size: 0.04, "metal.titanium") } + rig.add(sb, to: "dish-slew", lod: 0) + B(V3(1.5, 0.2, 3.4), V3(0, 2.92, 0), tower, r: 0.006, to: "dish-slew") + let arm = Shape2D.rounded([V2(-0.75, 0), V2(0.75, 0), V2(0.34, 1.85), V2(-0.34, 1.85)], radius: 0.03) + for sz: Float in [-1, 1] { + rig.add(Prim.extrude(arm, depth: 0.16, bevel: 0.006, bevelSegments: 1, material: tower), Xform(translation: V3(0, 3.0, sz * 1.62)), to: "dish-slew") + let (cap, cx) = OK.cylZ(0.3, 0.26, x: 0, y: H, z0: sz * 1.62 + (sz > 0 ? 0.08 : -0.34), steel, seg: 24, bevel: 0.01) + rig.add(cap, cx, to: "dish-slew") + var ab = Model(name: "capbolts") + for k in 0..<6 { let a = Float(k) / 6 * 2 * .pi + ab.add(Prim.cylinder(radius: 0.02, height: 0.03, bevel: 0, segments: 6, bevelSegments: 1, material: "metal.titanium"), + Xform(translation: V3(cos(a) * 0.22, H + sin(a) * 0.22, sz * 1.62 + (sz > 0 ? 0.34 : -0.37)), rotation: OK.qz)) } + rig.add(ab, to: "dish-slew", lod: 0) + // Elevation drive housing on the outboard face. + B(V3(0.36, 0.34, 0.2), V3(0.0, 3.55, sz * 1.82), "metal.painted:2F3438", r: 0.005, to: "dish-slew") + } + B(V3(0.7, 0.5, 0.9), V3(-0.8, 3.25, 0), "metal.painted:2F3438", r: 0.006, to: "dish-slew") // az drive cabinet + // Elevation jackscrew from the deck to the reflector hub. + rig.add(OK.rod(V3(-0.5, 3.0, 0.0), V3(-0.65, 4.4, 0.0), r: 0.07, "metal.casework", sides: 8), to: "dish-slew") + + // Reflector and back structure, authored relative to the elevation axis, then tilted. + rig.part("dish-elevate", parent: "dish-slew", pivot: V3(0, H, 0), joint: .hinge(axis: V3(0, 0, 1), -35...50, duration: 8)) + let P = V3(0, H, 0) + func put(_ s: Surface, _ mat: MaterialKey? = nil) { rig.add(s, Xform(translation: P, rotation: tilt), to: "dish-elevate") } + // Shell: front skin then rim then back skin. + var shell: [V2] = [] + let steps = 10 + for i in 0...steps { let r = R * Float(i) / Float(steps); shell.append(V2(max(r, 0.001), para(r))) } + shell.append(V2(R + 0.03, para(R) + 0.0)); shell.append(V2(R + 0.03, para(R) - 0.07)) + for i in stride(from: steps, through: 0, by: -1) { let r = R * Float(i) / Float(steps); shell.append(V2(max(r, 0.001), para(r) - 0.07)) } + put(Prim.lathe(Array(shell.reversed()), segments: 48, seamTile: 3.0, material: reflector)) + // Gore seams: brushed strips along the surface, plus two seam rings. + let seamH: Float = 0.012 + for g in 0.. V3 { V3(cos(a) * r, para(r) - 0.07 - rBack * (r / R), sin(a) * r) } + let nS = 10 + var ring: [V3] = [] + for k in 0...36 { ring.append(backPt(rRing, Float(k) / 36 * 2 * .pi)) } + put(Prim.tube(ring, radii: Array(repeating: 0.05, count: ring.count), sides: 4, seamTile: 0.3, material: truss)) + for k in 0.. Float = { $0 } + _ = deg + rig.states = [RigState("track"), RigState("park", ["dish-elevate": -50, "dish-slew": 0]), + RigState("horizon", ["dish-elevate": 40, "dish-slew": 60])] + return rig + } +} diff --git a/Sources/RealLibrary/Props/DataCenterSite/LunarDCHabitat.swift b/Sources/RealLibrary/Props/DataCenterSite/LunarDCHabitat.swift new file mode 100644 index 00000000..4bed3a43 --- /dev/null +++ b/Sources/RealLibrary/Props/DataCenterSite/LunarDCHabitat.swift @@ -0,0 +1,123 @@ +import simd +import Foundation + +/// Lunar data-center habitat, 15 x 6 x 15 m: three rigid white pressure modules and one inflatable module half buried in a +/// regolith berm with sandbag retaining courses, a radiator fin bank on stands, an airlock with hatch and access ramp, +/// a comms mast, and a cratered grey regolith ground skirt. Base y = 0, centred on X/Z. +public struct LunarDCHabitat: RealAsset { + public static let id = "lunar-dc-habitat" + public static let summary = "Lunar data center habitat: modules under a sandbag regolith berm, radiator fins, airlock and grey regolith skirt." + public static let tags = ["prop", "space", "datacenter", "ground"] + public static let budget = 15000 + public static let author = "realityhd" + public static let preview = PreviewHint(azimuth: 35, elevation: 28, distance: 1.0, studio: true) + + /// Ground and berm slope material. + public var ground: MaterialKey = "regolith.lunar" + public var slope: MaterialKey = "regolith.lunar-fine" + /// Sandbag fabric, tinted by dust. + public var bag: MaterialKey = "fabric.canvas:8D887C" + public var hull: MaterialKey = "thermal.radiator-white:DDDDD7" + public var inflatable: MaterialKey = "mli.silver" + public var metal: MaterialKey = "metal.aluminum-brushed" + public init() {} + + public func build(seed: UInt64) -> LODModel { + var rng = SeededRNG(seed: seed) + var m = Model(name: Self.id) + // Craters: x, z, radius. + var craters: [(V2, Float)] = [] + for _ in 0..<7 { craters.append((V2(rng.float(-7...7), rng.float(-7...7)), rng.float(0.4...1.1))) } + craters.append((V2(5.4, -5.2), 1.3)) + let bx: Float = 4.6, bz: Float = 3.4, top: Float = 2.1 + func berm(_ x: Float, _ z: Float) -> Float { + let qx = abs(x) - (bx - 1.6), qz = abs(z - 0.5) - (bz - 1.6) + let d = simd_length(V2(max(qx, 0), max(qz, 0))) + min(max(qx, qz), 0) - 1.6 + let t = max(0, min(1, 1 - d / 3.2)) + return top * t * t * (3 - 2 * t) + } + func height(_ p: V2) -> Float { + var h = berm(p.x, p.y) + 0.12 + h += 0.05 * sin(p.x * 0.9 + 1.3) * cos(p.y * 0.8) + 0.03 * sin(p.x * 2.7 + p.y * 2.1) + for (c, r) in craters { + let d = simd_length(p - c) / r + if d < 1.5 { h -= 0.1 * r * (d < 1 ? (1 - d * d) : 0) - 0.05 * r * exp(-pow((d - 1.05) * 5, 2)) * -1 } + } + let edge = max(abs(p.x), abs(p.y)); if edge > 6.2 { h *= max(0, 1 - (edge - 6.2) / 1.3) } + return h + } + m.add(Prim.terrain(size: V2(15, 15), segments: 34, material: slope) { height($0) }) + // Rigid modules: lathe cylinders along X and Z buried to ~ y 1.3. + let cy: Float = 1.75, R: Float = 1.45 + func cyl(len: Float, at c: V3, alongZ: Bool) { + let hp: [V2] = [V2(0, 0), V2(R - 0.12, 0), V2(R, 0.12), V2(R, len - 0.12), V2(R - 0.12, len), V2(0, len)] + let q = alongZ ? OK.qz : OK.qx + let off = alongZ ? V3(c.x, c.y, c.z - len / 2) : V3(c.x - len / 2, c.y, c.z) + m.add(Prim.lathe(hp, segments: 20, seamTile: 1.0, material: hull), Xform(translation: off, rotation: q)) + for k in 1..<4 { + let t = Float(k) * len / 4 + let ring: [V2] = [V2(R - 0.002, 0), V2(R + 0.03, 0.01), V2(R + 0.03, 0.09), V2(R - 0.002, 0.1)] + let o = alongZ ? V3(c.x, c.y, c.z - len / 2 + t) : V3(c.x - len / 2 + t, c.y, c.z) + m.add(Prim.lathe(ring, segments: 20, seamTile: 1.0, material: metal), Xform(translation: o, rotation: q)) + } + } + cyl(len: 7.6, at: V3(-0.2, cy, -0.9), alongZ: false) + cyl(len: 5.2, at: V3(-2.9, cy, 1.9), alongZ: true) + cyl(len: 4.4, at: V3(2.8, cy, 1.3), alongZ: true) + // Inflatable module: fat superellipsoid with MLI skin and restraint straps. + m.add(Prim.superellipsoid(V3(4.6, 3.0, 3.2), exponent: 2.6, subdivisions: 7, material: inflatable), Xform(translation: V3(-0.2, 1.5, -4.6))) + for k in -2...2 { + let p: [V2] = [V2(1.5, 0), V2(1.55, 0.02), V2(1.55, 0.1), V2(1.5, 0.12)] + _ = p + m.add(Prim.torus(major: 1.45 * (1 - 0.07 * Float(k * k)), minor: 0.018, segments: 20, sides: 4, material: metal), Xform(translation: V3(-0.2 + Float(k) * 0.8, 1.5, -4.6), rotation: simd_quatf(degrees: 90, axis: V3(0, 0, 1)))) + } + // Tunnel linking inflatable to the main module. + m.add(Prim.cylinder(radius: 0.55, height: 1.6, bevel: 0.02, segments: 12, bevelSegments: 1, material: hull), Xform(translation: V3(-0.2, cy - 0.2, -3.9), rotation: OK.qz)) + // Airlock at the +X end of the main module: cylinder, hatch, ramp, handrail. + let ax: Float = 3.4 + m.add(Prim.cylinder(radius: 1.0, height: 2.2, bevel: 0.05, segments: 20, bevelSegments: 1, material: hull), Xform(translation: V3(ax, cy - 0.1, -0.9), rotation: OK.qx)) + m.add(Prim.cylinder(radius: 0.62, height: 0.06, bevel: 0.01, segments: 20, bevelSegments: 1, material: "plastic.matte:2A2D33"), Xform(translation: V3(ax + 2.2, cy - 0.1, -0.9), rotation: OK.qx)) + m.add(Prim.torus(major: 0.68, minor: 0.04, segments: 20, sides: 6, material: "metal.titanium"), Xform(translation: V3(ax + 2.22, cy - 0.1, -0.9), rotation: OK.qx)) + m.add(Prim.sweep(Shape2D.rect(1.2, 0.05), along: [V3(ax + 2.3, 0.95, -0.9), V3(ax + 3.5, 0.12, -0.9)], up: V3(0, 0, 1), material: metal)) + for z: Float in [-1.5, -0.3] { m.add(OK.rod(V3(ax + 2.35, 1.5, z), V3(ax + 3.5, 0.7, z), r: 0.02, "metal.stainless", sides: 6)) } + // Sandbag courses: retaining walls flanking the airlock path and capping module ends. + func bags(from a: V3, to b: V3, courses: Int, bagLen: Float = 0.5) { + let dir = b - a, len = simd_length(dir), n = Int(len / bagLen) + let ang = atan2(dir.x, dir.z) * 180 / .pi + for c in 0.. 0.3 { continue } + let s = rng.float(0.1...0.45) + m.add(Prim.superellipsoid(V3(s, s * 0.6, s * 0.8), exponent: 3, subdivisions: 4, material: ground), Xform(translation: V3(p.x, height(p) + s * 0.1, p.y), rotation: simd_quatf(degrees: rng.float(0...360), axis: V3(0, 1, 0)))) + } + groundAO(&m, height: 0.1, floor: 0.7) + return LODModel(m) + } +} diff --git a/Sources/RealLibrary/Props/DataCenterSite/MarsDCDome.swift b/Sources/RealLibrary/Props/DataCenterSite/MarsDCDome.swift new file mode 100644 index 00000000..7b8deda0 --- /dev/null +++ b/Sources/RealLibrary/Props/DataCenterSite/MarsDCDome.swift @@ -0,0 +1,131 @@ +import simd +import Foundation + +/// Mars data-center dome, 20 x 10 x 20 m: frequency-3 geodesic dome (radius 7 m) of white panels in aluminum struts with +/// red dust accumulated on the lower panels, tinted glazing near the crown, a pressurized annex with airlock on +X, a +/// radiator bank on stands at -Z, a vent mast, and a red dust ground with drifts and rocks. Base y = 0, centred on X/Z. +public struct MarsDCDome: RealAsset { + public static let id = "mars-dc-dome" + public static let summary = "Mars data center: geodesic dome with red dust on lower panels, pressurized annex and radiators." + public static let tags = ["prop", "space", "datacenter", "ground"] + public static let budget = 15000 + public static let author = "realityhd" + public static let preview = PreviewHint(azimuth: 35, elevation: 26, distance: 1.0, studio: true) + + /// Dome radius (m). + public var radius: Float = 7 + public var ground: MaterialKey = "dust.mars" + public var film: MaterialKey = "dust.mars-film" + public var panel: MaterialKey = "thermal.radiator-white" + public var strut: MaterialKey = "metal.aluminum-brushed" + public var glazing: MaterialKey = "glass.tinted" + public init() {} + + public func build(seed: UInt64) -> LODModel { + var rng = SeededRNG(seed: seed) + var m = Model(name: Self.id) + let R = radius + func height(_ p: V2) -> Float { + let r = simd_length(p) + var h = 0.1 + 0.06 * sin(p.x * 0.7 + 0.4) * cos(p.y * 0.9) + 0.03 * sin(p.x * 2.3 - p.y * 1.7) + h += 0.45 * exp(-pow((r - (R + 0.5)) / 0.9, 2)) // dust drift hugging the footing + let e = max(abs(p.x), abs(p.y)); if e > 8.5 { h = 0.1 + (h - 0.1) * max(0, 1 - (e - 8.5) / 1.5) } + return h + } + m.add(Prim.terrain(size: V2(20, 20), segments: 40, material: ground) { height($0) }) + // Geodesic hemisphere: icosahedron subdivided to frequency 3, kept where the face centre is above the base. + let t: Float = (1 + sqrt(5)) / 2 + var vs: [V3] = [V3(-1, t, 0), V3(1, t, 0), V3(-1, -t, 0), V3(1, -t, 0), V3(0, -1, t), V3(0, 1, t), V3(0, -1, -t), V3(0, 1, -t), V3(t, 0, -1), V3(t, 0, 1), V3(-t, 0, -1), V3(-t, 0, 1)].map { simd_normalize($0) } + let fi: [[Int]] = [[0,11,5],[0,5,1],[0,1,7],[0,7,10],[0,10,11],[1,5,9],[5,11,4],[11,10,2],[10,7,6],[7,1,8],[3,9,4],[3,4,2],[3,2,6],[3,6,8],[3,8,9],[4,9,5],[2,4,11],[6,2,10],[8,6,7],[9,8,1]] + // Rotate so a vertex points up (pentagon crown). + let up = simd_quatf(from: vs[0], to: V3(0, 1, 0)) + vs = vs.map { up.act($0) } + let N = 3 + var tris: [(V3, V3, V3)] = [] + for f in fi { + let a = vs[f[0]], b = vs[f[1]], c = vs[f[2]] + func P(_ i: Int, _ j: Int) -> V3 { simd_normalize(a + (b - a) * (Float(i) / Float(N)) + (c - a) * (Float(j) / Float(N))) } + for i in 0..() + func key(_ a: V3, _ b: V3) -> String { + let ka = "\(Int(a.x * 1000)),\(Int(a.y * 1000)),\(Int(a.z * 1000))", kb = "\(Int(b.x * 1000)),\(Int(b.y * 1000)),\(Int(b.z * 1000))" + return ka < kb ? ka + "|" + kb : kb + "|" + ka + } + var dustLine = [Float]() + for (a, b, c) in tris { + let cen = (a + b + c) / 3 + if cen.y < cut || min(a.y, min(b.y, c.y)) < -0.01 { continue } + let A = a * R, B = b * R, C = c * R + let ctr = cen * R + let n = simd_normalize(cen) + let u = simd_normalize(B - A), w = simd_cross(n, u) + // Shrink toward centroid for the strut gap, then express in the panel plane. + func loc(_ p: V3) -> V2 { let q = ctr + (p - ctr) * 0.93; return V2(simd_dot(q - ctr, u), simd_dot(q - ctr, w)) } + let outline = [loc(A), loc(B), loc(C)] + let h = ctr.y / R + let mat: MaterialKey + if h < 0.34 { mat = film } + else if h < 0.5 { mat = rng.chance(0.55) ? film : "\(panel):D8C4B2" } + else if h < 0.66 { mat = rng.chance(0.25) ? "\(panel):E0CDBD" : "\(panel):DEDDD6" } + else if h > 0.86 && rng.chance(0.6) { mat = glazing } + else { mat = "\(panel):\(["E6E6E0", "E2E2DB", "E9E9E4"][rng.int(0...2)])" } + let s = Prim.extrude(outline, depth: 0.06, bevel: 0.006, bevelSegments: 1, material: mat) + let basis = simd_float3x3(columns: (u, w, n)) + m.add(s, Xform(translation: ctr, rotation: simd_quatf(basis))) + for (p, q) in [(A, B), (B, C), (C, A)] { + let k = key(p, q) + if edges.insert(k).inserted { + let low = (p.y + q.y) / (2 * R) < 0.3 + m.add(OK.rod(p, q, r: 0.04, low ? "metal.aluminum-brushed" : strut, sides: 4)) + } + } + dustLine.append(h) + } + // Footing ring and node balls. + m.add(Prim.torus(major: R + 0.05, minor: 0.12, segments: 48, sides: 6, material: "concrete.smooth"), Xform(translation: V3(0, 0.1, 0))) + // Dome airlock vestibule on +X side leading to the annex, annex pressure cylinder, door, windows. + let ax: Float = 3.8, ar: Float = 2.0, al: Float = 4.6 + m.add(Prim.cylinder(radius: ar, height: al, bevel: 0.06, segments: 22, bevelSegments: 1, material: panel + ":E3E3DD"), Xform(translation: V3(ax, ar + 0.05, 0), rotation: OK.qx)) + for k in 1..<4 { + m.add(Prim.torus(major: ar + 0.02, minor: 0.04, segments: 22, sides: 4, material: strut), Xform(translation: V3(ax + Float(k) * al / 4, ar + 0.05, 0), rotation: OK.qx)) + } + // Dust film on the annex lower half: low shell band. + m.add(Prim.lathe([V2(ar + 0.012, 0), V2(ar + 0.02, 0.5), V2(ar + 0.012, 0.9)], segments: 22, seamTile: 1.0, material: film), Xform(translation: V3(ax, ar + 0.05, 0), rotation: OK.qx)) + m.add(Prim.cylinder(radius: 0.62, height: 0.07, bevel: 0.01, segments: 20, bevelSegments: 1, material: "plastic.matte:2A2D33"), Xform(translation: V3(ax + al, ar + 0.05, 0), rotation: OK.qx)) + m.add(Prim.torus(major: 0.7, minor: 0.05, segments: 20, sides: 6, material: "metal.titanium"), Xform(translation: V3(ax + al + 0.03, ar + 0.05, 0), rotation: OK.qx)) + for z: Float in [-1.2, 1.2] { + let (a, x) = OK.box(V3(0.7, 0.36, 0.04), V3(ax + 2.4, ar + 0.6, z * 1.7 * 0.85), glazing, r: 0.01); _ = (a, x) + } + // Access steps. + for k in 0..<3 { let (a, x) = OK.box(V3(0.5, 0.15, 1.4), V3(ax + al + 0.6 + Float(k) * 0.5, 0.075 + Float(2 - k) * 0.0 + Float(2 - k) * 0.15, 0), "metal.aluminum-brushed", r: 0.006); m.add(a, x) } + // Radiator bank at -Z on stands, with glowing coolant header. + for i in 0..<10 { + let x = -4.0 + Float(i) * 0.62 + m.add(Prim.extrude(Shape2D.rect(0.05, 2.4), depth: 3.0, bevel: 0.004, bevelSegments: 1, material: "thermal.radiator-white:E0DDD6"), Xform(translation: V3(x, 1.7, -8.4))) + } + for x: Float in [-4.3, 1.9] { for z: Float in [-9.8, -7.0] { m.add(OK.rod(V3(x, 0, z), V3(x, 0.45, z), r: 0.05, strut, sides: 6)) } } + let (hdr, hx) = OK.box(V3(6.4, 0.14, 0.14), V3(-1.2, 0.45, -8.4), "thermal.radiator-glow", r: 0.01); m.add(hdr, hx) + // Vent mast with weather head on the crown. + m.add(OK.rod(V3(0, R - 0.05, 0), V3(0, 9.2, 0), r: 0.05, strut, sides: 8)) + m.add(Prim.lathe([V2(0, 0), V2(0.35, 0.05), V2(0.45, 0.2)], segments: 14, seamTile: 1.0, material: "thermal.radiator-white"), Xform(translation: V3(0, 9.0, 0), rotation: simd_quatf(degrees: 90, axis: V3(1, 0, 0)))) + // Dust drifts against the footing and rocks. + for _ in 0..<14 { + let a = rng.float(0...(2 * .pi)), r = R + rng.float(0.5...1.2) + if cos(a) > 0.55 && abs(sin(a)) < 0.45 { continue } + let s = rng.float(0.7...1.5) + m.add(Prim.superellipsoid(V3(s * 1.4, s * 0.28, s), exponent: 2.4, subdivisions: 4, material: film), Xform(translation: V3(r * cos(a), 0.1, r * sin(a)), rotation: simd_quatf(angle: -a, axis: V3(0, 1, 0)))) + } + for _ in 0..<16 { + let p = V2(rng.float(-9...9), rng.float(-9...9)); if simd_length(p) < R + 1.5 || (p.y < -6.5) { continue } + let s = rng.float(0.12...0.5) + m.add(Prim.superellipsoid(V3(s, s * 0.65, s * 0.85), exponent: 3, subdivisions: 4, material: "rock.basalt"), Xform(translation: V3(p.x, height(p) + s * 0.15, p.y), rotation: simd_quatf(degrees: rng.float(0...360), axis: V3(0, 1, 0)))) + } + groundAO(&m, height: 0.1, floor: 0.7) + return LODModel(m) + } +} diff --git a/Sources/RealLibrary/Props/DataCenterSite/OffworldKit.swift b/Sources/RealLibrary/Props/DataCenterSite/OffworldKit.swift new file mode 100644 index 00000000..fb9ad45b --- /dev/null +++ b/Sources/RealLibrary/Props/DataCenterSite/OffworldKit.swift @@ -0,0 +1,31 @@ +import simd + +/// Shared helpers for the off-world site props. +enum OK { + static let qx = simd_quatf(degrees: -90, axis: V3(0, 0, 1)) + static let qz = simd_quatf(degrees: 90, axis: V3(1, 0, 0)) + + static func box(_ s: V3, _ c: V3, _ mat: MaterialKey, r: Float = 0.002) -> (Surface, Xform) { + if min(s.x, min(s.y, s.z)) > 0.25 { return (Prim.roundedBox(s, radius: r, bevelSegments: 1, material: mat), Xform(translation: c)) } + return (Prim.extrude(Shape2D.rect(s.x, s.y), depth: s.z, bevel: 0, bevelSegments: 1, material: mat), Xform(translation: c)) + } + static func rod(_ a: V3, _ b: V3, r: Float, _ mat: MaterialKey, sides: Int = 4) -> Surface { + Prim.tube([a, b], radii: [r, r], sides: sides, seamTile: 0.25, material: mat) + } + /// Cylinder along +X starting at x0. + static func cylX(_ r: Float, _ len: Float, x0: Float, y: Float, z: Float, _ mat: MaterialKey, seg: Int = 10, bevel: Float = 0) -> (Surface, Xform) { + (Prim.cylinder(radius: r, height: len, bevel: bevel, segments: seg, bevelSegments: 1, material: mat), Xform(translation: V3(x0, y, z), rotation: qx)) + } + /// Cylinder along +Z starting at z0. + static func cylZ(_ r: Float, _ len: Float, x: Float, y: Float, z0: Float, _ mat: MaterialKey, seg: Int = 10, bevel: Float = 0) -> (Surface, Xform) { + (Prim.cylinder(radius: r, height: len, bevel: bevel, segments: seg, bevelSegments: 1, material: mat), Xform(translation: V3(x, y, z0), rotation: qz)) + } + /// Cylinder along +Y. + static func cylY(_ r: Float, _ len: Float, at p: V3, _ mat: MaterialKey, seg: Int = 10, bevel: Float = 0) -> (Surface, Xform) { + (Prim.cylinder(radius: r, height: len, bevel: bevel, segments: seg, bevelSegments: 1, material: mat), Xform(translation: p)) + } + /// Low-poly bolt head (hex prism, ~20 tris) for budget-tight props. + static func bolt(_ m: inout Model, at p: V3, size: Float, _ mat: MaterialKey, rotation: simd_quatf = simd_quatf(angle: 0, axis: V3(0, 1, 0))) { + m.add(Prim.cylinder(radius: size * 0.6, height: size * 0.5, bevel: 0, segments: 6, bevelSegments: 1, material: mat), Xform(translation: p, rotation: rotation)) + } +} diff --git a/Sources/RealLibrary/Props/DataCenterSite/OrbitalDCModule.swift b/Sources/RealLibrary/Props/DataCenterSite/OrbitalDCModule.swift new file mode 100644 index 00000000..c40e44b9 --- /dev/null +++ b/Sources/RealLibrary/Props/DataCenterSite/OrbitalDCModule.swift @@ -0,0 +1,141 @@ +import simd +import Foundation + +/// Orbital data-center module, 20 x 6 x 6 m: a 15 m pressurized hull (white Whipple-shield skin with aluminum shield straps) +/// inside a 17 m square truss of carbon-grey chords, ring frames, diagonals and hull struts; gold MLI blanket end cones, +/// two axial docking ports with capture ring, petals, alignment pins and bolt circles, two radial ports, eight radiator +/// mounts with MLI hoses and blue/red quick disconnects, six external compute ORU boxes with handles and connector panels, +/// a grapple fixture, star tracker and omni antenna. Axis along X, base y = 0. +public struct OrbitalDCModule: RealAsset { + public static let id = "orbital-dc-module" + public static let summary = "Pressurized orbital compute module: Whipple-shield hull in a square truss, gold MLI end cones, docking ports, radiator mounts and external compute boxes." + public static let tags = ["prop", "space", "datacenter", "metal"] + public static let budget = 15000 + public static let author = "realityhd" + public static let preview = PreviewHint(azimuth: 32, elevation: 22, distance: 1.0, studio: true) + + /// Hull radius (m) and cylinder half length (m). + public var hullRadius: Float = 2.0 + public var hullHalf: Float = 7.5 + public var hull: MaterialKey = "thermal.radiator-white:DDDDD7" + public var strap: MaterialKey = "metal.aluminum-brushed" + public var blanket: MaterialKey = "mli.blanket" + public var truss: MaterialKey = "composite.carbon" + public var node: MaterialKey = "metal.titanium" + public var oru: MaterialKey = "metal.powdercoat:C9CBCC" + public var panel: MaterialKey = "plastic.matte:1B1D22" + public init() {} + + public func build(seed: UInt64) -> LODModel { + let rng = SeededRNG(seed: seed) + var m = Model(name: Self.id) + let R = hullRadius, H = hullHalf, cy: Float = 3 + func put(_ t: (Surface, Xform)) { m.add(t.0, t.1) } + // Hull: closed lathe about X with rounded shoulders. + let hp: [V2] = [V2(0, 0), V2(R - 0.3, 0), V2(R - 0.08, 0.1), V2(R, 0.3), V2(R, 2 * H - 0.3), V2(R - 0.08, 2 * H - 0.1), V2(R - 0.3, 2 * H), V2(0, 2 * H)] + m.add(Prim.lathe(hp, segments: 24, seamTile: 1.0, material: hull), Xform(translation: V3(-H, cy, 0), rotation: OK.qx)) + // Whipple shield straps, with a weld-seam bead each side. + for k in 0...5 { + let x = -H + 0.6 + Float(k) * (2 * H - 1.2) / 5 + let p: [V2] = [V2(R - 0.002, 0), V2(R + 0.035, 0.012), V2(R + 0.035, 0.1), V2(R - 0.002, 0.112)] + m.add(Prim.lathe(p, segments: 20, seamTile: 1.0, material: strap), Xform(translation: V3(x - 0.056, cy, 0), rotation: OK.qx)) + } + // End cones in gold MLI, collars, docking ports. + for s: Float in [-1, 1] { + let cp: [V2] = [V2(0.95, 0), V2(1.05, 0.5), V2(1.4, 1.0), V2(R - 0.3, 1.25), V2(R - 0.3, 1.3)] + let q = s < 0 ? OK.qx : simd_quatf(degrees: 90, axis: V3(0, 0, 1)) + m.add(Prim.lathe(cp, segments: 20, seamTile: 1.0, material: blanket), Xform(translation: V3(s * (H + 1.3), cy, 0), rotation: q)) + put(OK.cylX(0.98, 0.55, x0: s < 0 ? -H - 1.85 : H + 1.3, y: cy, z: 0, strap, seg: 20)) + put(OK.cylX(0.72, 0.2, x0: s < 0 ? -H - 2.05 : H + 1.85, y: cy, z: 0, node, seg: 20)) + let ring = Prim.torus(major: 0.84, minor: 0.07, segments: 16, sides: 4, material: node) + m.add(ring, Xform(translation: V3(s * (H + 2.1 + 0.05) , cy, 0), rotation: OK.qx)) + // Hatch plate with a window, alignment pins, capture petals. + put(OK.cylX(0.55, 0.03, x0: s < 0 ? -H - 1.95 - 0.04 : H + 1.95 + 0.01, y: cy, z: 0, panel, seg: 24)) + for k in 0..<3 { + let a = Float(k) * 2 * .pi / 3 + 0.5 + let c = V3(s * (H + 2.0 + 0.1), cy + 0.84 * cos(a), 0.84 * sin(a)) + let (p, xf) = OK.box(V3(0.22, 0.12, 0.2), c, node, r: 0.012) + m.add(p, Xform(translation: xf.translation, rotation: simd_quatf(degrees: -a * 180 / .pi + 90, axis: V3(1, 0, 0)))) + let pin = V3(s * (H + 2.2), cy + 0.62 * cos(a + 1.05), 0.62 * sin(a + 1.05)) + m.add(Prim.cylinder(radius: 0.022, height: 0.22, bevel: 0, segments: 6, bevelSegments: 1, material: "metal.steel"), Xform(translation: pin - V3(0.11, 0, 0) , rotation: OK.qx)) + } + var b = Model(name: "bc") + for k in 0..<8 { + let a = Float(k) * 2 * .pi / 8 + OK.bolt(&b, at: V3(s * (H + 1.55), cy + 1.02 * cos(a), 1.02 * sin(a)), size: 0.04, node, rotation: OK.qx) + } + m.add(b) + } + // Truss: 4 chords, 9 ring frames, diagonals, hull struts, corner nodes. + let hs: Float = 2.75, x0: Float = -8.5, bays = 8, bay = 17 / Float(bays) + let ys: [Float] = [cy - hs, cy + hs] + for yy in ys { for zz: Float in [-hs, hs] { + m.add(OK.rod(V3(x0, yy, zz), V3(-x0, yy, zz), r: 0.085, truss, sides: 6)) } } + for i in 0...bays { + let x = x0 + Float(i) * bay + for (a, b) in [(V3(x, ys[0], -hs), V3(x, ys[0], hs)), (V3(x, ys[1], -hs), V3(x, ys[1], hs)), (V3(x, ys[0], -hs), V3(x, ys[1], -hs)), (V3(x, ys[0], hs), V3(x, ys[1], hs))] { + m.add(OK.rod(a, b, r: 0.055, truss)) + } + for yy in ys { for zz: Float in [-hs, hs] { put(OK.box(V3(0.2, 0.2, 0.2), V3(x, yy, zz), node, r: 0.012)) } } + // Struts from frame mid-sides to the hull. + let dirs: [(V3, V3)] = [(V3(x, ys[0], 0), V3(x, cy - R * 0.97, 0)), (V3(x, ys[1], 0), V3(x, cy + R * 0.97, 0)), + (V3(x, cy, -hs), V3(x, cy, -R * 0.97)), (V3(x, cy, hs), V3(x, cy, R * 0.97))] + for (a, b) in dirs { m.add(OK.rod(a, b, r: 0.05, truss)) } + if i < bays { + let x1 = x + bay, f: Float = i % 2 == 0 ? 1 : -1 + m.add(OK.rod(V3(x, ys[0], -hs * f), V3(x1, ys[0], hs * f), r: 0.04, truss)) + m.add(OK.rod(V3(x, ys[1], hs * f), V3(x1, ys[1], -hs * f), r: 0.04, truss)) + m.add(OK.rod(V3(x, ys[0] , -hs), V3(x1, ys[1], -hs), r: 0.04, truss)) + m.add(OK.rod(V3(x, ys[1] , hs), V3(x1, ys[0], hs), r: 0.04, truss)) + } + } + // Radiator mounts on both Z faces: plate, hinge pins, QD pair, MLI hoses. + var bolts = Model(name: "mb") + for s: Float in [-1, 1] { for xm: Float in [-6.375, -2.125, 2.125, 6.375] { + put(OK.box(V3(0.55, 0.55, 0.06), V3(xm, cy, s * (hs + 0.12)), strap, r: 0.01)) + for dy: Float in [-0.17, 0.17] { + put(OK.cylZ(0.04, 0.22, x: xm + dy * 0, y: cy + dy, z0: s < 0 ? -hs - 0.19 : hs + 0.05, "metal.steel", seg: 8)) + put(OK.cylZ(0.05, 0.03, x: xm, y: cy + dy, z0: s < 0 ? -hs - 0.2 : hs + 0.17, dy < 0 ? "plastic.matte:2A6BD6" : "plastic.matte:D62A1E", seg: 8)) + } + put(OK.cylX(0.04, 0.5, x0: xm - 0.25, y: cy - 0.32, z: s * (hs + 0.12), node, seg: 10)) + m.add(Prim.tube(catmull([V3(xm - 0.15, cy - 0.17, s * (hs + 0.15)), V3(xm - 0.35, cy - 0.4, s * (hs + 0.2)), V3(xm - 0.55, cy - 0.6, s * (hs + 0.12))], per: 4), + radii: Array(repeating: 0.028, count: 9), sides: 6, seamTile: 0.3, material: blanket)) + for (bx, by) in [(-0.22, -0.22), (0.22, 0.22)] { + OK.bolt(&bolts, at: V3(xm + Float(bx), cy + Float(by), s * (hs + 0.19)), size: 0.04, node, rotation: OK.qz) + } + } } + m.add(bolts) + // Compute ORUs on the hull top: white boxes with handles and dark connector panels. + let topY = cy + R + for (ix, x) in [-4.6, 0.0, 4.6].enumerated() { for sz: Float in [-1, 1] { + let c = V3(Float(x), topY + 0.3 + 0.03, sz * 0.7) + put(OK.box(V3(1.5, 0.6, 1.1), c, oru, r: 0.02)) + put(OK.box(V3(1.3, 0.1, 0.9), V3(c.x, topY + 0.03 - 0.02, c.z), strap, r: 0.01)) + put(OK.box(V3(1.1, 0.012, 0.35), V3(c.x, c.y + 0.3 + 0.004, c.z - sz * 0.25), panel, r: 0.003)) + for hx: Float in [-0.45, 0.45] { + let hb = V3(c.x + hx, c.y + 0.3, c.z + sz * 0.3) + m.add(OK.rod(hb + V3(0, 0.06, -0.2), hb + V3(0, 0.06, 0.2), r: 0.012, "metal.stainless", sides: 4)) + m.add(OK.rod(hb, hb + V3(0, 0.06, 0), r: 0.012, "metal.stainless", sides: 4)) + } + // Connector row on the panel. + for k in 0..<5 { put(OK.cylY(0.026, 0.02, at: V3(c.x - 0.4 + Float(k) * 0.2, c.y + 0.31, c.z - sz * 0.25), node, seg: 10)) } + _ = ix + } } + // Grapple fixture, star tracker and omni antenna. + put(OK.cylY(0.07, 0.25, at: V3(-6.2, topY, 0), node, seg: 12)) + put(OK.cylY(0.14, 0.03, at: V3(-6.2, topY + 0.25, 0), node, seg: 16)) + put(OK.cylY(0.035, 0.08, at: V3(-6.2, topY + 0.28, 0), "metal.steel", seg: 8)) + put(OK.box(V3(0.3, 0.24, 0.3), V3(6.4, topY + 0.12, 0), strap, r: 0.01)) + put(OK.cylY(0.1, 0.35, at: V3(6.4, topY + 0.24, 0), panel, seg: 14)) + m.add(OK.rod(V3(-0.5, topY + 0.02, -1.4), V3(-0.5, topY + 1.6 - 0.3, -1.4), r: 0.025, "metal.stainless", sides: 4)) + put(OK.cylY(0.06, 0.1, at: V3(-0.5, topY + 1.3, -1.4), panel, seg: 12)) + // Radial ports on +Z mid-hull sides? kept on the hull flanks between struts: nadir and zenith hatches. + put(OK.cylY(0.5, 0.2, at: V3(2.1, topY, 0), strap, seg: 24)) + put(OK.cylY(0.4, 0.04, at: V3(2.1, topY + 0.2, 0), panel, seg: 20)) + // Re-seat on the ground and report. + m.add(Prim.roundedBox(V3(0.5, 0.01, 0.5), radius: 0.003, bevelSegments: 1, material: node), Xform(translation: V3(0, 0.005, 0))) + groundAO(&m, height: 0.25, floor: 0.6) + _ = rng + return LODModel(m) + } +} diff --git a/Sources/RealLibrary/Props/DataCenterSite/SolarArrayWing.swift b/Sources/RealLibrary/Props/DataCenterSite/SolarArrayWing.swift new file mode 100644 index 00000000..ca727925 --- /dev/null +++ b/Sources/RealLibrary/Props/DataCenterSite/SolarArrayWing.swift @@ -0,0 +1,78 @@ +import simd +import Foundation + +/// Z-fold rigid-panel solar array wing, 30 x 0.1 x 6 m: ten 2.7 m panels, each a carbon-skinned honeycomb tray with an aluminum +/// edge frame and a Kapton-backed grid of 8 x 4 cm triple-junction cells (cell grid is the material), hinged in a chain +/// with titanium knuckles and pins, a root spar with drive housing and slip-ring box, and a tip lock bar. +/// Parts: base root spar, deploy (hinge, panel 1), fold2..fold10 (each mimics deploy at -/+2x). +/// States: deployed, half, stowed. Base y = 0, wing runs along +X from x = -15. +public struct SolarArrayWing: RealArticulated { + public static let id = "solar-array-wing" + public static let summary = "Z-fold solar array wing: ten hinged cell-grid panels with carbon trays, edge frames, knuckles and a root drive spar." + public static let tags = ["prop", "articulated", "space", "solar", "datacenter"] + public static let budget = 4000 + public static let author = "realityhd" + public static let preview = PreviewHint(azimuth: 30, elevation: 40, distance: 1.0, studio: true) + + /// Panel count and width along Z (m). + public var panelCount = 10 + public var panelWidth: Float = 6 + public var cells: MaterialKey = "solar.cell" + public var back: MaterialKey = "solar.kapton" + public var tray: MaterialKey = "composite.carbon" + public var frame: MaterialKey = "metal.aluminum-brushed" + public var hardware: MaterialKey = "metal.titanium" + public init() {} + + public func rig(seed: UInt64) -> Rig { + var rng = SeededRNG(seed: seed) + var rig = Rig(name: Self.id, lods: 1) + let W = panelWidth, n = panelCount, x0: Float = -14.6 + let pitch = (15 - 0.4 - x0 - 0.0 + 14.6 - 0.0) / Float(n) // panel pitch + let pl = (30 - 0.4) / Float(n) - 0.02 + _ = pitch + let T: Float = 0.05, y: Float = 0.05 + func B(_ s: V3, _ c: V3, _ mat: MaterialKey, r: Float = 0.002) { let (a, x) = OK.box(s, c, mat, r: r); rig.base[0].add(a, x) } + // Root spar along Z with drive housing, slip-ring box and cable loops. + let (spar, sx) = OK.cylZ(0.045, W, x: -14.75, y: 0.05, z0: -W / 2, hardware, seg: 16); rig.base[0].add(spar, sx) + B(V3(0.3, 0.09, 0.6), V3(-14.85, 0.045, 0), frame, r: 0.006) + B(V3(0.18, 0.07, 0.4), V3(-14.88, 0.045, 1.2), "plastic.matte:2A2D33", r: 0.004) + for sz: Float in [-1, 1] { + rig.base[0].add(Prim.tube(catmull([V3(-14.85, 0.06, sz * 0.3), V3(-14.95, 0.075, sz * 0.9), V3(-14.8, 0.07, sz * 1.5)], per: 4), radii: Array(repeating: 0.012, count: 9), sides: 6, seamTile: 0.2, material: "mli.blanket")) + } + var bolts = Model(name: "bolts") + for k in 0..<4 { OK.bolt(&bolts, at: V3(-14.85, 0.09, -0.25 + Float(k % 2) * 0.16 + (k < 2 ? 0 : 1.2) - 0.0), size: 0.012, hardware) } + rig.base[0].add(bolts) + + var parent: String? = nil + for i in 0.. Rig { + var rng = SeededRNG(seed: seed) + var rig = Rig(name: Self.id, lods: 1) + let W = panelWidth, T: Float = 0.078, px: Float = -4.78, py: Float = 0.045 + let L: Float = 4.77, rail: Float = 0.04 + let kx: Float = 0.0 // mid hinge x + func B(_ s: V3, _ c: V3, _ mat: MaterialKey, r: Float = 0.002) { let (a, x) = OK.box(s, c, mat, r: r); rig.base[0].add(a, x) } + // Root bracket: machined block, hinge ears, bolts. + B(V3(0.22, 0.09, W), V3(-4.89, 0.045, 0), frame, r: 0.004) + B(V3(0.05, 0.09, W - 0.1), V3(-4.995, 0.045, 0), hardware, r: 0.003) + var bolts = Model(name: "bolts") + for k in 0..<6 { let z = -W / 2 + 0.2 + Float(k) * (W - 0.4) / 5 + OK.bolt(&bolts, at: V3(-4.9, 0.09, z), size: 0.016, hardware) } + rig.base[0].add(bolts) + for k in 0..<4 { + let z = -1.5 + Float(k) * 1.0 + let (a, x) = OK.cylZ(0.04, 0.4, x: px, y: py, z0: z - 0.2, hardware, seg: 8); rig.base[0].add(a, x) + let (c, y) = OK.cylZ(0.022, 0.5, x: px, y: py, z0: z - 0.25, "metal.steel", seg: 8); rig.base[0].add(c, y) + } + // Fluid lines: gold MLI hoses with QD couplers entering the bracket from behind. + for z: Float in [-1.2, 1.2] { + rig.base[0].add(Prim.tube(catmull([V3(-4.98, 0.05, z), V3(-5.0, 0.07, z + 0.15 * (z < 0 ? -1 : 1)), V3(-4.9, 0.06, z + 0.3 * (z < 0 ? -1 : 1))], per: 4), + radii: [0.028, 0.028, 0.028, 0.028, 0.028, 0.028, 0.028, 0.028, 0.028], sides: 8, seamTile: 0.3, material: blanket)) + let (q, qx) = OK.cylX(0.032, 0.07, x0: -4.99, y: 0.05, z: z, "plastic.matte:2A6BD6", seg: 8); rig.base[0].add(q, qx) + } + + rig.part("deploy", pivot: V3(px, py, 0), joint: .hinge(axis: V3(0, 0, 1), 0...90, duration: 4)) + rig.part("tubes1", parent: "deploy", pivot: V3(px, py, 0), joint: .fixed, options: 2) + rig.part("fold", parent: "deploy", pivot: V3(kx, py, 0), joint: Joint(.revolute, axis: V3(0, 0, 1), range: -180...0, duration: 4, mimic: .init("deploy", ratio: -2))) + rig.part("tubes2", parent: "fold", pivot: V3(kx, py, 0), joint: .fixed, options: 2) + + func segment(_ part: String, _ tubes: String, x0: Float, x1: Float, tip: Bool) { + let xc = (x0 + x1) / 2, len = x1 - x0 + func A(_ s: V3, _ c: V3, _ mat: MaterialKey, r: Float = 0.002) { let (a, x) = OK.box(s, c, mat, r: r); rig.add(a, x, to: part) } + for sz: Float in [-1, 1] { A(V3(len, T, rail), V3(xc, T / 2, sz * (W / 2 - rail / 2)), frame, r: 0.004) } + for xe in [x0 + rail / 2, x1 - rail / 2] { A(V3(rail, T, W - 2 * rail), V3(xe, T / 2, 0), frame, r: 0.004) } + A(V3(len - 2 * rail, T - 0.016, W - 2 * rail), V3(xc, T / 2, 0), "metal.casework", r: 0.001) + A(V3(len - 2 * rail, 0.006, W - 2 * rail), V3(xc, 0.004, 0), "\(coating):D9D9D3", r: 0.001) + let n = 6, gap: Float = 0.008, tw = (len - 2 * rail - Float(n + 1) * gap) / Float(n) + for i in 0.. Surface { + Prim.extrude(Shape2D.rect(s.x, s.y), depth: s.z, bevel: 0, bevelSegments: 1, material: mat) + } + + public func build(seed: UInt64) -> LODModel { + var rng = SeededRNG(seed: seed) + var m = Model(name: Self.id) + let ep = "metal.powdercoat:8C9196", st = "metal.stainless" + // Perimeter frame. + for s: Float in [-1, 1] { + m.add(bx(V3(0.6, 0.05, 0.02), 0.003, ep), Xform(translation: V3(0, 0.025, s * 0.29))) + m.add(bx(V3(0.02, 0.05, 0.56), 0.003, ep), Xform(translation: V3(s * 0.29, 0.025, 0))) + } + // Bearing bars along X, cross rods along Z. + for i in 0..<13 { + let z = -0.264 + Float(i) * 0.044 + m.add(bx(V3(0.56, 0.044, 0.006), 0.001, ep), Xform(translation: V3(0, 0.025, z))) + } + for i in 0..<5 { + let x = -0.22 + Float(i) * 0.11 + m.add(bx(V3(0.006, 0.01, 0.56), 0.001, ep), Xform(translation: V3(x, 0.035, 0))) + } + // Lifting-hole plate and corner screws (one worn scuff via a lighter patch). + for sx: Float in [-1, 1] { for sz: Float in [-1, 1] { + m.add(Prim.cylinder(radius: 0.012 / 2, height: 0.012 * 0.5, bevel: 0, segments: 6, material: st), Xform(translation: V3(sx * 0.29, 0.047, sz * 0.29))) + } } + m.add(bx(V3(0.05, 0.004, 0.02), 0.001, "metal.powdercoat:C8B070"), Xform(translation: V3(0.12 + rng.float(-0.02...0.02), 0.048, 0.29))) + let b = m.bounds; m = m.transformed(Xform(translation: V3(0, -b.min.y, 0))) + return LODModel(m) + } +} diff --git a/Sources/RealLibrary/Props/Fab/ClusterTool.swift b/Sources/RealLibrary/Props/Fab/ClusterTool.swift new file mode 100644 index 00000000..7369dfbf --- /dev/null +++ b/Sources/RealLibrary/Props/Fab/ClusterTool.swift @@ -0,0 +1,281 @@ +import simd +import Foundation + +/// Parameters for a hexagonal-transfer-chamber cluster tool (etch, CVD/ALD, PVD). Angles are degrees in the XZ plane +/// measured from +X toward +Z, so 90 faces the EFEM at the front. Props pick chamber count, materials and lid parts. +public struct ClusterSpec { + public var chamberAngles: [Float] = [0, 180, -60, -120] + public var loadLockAngles: [Float] = [60, 120] + public var lidPrefix = "lid" + public var hexApothem: Float = 0.6 + public var hexCenterZ: Float = -0.15 + public var chamberRadius: Float = 0.46 + public var chamberBase: Float = 0.82 + public var chamberHeight: Float = 0.4 + public var chamberMat: MaterialKey = "metal.satin-aluminum" + public var lidMat: MaterialKey = "metal.satin-aluminum" + public var frameMat: MaterialKey = "metal.powdercoat:34373C" + public var skinMat: MaterialKey = "metal.powdercoat:EDEDE9" + public var matchBoxes = true + public var gasSticks = 6 + public var portX: [Float] = [-0.9, 0, 0.9] + public var efemZ: ClosedRange = 1.0...1.6 + public var efemWidth: Float = 3.0 + public var efemHeight: Float = 2.05 + /// Per-chamber extras hook (lid hardware, ampoule cabinets, pumps). Args: kit, chamber centre, outward dir, tangent, lid top y, lid part name, index. + public var chamberExtra: ((ClusterKit, V3, V3, V3, Float, String, Int) -> Void)? = nil + public init() {} +} + +/// Geometry sink shared by cluster builders: one Model per LOD plus named rig parts. +public final class ClusterKit { + public var rig: Rig + public var m: Model + public let hi: Bool + let lod: Int + public init(rig: Rig, lod: Int) { + self.rig = rig; self.lod = lod; hi = lod == 0; m = Model(name: rig.name) + } + public func put(_ s: Surface, _ x: Xform, _ name: String? = nil) { + if let n = name { rig.add(s, x, to: n, lods: lod...lod) } else { m.add(s, x) } + } + public func bx(_ s: V3, _ c: V3, _ mat: MaterialKey, r: Float = 0.004, yaw: Float = 0, name: String? = nil) { + let surf = r > 0.003 && s.min() > 0.12 + ? Prim.extrude(Shape2D.rect(s.x, s.y), depth: s.z, bevel: min(r, s.min() * 0.4), bevelSegments: 1, material: mat) + : cuboid(s, material: mat) + put(surf, Xform(translation: c, rotation: simd_quatf(degrees: yaw, axis: V3(0, 1, 0))), name) + } + public func cyl(_ r: Float, _ h: Float, _ c: V3, _ mat: MaterialKey, seg: Int = 16, name: String? = nil) { + put(Prim.cylinder(radius: r, height: h, bevel: 0, segments: hi ? seg : max(5, seg / 2), material: mat), Xform(translation: c), name) + } + public func tube(_ pts: [V3], _ r: Float, _ mat: MaterialKey, sides: Int = 8) { + m.add(Prim.tube(pts, radii: Array(repeating: r, count: pts.count), sides: hi ? sides : 4, seamTile: 0.25, material: mat), .identity) + } +} + +public enum ClusterTool { + static func dir(_ a: Float) -> V3 { V3(cos(a * .pi / 180), 0, sin(a * .pi / 180)) } + static func tang(_ a: Float) -> V3 { V3(-sin(a * .pi / 180), 0, cos(a * .pi / 180)) } + + /// Chamber centre for a face angle. + public static func chamberCenter(_ s: ClusterSpec, _ a: Float) -> V3 { + V3(0, 0, s.hexCenterZ) + dir(a) * (s.hexApothem + 0.1 + s.chamberRadius) + } + + /// Declares the hinged lids (pivot at the outer rim, tangent axis). + public static func declareParts(_ rig: inout Rig, _ s: ClusterSpec) { + for (i, a) in s.chamberAngles.enumerated() { + let c = chamberCenter(s, a) + let top = s.chamberBase + s.chamberHeight + let pivot = c + dir(a) * (s.chamberRadius + 0.04) + V3(0, top, 0) + let t = tang(a) + rig.part("\(s.lidPrefix)-\(i + 1)", pivot: pivot, joint: .hinge(axis: t, -105...0, duration: 1.4)) + } + } + + /// Builds one LOD of the cluster: plinth, transfer chamber, load locks, chambers, gas sticks, cables, EFEM. + public static func assemble(_ k: ClusterKit, _ s: ClusterSpec, status: V3) { + let hi = k.hi + let steel: MaterialKey = "metal.stainless", anod: MaterialKey = "metal.anodized" + let black: MaterialKey = "plastic.matte:1E2023" + let frame = s.frameMat, skin = s.skinMat + let y0: Float = 0.12, cz = s.hexCenterZ, a0 = s.hexApothem + let yb = s.chamberBase + let ezf = s.efemZ.upperBound, ezb = s.efemZ.lowerBound + let ew = s.efemWidth, eh = s.efemHeight + + // Cleanroom grate floor and central plinth. + k.m.add(Prim.extrude(Shape2D.rect(4.0, 4.0), depth: 0.05, bevel: 0.004, bevelSegments: 1, material: "metal.grate-iron"), + Xform(translation: V3(0, 0.025, 0), rotation: simd_quatf(degrees: 90, axis: V3(1, 0, 0)))) + + // Transfer chamber: hex body, lid ring, viewport, robot motor cap. + let R = a0 / cos(Float.pi / 6) + func hex(_ r: Float) -> [V2] { (0..<6).map { let t = (30 + Float($0) * 60) * Float.pi / 180; return V2(r * cos(t), r * sin(t)) } } + func hexPrism(_ r: Float, _ y0: Float, _ y1: Float, _ mat: MaterialKey, bevel: Float = 0.006) { + k.put(Prim.extrude(hex(r), depth: y1 - y0, bevel: bevel, bevelSegments: 1, material: mat), + Xform(translation: V3(0, (y0 + y1) / 2, cz), rotation: simd_quatf(degrees: 90, axis: V3(1, 0, 0)))) + } + hexPrism(R - 0.04, y0, yb, frame) + hexPrism(R, yb, yb + 0.36, s.chamberMat) + hexPrism(R + 0.012, yb + 0.36, yb + 0.4, anod) + hexPrism(R - 0.03, yb + 0.4, yb + 0.46, s.lidMat) + hexPrism(R - 0.2, yb + 0.46, yb + 0.5, anod) + k.cyl(0.13, 0.12, V3(0, yb + 0.5, cz), anod, seg: 20) + k.cyl(0.085, 0.08, V3(0, yb + 0.62, cz), "glass.window-tint", seg: 16) + k.cyl(0.1, 0.02, V3(0, yb + 0.7, cz), steel, seg: 16) + for i in 0..<6 where hi { + let t = (Float(i) * 60 + 20) * Float.pi / 180 + k.cyl(0.011, 0.014, V3(0.31 * cos(t), yb + 0.5, cz + 0.31 * sin(t)), steel, seg: 6) + } + // Ion gauge and pump-out tee on the transfer lid. + k.cyl(0.03, 0.15, V3(0.25, yb + 0.5, cz - 0.15), steel, seg: 10) + k.cyl(0.04, 0.03, V3(0.25, yb + 0.65, cz - 0.15), black, seg: 10) + + // Process chambers. + for (i, a) in s.chamberAngles.enumerated() { + let c = chamberCenter(s, a), d = dir(a), t = tang(a), rc = s.chamberRadius + let top = yb + s.chamberHeight + // Slit valve housing between hex face and chamber. + let sv = V3(0, 0, cz) + d * (a0 + 0.06) + k.bx(V3(0.46, 0.34, 0.12), V3(sv.x, yb + 0.2, sv.z), anod, r: 0.004, yaw: 90 - a) + k.bx(V3(0.5, 0.05, 0.14), V3(sv.x, yb + 0.4, sv.z), steel, r: 0.003, yaw: 90 - a) + // Pedestal, body and flange rings. + k.bx(V3(rc * 1.9, yb - y0, rc * 1.9), V3(c.x, (yb + y0) / 2, c.z), frame, r: 0.006, yaw: 90 - a) + let body: [V2] = [V2(0, yb), V2(rc + 0.01, yb), V2(rc + 0.01, yb + 0.05), V2(rc - 0.01, yb + 0.07), V2(rc - 0.01, top - 0.09), + V2(rc + 0.03, top - 0.07), V2(rc + 0.03, top - 0.02), V2(rc + 0.04, top - 0.02), V2(rc + 0.04, top), V2(0, top)] + k.put(Prim.lathe(body, segments: hi ? 16 : 8, seamTile: 0.45, material: s.chamberMat), Xform(translation: V3(c.x, 0, c.z))) + if hi { + for j in 0..<8 { + let q = Float(j) / 8 * 2 * .pi + k.cyl(0.009, 0.012, V3(c.x + (rc + 0.02) * cos(q), top - 0.05 + 0.0, c.z + (rc + 0.02) * sin(q)), steel, seg: 6) + } + // Heater jacket stripes and service port with blank flange. + k.bx(V3(0.1, 0.1, 0.05), c + d * (rc + 0.03) + t * 0.15 + V3(0, yb + 0.22 - c.y, 0), anod, r: 0.003, yaw: 90 - a) + k.cyl(0.04, 0.03, c + t * -0.18 + d * (rc * 0.98) + V3(0, yb + 0.15, 0), steel, seg: 10) + } + k.cyl(rc + 0.02, 0.03, V3(c.x, yb + 0.12, c.z), anod, seg: hi ? 16 : 8) + // Quarter-turn latches around the flange. + for j in 0..<4 where hi { + let q = (Float(j) * 90 + 45) * Float.pi / 180 + k.bx(V3(0.05, 0.05, 0.03), V3(c.x + (rc + 0.05) * cos(q), top - 0.04, c.z + (rc + 0.05) * sin(q)), steel, r: 0.002, yaw: 90 - Float(j) * 90 - 45) + } + // Lid (named): disc, rim, hinge knuckles, handle, match box, RF strap. + let ln = "\(s.lidPrefix)-\(i + 1)" + let lid: [V2] = [V2(0, 0.0), V2(rc + 0.04, 0.0), V2(rc + 0.04, 0.045), V2(rc, 0.06), V2(0.22, 0.075), V2(0.2, 0.11), V2(0, 0.11)] + k.put(Prim.lathe(lid, segments: hi ? 16 : 8, seamTile: 0.45, material: s.lidMat), Xform(translation: V3(c.x, top + 0.0015, c.z)), ln) + let outer = c + d * (rc + 0.04) + for q: Float in [-1, 1] { + k.bx(V3(0.06, 0.05, 0.05), V3(outer.x + t.x * q * 0.18, top + 0.03, outer.z + t.z * q * 0.18), steel, r: 0.002, yaw: 90 - a, name: ln) + } + k.bx(V3(0.2, 0.03, 0.03), V3(c.x - d.x * (rc - 0.03), top + 0.1, c.z - d.z * (rc - 0.03)), steel, r: 0.002, yaw: 90 - a, name: ln) + if s.matchBoxes { + let mc = c + d * 0.06 + k.put(cuboid(V3(0.34, 0.26, 0.3), material: "metal.powdercoat:C9CBCC").rotated(yaw: 90 - a), Xform(translation: V3(mc.x, top + 0.24, mc.z)), ln) + k.put(cuboid(V3(0.36, 0.03, 0.32), material: steel).rotated(yaw: 90 - a), Xform(translation: V3(mc.x, top + 0.375, mc.z)), ln) + k.put(cuboid(V3(0.2, 0.08, 0.01), material: black).rotated(yaw: 90 - a), Xform(translation: V3(mc.x + d.x * 0.151, top + 0.26, mc.z + d.z * 0.151)), ln) + k.put(Prim.cylinder(radius: 0.035, height: 0.12, bevel: 0, segments: hi ? 14 : 6, material: "metal.anodized"), Xform(translation: V3(mc.x, top + 0.39, mc.z)), ln) + k.put(Prim.cylinder(radius: 0.05, height: 0.14, bevel: 0, segments: hi ? 16 : 6, material: steel), Xform(translation: V3(c.x, top + 0.11, c.z)), ln) + if hi { + for q: Float in [-1, 1] { + k.put(Prim.cylinder(radius: 0.012, height: 0.05, bevel: 0, segments: 8, material: "metal.brass"), + Xform(translation: V3(mc.x - d.x * 0.0 + t.x * q * 0.09, top + 0.5, mc.z + t.z * q * 0.09)), ln) + } + } + } + // Gas tray beyond the outer rim: posts, tray, stainless sticks with valves and MFCs. + let gc = c + d * (rc + 0.2) + let gy = top + 0.16 + for q: Float in [-1, 1] { for w: Float in [0, 1] { + let p = gc + t * q * 0.27 + d * (w * 0.2 - 0.1) + k.bx(V3(0.04, gy - y0, 0.04), V3(p.x, (gy + y0) / 2, p.z), skin, r: 0.001) + } } + k.bx(V3(0.6, 0.03, 0.34), V3(gc.x, gy, gc.z), "metal.powdercoat:9EA3A8", r: 0.002, yaw: 90 - a) + let n = s.gasSticks + for j in 0.. Surface { + if yaw == 0 { return self } + return self.transformed(Xform(rotation: simd_quatf(degrees: yaw, axis: V3(0, 1, 0)))) + } +} diff --git a/Sources/RealLibrary/Props/Fab/CmpTool.swift b/Sources/RealLibrary/Props/Fab/CmpTool.swift new file mode 100644 index 00000000..2943b968 --- /dev/null +++ b/Sources/RealLibrary/Props/Fab/CmpTool.swift @@ -0,0 +1,150 @@ +import simd +import Foundation + +/// CMP polisher, generic, 4 x 2.2 x 3 m. White powdercoat polish module with a large tinted front window over three +/// polishing platens (grooved grey pads, slurry arms, carrier heads on ceiling rails), a post-CMP cleaner module with +/// brush rollers behind a side window, EFEM with three FOUP load ports and a status tower. Named parts: platen-1...platen-3 spin. +public struct CmpTool: RealArticulated { + public static let id = "cmp-tool" + public static let summary = "CMP tool: white enclosure with large window, 3 spinning platens with pads, slurry arms, carrier heads, cleaner module, load ports." + public static let tags = ["prop", "semiconductor", "articulated"] + public static let budget = 12000 + public static let author = "realityhd" + public static let preview = PreviewHint(azimuth: 30, elevation: 16, distance: 1.0, studio: true) + + /// Enclosure powdercoat (sRGB hex). + public var color: UInt32 = 0xEDEDE9 + public init() {} + + static let platens: [V3] = [V3(-1.45, 0, 0.55), V3(-0.4, 0, 0.55), V3(-0.92, 0, -0.45)] + + public func rig(seed: UInt64) -> Rig { + _ = seed + var rig = Rig(name: Self.id, lods: 2, switchDistances: [10]) + let white: MaterialKey = "metal.powdercoat:" + String(format: "%06X", color) + let dark: MaterialKey = "metal.powdercoat:34373C", inner: MaterialKey = "metal.powdercoat:C4C8CC" + let steel: MaterialKey = "metal.stainless", anod: MaterialKey = "metal.anodized", black: MaterialKey = "plastic.matte:1E2023" + let glass: MaterialKey = "glass.window-tint" + let y0: Float = 0.12, top: Float = 2.2, padY: Float = 0.92 + for (i, p) in Self.platens.enumerated() { + rig.part("platen-\(i + 1)", pivot: V3(p.x, padY, p.z), joint: .hinge(axis: .up, -360...360, duration: 2)) + } + for l in 0..<2 { + let k = ClusterKit(rig: rig, lod: l) + let hi = k.hi + FabParts.grate(k, w: 4.0, d: 3.0) + // Plinths. + k.bx(V3(3.9, y0 - 0.04, 2.9), V3(0, (y0 + 0.05) / 2, 0), dark) + // Polish module shell x -2..0.4, z -1.5..1.5. + let px0: Float = -2, px1: Float = 0.4, pz0: Float = -1.5, pz1: Float = 1.5 + let cx = (px0 + px1) / 2, w = px1 - px0 + k.bx(V3(w - 0.04, 0.03, pz1 - pz0 - 0.04), V3(cx, y0 + 0.02, 0), "metal.powdercoat:A8ACB0", r: 0) // interior floor + k.bx(V3(w, 0.03, 3.0), V3(cx, top - 0.015, 0), white, r: 0.004) // roof + k.bx(V3(w, top - y0, 0.03), V3(cx, (top + y0) / 2, pz0 + 0.015), white, r: 0.004) // back wall + k.bx(V3(0.03, top - y0, 3.0), V3(px0 + 0.015, (top + y0) / 2, 0), white, r: 0.004) // left wall + k.bx(V3(0.03, top - y0, 1.0), V3(px1 - 0.015, (top + y0) / 2, -1.0), inner, r: 0.002) // right wall behind cleaner + k.bx(V3(0.03, top - y0, 0.6), V3(px1 - 0.015, (top + y0) / 2, 1.2), inner, r: 0.002) + k.bx(V3(w - 0.06, top - y0 - 0.06, 0.02), V3(cx, (top + y0) / 2, pz0 + 0.04), inner, r: 0) // back liner + // Front face with window opening x -1.8..0.2, y 0.7..1.9. + k.bx(V3(w, 0.5 - y0, 0.03), V3(cx, (0.5 + y0) / 2, pz1 - 0.015), white, r: 0.004) + k.bx(V3(w, top - 1.9, 0.03), V3(cx, (top + 1.9) / 2, pz1 - 0.015), white, r: 0.004) + k.bx(V3(0.2, 1.4, 0.03), V3(-1.9, 1.2, pz1 - 0.015), white, r: 0.004) + k.bx(V3(0.2, 1.4, 0.03), V3(0.3, 1.2, pz1 - 0.015), white, r: 0.004) + k.bx(V3(2.0, 1.4, 0.01), V3(-0.8, 1.2, pz1 - 0.02), glass, r: 0) + for (c, s) in [(V3(-0.8, 0.5, pz1 + 0.005), V3(2.06, 0.03, 0.04)), (V3(-0.8, 1.9, pz1 + 0.005), V3(2.06, 0.03, 0.04)), + (V3(-1.8, 1.3, pz1 + 0.005), V3(0.03, 1.4, 0.04)), (V3(0.2, 1.2, pz1 + 0.005), V3(0.03, 1.2, 0.04))] { + k.bx(s, c, anod, r: 0.002) + } + if hi { + for q in 0..<5 { k.bx(V3(0.01, 0.35, 0.012), V3(-1.75 + Float(q) * 0.5, 0.3, pz1 + 0.01), "metal.powdercoat:D8D9D5", r: 0) } + k.bx(V3(0.3, 0.012, 0.012), V3(-1.2, 1.97, pz1 + 0.012), black, r: 0) + // Left end vent slots and hazard strip. + for q in 0..<6 { k.bx(V3(0.01, 0.02, 0.8), V3(px0 - 0.003, 1.9 + Float(q) * 0.0, -0.2) + V3(0, -Float(q) * 0.04, 0), black, r: 0) } + k.bx(V3(0.006, 0.06, 1.0), V3(px0 - 0.004, 0.3, 0.0), "plastic.yellow", r: 0.0005) + } + // Gantry rails and carrier heads. + for (i, p) in Self.platens.enumerated() { + k.bx(V3(0.07, 0.08, 2.8), V3(p.x, 1.82, 0), anod, r: 0.002) + let hz = p.z + (i == 2 ? 0.0 : -0.05) + k.bx(V3(0.14, 0.12, 0.24), V3(p.x, 1.72, hz), steel, r: 0.004) + k.cyl(0.035, 0.34, V3(p.x, 1.52, hz), steel, seg: 10) + k.cyl(0.19, 0.12, V3(p.x, 1.26, hz), anod, seg: 18) + k.cyl(0.17, 0.05, V3(p.x, 1.16, hz), "metal.powdercoat:2A2C2F", seg: 18) + k.cyl(0.14, 0.02, V3(p.x, 1.125, hz), "silicon.thinfilm", seg: 18) + if hi { for j in 0..<6 { let t = Float(j) / 6 * 2 * .pi; k.cyl(0.01, 0.016, V3(p.x + 0.16 * cos(t), 1.31, hz + 0.16 * sin(t)), steel, seg: 6) } } + } + // Platens: table, pad with grooves, named spinning parts. + for (i, p) in Self.platens.enumerated() { + let name = "platen-\(i + 1)" + let prof: [V2] = [V2(0, 0.0), V2(0.43, 0.0), V2(0.43, -0.05), V2(0.2, -0.09), V2(0.2, -0.12), V2(0, -0.12)] + k.put(Prim.lathe(prof.map { V2($0.x, $0.y + padY - 0.03) }, segments: hi ? 22 : 10, seamTile: 0.4, material: steel), Xform(translation: V3(p.x, 0, p.z)), name) + var pad: [V2] = [V2(0, 0.032), V2(0.1, 0.032)] + if hi { + for g in 0..<4 { + let r = 0.1 + Float(g) * 0.06 + pad += [V2(r + 0.0, 0.032), V2(r + 0.004, 0.025), V2(r + 0.012, 0.025), V2(r + 0.016, 0.032)] + } + pad += [V2(0.4, 0.032)] + } else { pad += [V2(0.4, 0.032)] } + pad += [V2(0.4, 0.0), V2(0, 0.0)] + k.put(Prim.lathe(pad.map { V2($0.x, $0.y + padY - 0.032) }, segments: hi ? 28 : 12, seamTile: 0.4, material: "plastic.pad"), Xform(translation: V3(p.x, 0, p.z)), name) + k.cyl(0.05, 0.004, V3(p.x + 0.09, padY + 0.002, p.z - 0.1), "plastic.matte:4C4E50", seg: 10, name: name) // slurry stain + k.cyl(0.18, 0.003, V3(p.x - 0.1, padY + 0.0015, p.z + 0.12), "plastic.matte:6A6B68", seg: 12, name: name) + // Static: splash pan, conditioner post. + k.cyl(0.5, 0.03, V3(p.x, padY - 0.3, p.z), "metal.powdercoat:A8ACB0", seg: hi ? 24 : 10) + k.cyl(0.3, 0.7, V3(p.x, y0 + 0.35 + 0.02, p.z), dark, seg: hi ? 16 : 8) + // Slurry arm: post, swan neck, nozzle. + let bz = p.z - 0.5, bx0 = p.x + (i == 1 ? 0.3 : -0.3) + k.cyl(0.03, 1.0 - y0, V3(bx0, (1.0 + y0) / 2, bz), steel, seg: 8) + let path = catmull([V3(bx0, 1.0, bz), V3(bx0, 1.28, bz + 0.02), V3(bx0 + (p.x - bx0) * 0.5, 1.28, bz + 0.3), V3(p.x - (p.x - bx0) * 0.1, 1.2, p.z - 0.12), V3(p.x, 1.08, p.z - 0.1)], per: hi ? 4 : 2) + k.tube(path, 0.012, steel, sides: 8) + k.cyl(0.008, 0.06, V3(p.x, 1.03, p.z - 0.1), black, seg: 6) + if hi { k.bx(V3(0.05, 0.06, 0.05), V3(bx0, 1.02, bz), anod, r: 0.002) } + } + // Cleaner module x 0.45..2.0, z -1.5..0.2, height 1.9. Window on +x face. + let cx0: Float = 0.45, cx1: Float = 2.0, ch: Float = 1.7 + k.bx(V3(0.55, ch - y0, 1.7), V3(cx0 + 0.275, (ch + y0) / 2, -0.65), dark) + k.bx(V3(0.03, ch - y0, 1.7), V3(1.0, (ch + y0) / 2, -0.65), inner, r: 0.002, yaw: 0) + k.bx(V3(1.0, 0.03, 1.7), V3(1.5, y0 + 0.02, -0.65), inner, r: 0) + k.bx(V3(1.0, ch - y0, 0.03), V3(1.5, (ch + y0) / 2, -1.485), inner, r: 0) + k.bx(V3(1.0, ch - y0, 0.03), V3(1.5, (ch + y0) / 2, 0.185), inner, r: 0) + k.bx(V3(0.03, ch - y0, 1.7 - 0.0), V3(cx1 + 0.005, (ch + y0) / 2, -0.65), white, r: 0.004) + k.bx(V3(cx1 - cx0 + 0.04, 0.04, 1.74), V3((cx0 + cx1) / 2, ch + 0.02, -0.65), white, r: 0.004) + k.bx(V3(1.1, 0.9, 0.012), V3(cx1 + 0.022, 1.05, -0.7), glass, r: 0, yaw: 90) + + for (c, s) in [(V3(cx1 + 0.03, 1.53, -0.7), V3(0.03, 0.03, 1.16)), (V3(cx1 + 0.03, 0.57, -0.7), V3(0.03, 0.03, 1.16))] { k.bx(s, c, anod, r: 0.002) } + // Brush rollers and tanks visible through the window. + for q in 0..<4 { + let z = -1.15 + Float(q) * 0.3 + k.put(Prim.cylinder(radius: 0.05, height: 0.95, bevel: 0, segments: hi ? 14 : 6, material: q % 2 == 0 ? "plastic.pad" : "plastic.matte:E4E2DA"), + Xform(translation: V3(1.45, 0.95, z), rotation: simd_quatf(degrees: 90, axis: V3(0, 0, 1)))) + k.put(Prim.cylinder(radius: 0.05, height: 0.95, bevel: 0, segments: hi ? 14 : 6, material: steel), + Xform(translation: V3(1.45, 1.2, z), rotation: simd_quatf(degrees: 90, axis: V3(0, 0, 1)))) + } + k.bx(V3(1.0, 0.1, 1.4), V3(1.45, 0.65, -0.7), steel, r: 0.003) + // EFEM x 0.45..2.0, z 0.2..1.05, with three load ports. + let ezf: Float = 1.0, eh: Float = 2.0 + k.bx(V3(cx1 - cx0, eh - y0, ezf - 0.2), V3((cx0 + cx1) / 2, (eh + y0) / 2, (ezf + 0.2) / 2), dark, r: 0.005) + for (cxp, cw) in [(0.725, 0.55), (1.275, 0.55), (1.825, 0.35)] as [(Float, Float)] { + k.bx(V3(cw - 0.006, 0.52, 0.02), V3(cxp, y0 + 0.26, ezf + 0.008), white, r: 0.003) + k.bx(V3(cw - 0.006, 0.42, 0.02), V3(cxp, eh - 0.21, ezf + 0.008), white, r: 0.003) + } + k.bx(V3(0.02, eh - y0 - 0.02, ezf - 0.2), V3(cx1 + 0.008, (eh + y0) / 2, (ezf + 0.2) / 2), white, r: 0.004) + k.bx(V3(cx1 - cx0 + 0.04, 0.04, ezf - 0.16), V3((cx0 + cx1) / 2, eh + 0.02, (ezf + 0.2) / 2), white, r: 0.004) + for px: Float in [0.72, 1.22, 1.72] { FabParts.loadPort(k, x: px, z: ezf, frame: dark, skin: white) } + k.bx(V3(0.1, 0.06, 0.004), V3(0.55, 1.55, ezf + 0.02), "plastic.matte:EEEEEA", r: 0.0005) + FabParts.statusTower(k, V3(1.75, ch + 0.04, -0.2)); _ = 0 + if hi { + for fx: Float in [-1.6, -0.2, 0.9, 1.8] { k.cyl(0.05, 0.018, V3(fx, 0.05, 1.3), steel, seg: 12) } + k.bx(V3(0.5, 0.16, 0.004), V3(1.22, 0.34, ezf + 0.02), "plastic.matte:C8C8C2", r: 0.0005) + // Slurry drip trough and tubing at the polish module right end. + k.tube(catmull([V3(0.4, 0.3, 0.9), V3(0.3, 0.3, 0.6), V3(0.1, 0.4, 0.5)], per: 2), 0.016, "rubber.tubing", sides: 6) + } + rig = k.rig + rig.base[l] = k.m + } + groundAO(&rig, height: 0.15, floor: 0.6) + rig.states = [RigState("idle"), RigState("spin", ["platen-1": 180, "platen-2": 180, "platen-3": 180])] + return rig + } +} diff --git a/Sources/RealLibrary/Props/Fab/CvdAldCluster.swift b/Sources/RealLibrary/Props/Fab/CvdAldCluster.swift new file mode 100644 index 00000000..a7fdb173 --- /dev/null +++ b/Sources/RealLibrary/Props/Fab/CvdAldCluster.swift @@ -0,0 +1,78 @@ +import simd +import Foundation + +/// CVD/ALD cluster tool, generic, 4 x 2.5 x 4 m. Variant of ClusterTool: four aluminum chambers with heated showerhead +/// lids (insulation jacket, heater leads, gas feed manifold), a precursor ampoule cabinet under each gas tray and extra +/// gas sticks. Named parts: lid-1...lid-4. +public struct CvdAldCluster: RealArticulated { + public static let id = "cvd-ald-cluster" + public static let summary = "CVD/ALD cluster: hex transfer chamber, 4 chambers with heated showerhead lids, precursor ampoule cabinets, 8 gas sticks each, EFEM." + public static let tags = ["prop", "semiconductor", "articulated"] + public static let budget = 14000 + public static let author = "realityhd" + public static let preview = PreviewHint(azimuth: 30, elevation: 22, distance: 1.0, studio: true) + + /// EFEM and load lock skin powdercoat (sRGB hex). + public var color: UInt32 = 0xE8EAE6 + /// Gas sticks per chamber. + public var gasSticks = 7 + /// Showerhead jacket tape color (sRGB hex). + public var jacketColor: UInt32 = 0xC9963A + public init() {} + + public func rig(seed: UInt64) -> Rig { + _ = seed + var rig = Rig(name: Self.id, lods: 2, switchDistances: [10]) + var spec = ClusterSpec() + spec.skinMat = MaterialKey(stringLiteral: "metal.powdercoat:" + String(format: "%06X", color)) + spec.gasSticks = gasSticks + spec.matchBoxes = false + let jacket = MaterialKey(stringLiteral: "plastic.matte:" + String(format: "%06X", jacketColor)) + spec.chamberExtra = { k, c, d, t, top, ln, _ in + let steel: MaterialKey = "metal.stainless", brass: MaterialKey = "metal.brass", black: MaterialKey = "plastic.matte:1E2023" + let yaw = 90 - Float(atan2(d.z, d.x)) * 180 / .pi + // Heated showerhead lid: insulation jacket, heater ring, gas feed manifold, heater leads. + k.cyl(0.3, 0.07, V3(c.x, top + 0.115 + 0.035, c.z), jacket, seg: k.hi ? 12 : 6, name: ln) + k.cyl(0.31, 0.012, V3(c.x, top + 0.115 + 0.076, c.z), steel, seg: k.hi ? 12 : 6, name: ln) + k.cyl(0.07, 0.14, V3(c.x, top + 0.115 + 0.15, c.z), steel, seg: 9, name: ln) + k.bx(V3(0.3, 0.05, 0.06), V3(c.x, top + 0.115 + 0.19, c.z), steel, r: 0.003, yaw: yaw, name: ln) + for q: Float in [-1, 1] { + let p = c + t * q * 0.15 + k.cyl(0.018, 0.1, V3(p.x, top + 0.115 + 0.24, p.z), brass, seg: 8, name: ln) + k.bx(V3(0.05, 0.05, 0.05), V3(p.x, top + 0.115 + 0.3, p.z), black, r: 0.004, yaw: yaw, name: ln) + let h = c + d * 0.22 + t * q * 0.11 + k.bx(V3(0.06, 0.06, 0.06), V3(h.x, top + 0.115 + 0.05, h.z), "metal.anodized", r: 0.004, yaw: yaw, name: ln) + } + // Precursor ampoule cabinet under the gas tray. + let gc = c + d * (0.46 + 0.2), cy: Float = 0.12 + let ch = top + 0.14 - cy + k.bx(V3(0.48, ch, 0.3), V3(gc.x, cy + ch / 2, gc.z), "metal.powdercoat:D4D6D2", r: 0.004, yaw: yaw) + k.bx(V3(0.4, ch * 0.78, 0.01), V3(gc.x, cy + ch * 0.5, gc.z) + d * 0.152, "metal.powdercoat:C2C5C2", r: 0.002, yaw: yaw) + k.bx(V3(0.3, 0.36, 0.008), V3(gc.x, cy + ch * 0.62, gc.z) + d * 0.158, "glass.window-tint", r: 0.002, yaw: yaw) + k.bx(V3(0.012, 0.16, 0.02), V3(gc.x, cy + ch * 0.35, gc.z) + d * 0.165 + t * 0.17, steel, r: 0.003, yaw: yaw) + k.bx(V3(0.36, 0.05, 0.008), V3(gc.x, cy + ch - 0.07, gc.z) + d * 0.155, "plastic.matte:2A2C2F", r: 0.001, yaw: yaw) + k.bx(V3(0.07, 0.015, 0.004), V3(gc.x + t.x * 0.1, cy + ch * 0.2, gc.z + t.z * 0.1) + d * 0.158, "glass.lamp:40E060", r: 0.0005, yaw: yaw) + if k.hi { + for q: Float in [-1, 1] { + let p = gc + t * q * 0.1 - d * 0.02 + k.cyl(0.04, 0.2, V3(p.x, cy + ch * 0.62 - 0.05, p.z), steel, seg: 9) + k.cyl(0.015, 0.06, V3(p.x, cy + ch * 0.62 + 0.08, p.z), brass, seg: 7) + } + } + // Heated delivery line (jacket wrapped) from the cabinet side to the chamber body. + let a0 = gc + t * 0.25 + V3(0, ch * 0.75 + cy, 0) + k.tube([a0, a0 + d * 0.0 + t * 0.07 + V3(0, 0.08, 0), c + t * 0.34 + d * 0.15 + V3(0, top - 0.08, 0), c + t * 0.4 + V3(0, top - 0.1, 0)], 0.016, jacket, sides: 6) + } + ClusterTool.declareParts(&rig, spec) + for l in 0..<2 { + let k = ClusterKit(rig: rig, lod: l) + ClusterTool.assemble(k, spec, status: V3(1.3, 0, 1.3)) + rig = k.rig + rig.base[l] = k.m + } + groundAO(&rig, height: 0.15, floor: 0.6) + rig.states = [RigState("closed"), + RigState("lids-open", (1...4).reduce(into: [String: Float]()) { $0["lid-\($1)"] = -100 })] + return rig + } +} diff --git a/Sources/RealLibrary/Props/Fab/DicingSaw.swift b/Sources/RealLibrary/Props/Fab/DicingSaw.swift new file mode 100644 index 00000000..b981363b --- /dev/null +++ b/Sources/RealLibrary/Props/Fab/DicingSaw.swift @@ -0,0 +1,90 @@ +import simd +import Foundation + +/// Generic wafer dicing saw, 1.5 x 1.8 x 1.5 m. Cabinet with doors, stainless wet deck, rear tower with status lights, hinged hood +/// (named part `lid`) with a window over the spindle, blade and a tape-frame wafer on the cutting table. +public struct DicingSaw: RealArticulated { + public static let id = "dicing-saw" + public static let summary = "Dicing saw: cabinet, wet deck, spindle with blade, tape-frame wafer, hinged lid with window." + public static let tags = ["prop", "semiconductor", "articulated"] + public static let budget = 8000 + public static let author = "realityhd" + public static let preview = PreviewHint(azimuth: 28, elevation: 24, distance: 1.0, studio: true) + + /// Enclosure powdercoat (sRGB hex). + public var color: UInt32 = 0xECECE8 + public init() {} + + public func rig(seed: UInt64) -> Rig { + _ = seed + var rig = Rig(name: Self.id, lods: 2, switchDistances: [8]) + let white: MaterialKey = "metal.powdercoat:" + String(format: "%06X", color) + let dark: MaterialKey = "metal.powdercoat:34373C", grey: MaterialKey = "metal.powdercoat:C4C8CC" + let steel: MaterialKey = "metal.stainless", anod: MaterialKey = "metal.anodized", black: MaterialKey = "plastic.matte:1E2023" + let yel: MaterialKey = "plastic.yellow" + let hy: Float = 1.0, zf: Float = 0.74, zb: Float = -0.74 + let lidY0: Float = 1.03, lidY1: Float = 1.4, lz0: Float = -0.42 + rig.part("lid", pivot: V3(0, lidY1, lz0), joint: .hinge(axis: V3(1, 0, 0), -100...0, duration: 1.3)) + for l in 0..<2 { + let k = ClusterKit(rig: rig, lod: l) + let hi = k.hi + // Cabinet with plinth, two doors, handles. + k.bb(V3(1.46, 0.08, 1.46), V3(0, 0.04, 0), black, r: 0.004) + k.bb(V3(1.5, hy - 0.08, 1.48), V3(0, (hy + 0.08) / 2, 0), white, r: 0.005) + for sx: Float in [-1, 1] { + k.bb(V3(0.46, 0.62, 0.014), V3(sx * 0.26, 0.5, zf + 0.007), grey, r: 0.003) + k.handle(V3(sx * 0.07, 0.6, zf + 0.014), len: 0.22) + k.scuffs(V3(sx * 0.07, 0.6, zf + 0.062), w: 0.03, h: 0.18, n: 4) + k.scuffs(V3(sx * 0.26, 0.25, zf + 0.0145), w: 0.3, h: 0.06, n: 5) + } + // Wet deck, splash trough, rear tower. + k.bb(V3(1.46, 0.03, 1.1), V3(0, hy + 0.015, 0.2), steel, r: 0.004) + k.bb(V3(1.46, 0.05, 0.03), V3(0, hy + 0.045, zf - 0.03), steel, r: 0.003) + k.bb(V3(1.5, 0.42, 0.34), V3(0, hy + 0.21, zb + 0.17), white, r: 0.005) + k.bb(V3(1.5, 0.03, 0.36), V3(0, hy + 0.435, zb + 0.17), anod, r: 0.004) + FabParts.statusTower(k, V3(0.55, hy + 0.45, zb + 0.17)) + // Control panel on the cabinet front corner with screen. + k.bb(V3(0.36, 0.26, 0.04), V3(0.5, 0.87, zf + 0.02), black, r: 0.004) + k.bb(V3(0.32, 0.22, 0.006), V3(0.5, 0.87, zf + 0.043), "plastic.matte:16283C", r: 0.002) + k.prints(V3(0.5, 0.87, zf + 0.0465), w: 0.28, h: 0.18, n: hi ? 5 : 3) + k.bb(V3(0.1, 0.1, 0.03), V3(-0.5, 0.9, zf + 0.015), black, r: 0.003) + k.cyl(0.022, 0.01, V3(-0.5, 0.9, zf + 0.035), "glass.lamp:E03020", seg: 12) + // Gantry: two side towers, bridge beam, spindle housing, blade, flange, nozzles. + for sx: Float in [-1, 1] { k.bb(V3(0.1, 0.3, 0.22), V3(sx * 0.66, hy + 0.18, lz0 + 0.12 - 0.0), dark, r: 0.004) } + k.bb(V3(1.3, 0.1, 0.12), V3(0, hy + 0.27, lz0 + 0.1), grey, r: 0.004) + k.bb(V3(0.2, 0.15, 0.14), V3(0.12, hy + 0.2, 0.02), anod, r: 0.004) + k.put(Prim.cylinder(radius: 0.035, height: 0.16, bevel: 0.002, segments: hi ? 18 : 8, material: steel), Xform(translation: V3(0.12, hy + 0.1, 0.12), rotation: simd_quatf(degrees: 90, axis: V3(0, 0, 1)))) + k.put(Prim.cylinder(radius: 0.058, height: 0.004, bevel: 0, segments: hi ? 24 : 12, material: "metal.stainless:D8DADC"), Xform(translation: V3(0.12 + 0.085, hy + 0.1, 0.12), rotation: simd_quatf(degrees: 90, axis: V3(0, 0, 1)))) + k.put(Prim.cylinder(radius: 0.03, height: 0.012, bevel: 0.002, segments: hi ? 18 : 8, material: anod), Xform(translation: V3(0.12 + 0.092, hy + 0.1, 0.12), rotation: simd_quatf(degrees: 90, axis: V3(0, 0, 1)))) + k.bb(V3(0.016, 0.06, 0.1), V3(0.12 + 0.07, hy + 0.14, 0.17), anod, r: 0.003) // blade guard + if hi { for q: Float in [-1, 1] { k.tube([V3(0.12 + 0.07, hy + 0.15, 0.17), V3(0.12 + 0.07 + q * 0.0, hy + 0.08, 0.17), V3(0.12 + 0.07 + q * 0.014, hy + 0.06, 0.13)], 0.003, steel, sides: 5) } } + // Cutting table with tape-frame wafer. + k.put(Prim.lathe([V2(0, 0), V2(0.26, 0), V2(0.26, 0.04), V2(0.22, 0.05), V2(0, 0.05)], segments: hi ? 24 : 12, seamTile: 0.4, material: anod), Xform(translation: V3(0.0, hy + 0.03, 0.2))) + k.put(Prim.lathe([V2(0, 0), V2(0.21, 0), V2(0.21, 0.001), V2(0, 0.001)], segments: hi ? 24 : 12, seamTile: 0.4, material: "plastic.matte:3C70B4"), Xform(translation: V3(0.0, hy + 0.0805, 0.2))) + k.put(Prim.lathe([V2(0.2, 0), V2(0.24, 0), V2(0.24, 0.004), V2(0.2, 0.004)], segments: hi ? 24 : 12, seamTile: 0.4, material: steel), Xform(translation: V3(0.0, hy + 0.0815, 0.2))) + k.put(Prim.lathe([V2(0, 0), V2(0.15, 0), V2(0.15, 0.0007), V2(0, 0.0007)], segments: hi ? 24 : 12, seamTile: 0.4, material: "silicon.thinfilm-patterned"), Xform(translation: V3(0.0, hy + 0.0822, 0.2))) + // Lid: hood with side walls, front wall and handle, top frame around a window, hinge barrel. + let n = "lid", lh = lidY1 - lidY0, lzc = (lz0 + zf) / 2, ld = zf - lz0 + for sx: Float in [-1, 1] { k.bb(V3(0.03, lh, ld), V3(sx * 0.72, (lidY0 + lidY1) / 2, lzc), white, r: 0.004, name: n) } + k.bb(V3(1.5, lh - 0.04, 0.03), V3(0, (lidY0 + lidY1) / 2 - 0.01, zf - 0.015), white, r: 0.004, name: n) + k.bb(V3(1.5, 0.04, 0.12), V3(0, lidY1 - 0.02, lz0 + 0.06), white, r: 0.004, name: n) + k.bb(V3(1.5, 0.04, 0.12), V3(0, lidY1 - 0.02, zf - 0.06), white, r: 0.004, name: n) + for sx: Float in [-1, 1] { k.bb(V3(0.2, 0.04, ld - 0.24), V3(sx * 0.65, lidY1 - 0.02, lzc), white, r: 0.004, name: n) } + k.bb(V3(1.1, 0.012, ld - 0.22), V3(0, lidY1 - 0.026, lzc), "glass.window-tint", r: 0.002, name: n) + k.bb(V3(1.2, 0.04, 0.04), V3(0, lidY1 - 0.01, lz0 + 0.02), steel, r: 0.004, name: n) + k.handle(V3(0, lidY0 + 0.12, zf), len: 0.5, vertical: false, name: n) + k.scuffs(V3(0, lidY0 + 0.12, zf + 0.048), w: 0.25, h: 0.02, n: 5, name: n) + if hi { k.bb(V3(0.5, 0.06, 0.004), V3(-0.4, lidY0 + 0.22, zf + 0.002), yel, r: 0, name: n) } + // Window fingerprints on the glass. + for q in 0..<(hi ? 7 : 3) { + k.put(Prim.extrude(Shape2D.roundedRect(0.022, 0.03, radius: 0.0105, segments: 2), depth: 0.0006, bevel: 0, bevelSegments: 1, material: "plastic.matte:A8B0B4"), + Xform(translation: V3(-0.3 + Float((q * 37) % 11) * 0.06, lidY1 - 0.0195, lzc - 0.08 + Float((q * 53) % 7) * 0.025), rotation: simd_quatf(degrees: -90, axis: V3(1, 0, 0))), n) + } + rig = k.rig + rig.base[l] = k.m + } + groundAO(&rig, height: 0.15, floor: 0.6) + rig.states = [RigState("closed"), RigState("open", ["lid": -90])] + return rig + } +} diff --git a/Sources/RealLibrary/Props/Fab/DiffusionFurnace.swift b/Sources/RealLibrary/Props/Fab/DiffusionFurnace.swift new file mode 100644 index 00000000..c8febea7 --- /dev/null +++ b/Sources/RealLibrary/Props/Fab/DiffusionFurnace.swift @@ -0,0 +1,146 @@ +import simd +import Foundation + +/// Vertical diffusion furnace, generic, 2 x 3.5 x 2 m. Grey powdercoat tower over a wafer loading cabinet. A quartz boat with +/// wafer slots rides a boat elevator (named part "boat-elevator") into an insulated heater jacket seen through a window. Gas +/// panel with regulators on the right, controller on the front, exhaust duct across the roof. +public struct DiffusionFurnace: RealArticulated { + public static let id = "diffusion-furnace" + public static let summary = "Vertical diffusion furnace: powdercoat tower, quartz wafer boat on elevator, heater jacket, gas panel, exhaust duct." + public static let tags = ["prop", "semiconductor", "articulated"] + public static let budget = 10000 + public static let author = "realityhd" + public static let preview = PreviewHint(azimuth: 32, elevation: 14, distance: 1.0, studio: true) + + /// Tower powdercoat (sRGB hex). + public var color: UInt32 = 0xC9CCCE + /// Wafers loaded in the boat. + public var wafers = 25 + public init() {} + + public func rig(seed: UInt64) -> Rig { + _ = seed + var rig = Rig(name: Self.id, lods: 2, switchDistances: [8]) + let skin: MaterialKey = "metal.powdercoat:" + String(format: "%06X", color) + let dark: MaterialKey = "metal.powdercoat:34373C", inner: MaterialKey = "metal.powdercoat:A8ACB0" + let steel: MaterialKey = "metal.stainless", anod: MaterialKey = "metal.anodized", black: MaterialKey = "plastic.matte:1E2023" + let quartz: MaterialKey = "quartz" + let y0: Float = 0.1 + rig.part("boat-elevator", pivot: V3(0, 0.45, 0), joint: .slide(axis: V3(0, 1, 0), 0...1.05, duration: 6)) + for l in 0..<2 { + let k = ClusterKit(rig: rig, lod: l) + let hi = k.hi + FabParts.grate(k, w: 2.0, d: 2.0) + k.bx(V3(1.96, y0 - 0.05, 1.96), V3(0, (y0 + 0.05) / 2, 0), dark, r: 0.004) + // Loading cabinet x -1..0.5, y 0.1..1.5, front opening x -0.8..0.3, y 0.3..1.4. + let cx0: Float = -1, cx1: Float = 0.5, cz0: Float = -0.95, cz1: Float = 0.95 + let cw = cx1 - cx0, cc = (cx0 + cx1) / 2 + k.bx(V3(cw, 0.03, cz1 - cz0), V3(cc, y0 + 0.03, 0), inner, r: 0) + k.bx(V3(cw, 1.5 - y0, 0.03), V3(cc, (1.5 + y0) / 2, cz0 + 0.015), skin, r: 0.004) + k.bx(V3(0.03, 1.5 - y0, cz1 - cz0), V3(cx0 + 0.015, (1.5 + y0) / 2, 0), skin, r: 0.004) + k.bx(V3(cw - 0.06, 1.4 - y0, 0.02), V3(cc, (1.4 + y0) / 2, cz0 + 0.04), "metal.powdercoat:2A2C2F", r: 0) + k.bx(V3(cw, 0.2, 0.03), V3(cc, 0.2, cz1 - 0.015), skin, r: 0.004) + k.bx(V3(cw, 0.1, 0.03), V3(cc, 1.45, cz1 - 0.015), skin, r: 0.004) + k.bx(V3(0.2, 1.1, 0.03), V3(-0.9, 0.85, cz1 - 0.015), skin, r: 0.004) + k.bx(V3(0.2, 1.1, 0.03), V3(0.4, 0.85, cz1 - 0.015), skin, r: 0.004) + for (c, s) in [(V3(-0.25, 0.3, cz1 + 0.005), V3(1.1, 0.025, 0.03)), (V3(-0.25, 1.4, cz1 + 0.005), V3(1.1, 0.025, 0.03)), + (V3(-0.8, 0.85, cz1 + 0.005), V3(0.025, 1.1, 0.03)), (V3(0.3, 0.85, cz1 + 0.005), V3(0.025, 1.1, 0.03))] { k.bx(s, c, anod, r: 0.002) } + k.bx(V3(0.8, 0.05, 0.06), V3(-0.25, 0.28, cz1 - 0.2), black, r: 0.003) // loading shelf rail + // Cabinet roof with boat port. + k.bx(V3(cw, 0.04, cz1 - cz0), V3(cc, 1.52, 0), skin, r: 0.004) + // Upper tower x -1..0.5, y 1.54..3.2, window on front with heater jacket behind. + let tz0: Float = -0.95, tz1: Float = 0.45, th: Float = 3.2, ty = (th + 1.54) / 2, tzc = (tz0 + tz1) / 2 + k.bx(V3(cw, 0.03, tz1 - tz0 + 0.0), V3(cc, th - 0.015, tzc), skin, r: 0.004) + k.bx(V3(cw, th - 1.54, 0.03), V3(cc, ty, tz0 + 0.015), skin, r: 0.004) + k.bx(V3(0.03, th - 1.54, tz1 - tz0), V3(cx0 + 0.015, ty, tzc), skin, r: 0.004) + k.bx(V3(0.03, th - 1.54, tz1 - tz0), V3(cx1 - 0.015, ty, tzc), skin, r: 0.004) + k.bx(V3(cw, 0.3, 0.03), V3(cc, 1.69, tz1 - 0.015), skin, r: 0.004) + k.bx(V3(cw, 0.35, 0.03), V3(cc, th - 0.175, tz1 - 0.015), skin, r: 0.004) + k.bx(V3(0.25, 1.1, 0.03), V3(cx0 + 0.125, 2.4, tz1 - 0.015), skin, r: 0.004) + k.bx(V3(0.25, 1.1, 0.03), V3(cx1 - 0.125, 2.4, tz1 - 0.015), skin, r: 0.004) + k.bx(V3(0.98, 1.1, 0.01), V3(cc, 2.4, tz1 - 0.02), "glass.window-tint", r: 0) + for (c, s) in [(V3(cc, 1.84, tz1 + 0.005), V3(1.06, 0.025, 0.03)), (V3(cc, 2.96, tz1 + 0.005), V3(1.06, 0.025, 0.03))] { k.bx(s, c, anod, r: 0.002) } + // Heater jacket: insulated cylinder with end flanges, heater zone bands, thermocouple spikes. + let jx: Float = cc, jz: Float = 0.0 + k.cyl(0.36, 1.25, V3(jx, 1.78, jz), "metal.satin-aluminum", seg: 24) + k.cyl(0.38, 0.03, V3(jx, 1.78, jz), steel, seg: 24) + k.cyl(0.38, 0.03, V3(jx, 3.0, jz), steel, seg: 24) + for z in 0..<5 { k.cyl(0.368, 0.012, V3(jx, 1.95 + Float(z) * 0.2, jz), "metal.powdercoat:C86A28", seg: 24) } + if hi { for z in 0..<5 { k.cyl(0.012, 0.1, V3(jx + 0.37, 2.0 + Float(z) * 0.2, jz), steel, seg: 6) } } + // Quartz process tube rising through the jacket, domed cap, neck at the top. + k.cyl(0.2, 1.75, V3(jx, 1.55, jz), quartz, seg: 20) + k.put(Prim.lathe([V2(0, 0), V2(0.2, 0), V2(0.17, 0.08), V2(0.09, 0.1), V2(0.05, 0.13), V2(0.05, 0.15), V2(0, 0.15)], segments: hi ? 20 : 8, seamTile: 0.4, material: quartz), Xform(translation: V3(jx, 3.3, jz))) + // Boat port ring at tube bottom. + k.cyl(0.26, 0.04, V3(jx, 1.54, jz), steel, seg: 20) + // Elevator guide rail on the cabinet back and ballscrew. + k.bx(V3(0.06, 1.2, 0.04), V3(jx, 0.85, cz0 + 0.09), anod, r: 0.002) + k.cyl(0.015, 1.1, V3(jx + 0.06, 0.85, cz0 + 0.1), steel, seg: 8) + // Boat elevator, part-named: carriage, arm, quartz pedestal, base plate, boat rods, end plate, wafers. + let n = "boat-elevator", bz = jz + k.bx(V3(0.14, 0.12, 0.08), V3(jx, 0.45, cz0 + 0.14), anod, r: 0.003, name: n) + k.bx(V3(0.1, 0.05, bz - (cz0 + 0.14)), V3(jx, 0.45, (bz + cz0 + 0.14) / 2), steel, r: 0.003, name: n) + k.cyl(0.19, 0.012, V3(bz == 0 ? jx : jx, 0.46, bz), steel, seg: 20, name: n) + k.cyl(0.14, 0.12, V3(jx, 0.472, bz), quartz, seg: 16, name: n) + k.cyl(0.18, 0.012, V3(jx, 0.592, bz), quartz, seg: 20, name: n) + for q in 0..<4 { + let a = (Float(q) * 90 + 45) * .pi / 180 + k.cyl(0.016, 0.74, V3(jx + 0.15 * cos(a), 0.6, bz + 0.15 * sin(a)), quartz, seg: 8, name: n) + } + k.cyl(0.18, 0.012, V3(jx, 1.34, bz), quartz, seg: 20, name: n) + let wn = hi ? wafers : 8 + let pitch: Float = 0.7 / Float(max(1, wn - 1)) + for w in 0.. Rig { + _ = seed + var rig = Rig(name: Self.id, lods: 2, switchDistances: [10]) + let white: MaterialKey = "metal.powdercoat:" + String(format: "%06X", color) + let dark: MaterialKey = "metal.powdercoat:34373C", steel: MaterialKey = "metal.stainless", anod: MaterialKey = "metal.anodized" + let black: MaterialKey = "plastic.matte:1E2023" + let y0: Float = 0.13, zf: Float = 1.2 + let A = (x0: Float(-3.0), x1: Float(-0.9), top: Float(2.3)), B = (x0: Float(-0.9), x1: Float(1.5), top: Float(2.6)), C = (x0: Float(1.5), x1: Float(3.0), top: Float(2.2)) + rig.part("cover-top", pivot: V3(0.3, B.top, 0), joint: .slide(axis: V3(0, 1, 0), 0...1.2, duration: 1.8)) + rig.part("cover-side", pivot: V3(-1.95, 1.5, zf), joint: .slide(axis: V3(0, 0, 1), 0...1.0, duration: 1.6)) + rig.part("cover-handler", pivot: V3(2.25, C.top, 0), joint: .slide(axis: V3(0, 1, 0), 0...1.0, duration: 1.6)) + rig.part("door-service", pivot: V3(-1.85, 1.1, zf), joint: .hinge(axis: .up, -110...0, duration: 1.2)) + let alongZ = simd_quatf(degrees: 90, axis: V3(1, 0, 0)) + + for l in 0..<2 { + let k = ClusterKit(rig: rig, lod: l) + let hi = k.hi + FabParts.grate(k, w: 6.0, d: 3.0) + k.bx(V3(5.9, y0 - 0.04, 2.9), V3(0, (y0 + 0.05) / 2, 0), dark) + for m in [A, B, C] { + let w = m.x1 - m.x0, cx = (m.x0 + m.x1) / 2 + k.bx(V3(w - 0.04, m.top - y0, 2 * zf - 0.1), V3(cx, (m.top + y0) / 2, 0), dark) + // Front and back panels, 3 rows with 8 mm seams. + let nu = max(1, Int((w / 0.8).rounded())), du = w / Float(nu), dv = (m.top - y0 - 0.03) / 3 + for i in 0..= 1 && i <= 2) && !(m.x0 == A.x0 && j == 0 && i == 0) { k.bx(V3(du - 0.008, dv - 0.008, 0.03), V3(x, y, zf - 0.015), white, r: 0) } + k.bx(V3(du - 0.008, dv - 0.008, 0.03), V3(x, y, -zf + 0.015), white, r: 0) + } } + if m.x0 != B.x0 { k.bx(V3(w, 0.03, 2 * zf), V3(cx, m.top - 0.015, 0), white, r: 0) } + if m.x0 == A.x0 { k.bx(V3(0.03, m.top - y0, 2 * zf), V3(m.x0 + 0.015, (m.top + y0) / 2, 0), white, r: 0) } + if m.x0 == C.x0 { k.bx(V3(0.03, m.top - y0, 2 * zf), V3(m.x1 - 0.015, (m.top + y0) / 2, 0), white, r: 0) } + } + // Step seam strips between modules and louvre vents on the back. + for x: Float in [A.x1, B.x1] { k.bx(V3(0.03, 2.2, 2 * zf + 0.02), V3(x, 1.2, 0), "metal.powdercoat:C4C8CC", r: 0) } + k.bx(V3(1.0, 0.45, 0.012), V3(-2.0, 0.5, -zf - 0.004), dark, r: 0) + k.bx(V3(1.0, 0.45, 0.012), V3(2.2, 0.5, -zf - 0.004), dark, r: 0) + if hi { for q in 0..<6 { for x: Float in [-2.0, 2.2] { k.bx(V3(0.9, 0.025, 0.01), V3(x, 0.34 + Float(q) * 0.065, -zf - 0.011), white, r: 0) } } } + // Lower panel stubs under the projection-module window (front). + k.bx(V3(2.4, 0.5, 0.03), V3(0.3, 0.38, zf - 0.015), white, r: 0) + k.bx(V3(2.4, 0.5, 0.03), V3(0.3, 2.38, zf - 0.015), white, r: 0) + + // Projection module window: tinted pane, lens barrel, immersion hood, wafer chuck. + k.bx(V3(1.6, 1.5, 0.02), V3(0.3, 1.45, zf - 0.04), "glass.window-tint", r: 0) + for (c, s) in [(V3(0.3, 0.7, zf + 0.01), V3(1.7, 0.04, 0.05)), (V3(0.3, 2.2, zf + 0.01), V3(1.7, 0.04, 0.05)), + (V3(-0.55, 1.45, zf + 0.01), V3(0.04, 1.54, 0.05)), (V3(1.15, 1.45, zf + 0.01), V3(0.04, 1.54, 0.05))] { k.bx(s, c, anod, r: 0.002) } + k.bx(V3(1.5, 0.04, 0.8), V3(0.3, 0.8, 0.55), "metal.aluminum-brushed", r: 0.003) + k.cyl(0.35, 0.5, V3(0.3, 1.8, 0.2), "metal.aluminum-brushed", seg: 28) + k.cyl(0.18, 0.55, V3(0.3, 1.35, 0.2), steel, seg: 20) + k.cyl(0.24, 0.05, V3(0.3, 1.1, 0.2), anod, seg: 24) + k.put(Prim.torus(major: 0.2, minor: 0.02, segments: hi ? 24 : 10, sides: 6, material: "metal.anodized-black"), Xform(translation: V3(0.3, 1.05, 0.2))) + k.bx(V3(0.5, 0.12, 0.5), V3(0.3, 0.95, 0.2), anod, r: 0.004) + k.cyl(0.15, 0.015, V3(0.3, 1.015, 0.2), "silicon.thinfilm", seg: 28) + k.tube(catmull([V3(0.3 + 0.24, 1.08, 0.2), V3(0.9, 1.2, 0.3), V3(0.95, 1.6, 0.5), V3(1.1, 1.7, 0.8)], per: hi ? 3 : 2), 0.025, "rubber.tubing", sides: 8) + k.tube(catmull([V3(0.3 - 0.24, 1.08, 0.2), V3(-0.2, 1.3, 0.3), V3(-0.3, 1.7, 0.7)], per: hi ? 3 : 2), 0.025, "rubber.tubing", sides: 8) + if hi { k.prints(V3(0.3, 1.45, zf - 0.025), w: 1.3, h: 1.2, n: 6) } + + // Front A: side cover (named, slides forward), with louvre vents. Dark recess behind it. + k.bx(V3(0.7, 0.8, 0.03), V3(-2.45, 0.55, zf - 0.015), dark, r: 0) + let cs = "cover-side" + k.bx(V3(0.72, 0.8, 0.035), V3(-2.45, 0.55, zf + 0.02), white, r: 0, name: cs) + if hi { + for q in 0..<5 { k.put(cuboid(V3(0.5, 0.02, 0.012), material: dark), Xform(translation: V3(-2.45, 0.35 + Float(q) * 0.1, zf + 0.045)), cs) } + k.put(cuboid(V3(0.03, 0.2, 0.03), material: steel), Xform(translation: V3(-2.15, 0.9, zf + 0.05)), cs) + } + // Top cover over projection module and handler cover. + let ct = "cover-top" + k.bx(V3(2.4, 0.05, 2 * zf - 0.1), V3(0.3, B.top + 0.03, 0), white, r: 0.004, name: ct) + for q in 0..<4 { k.bx(V3(0.05, 0.12, 2 * zf - 0.1), V3(-0.8 + Float(q) * 0.75, B.top + 0.1, 0), white, r: 0.003, name: ct) } + k.bx(V3(2.4, 0.04, 0.04), V3(0.3, B.top + 0.15, 0), steel, r: 0, name: ct) + let ch = "cover-handler" + k.bx(V3(1.4, 0.06, 2 * zf - 0.1), V3(2.25, C.top + 0.03, 0), white, r: 0.004, name: ch) + if hi { + for sx: Float in [-1, 1] { k.put(Prim.torus(major: 0.035, minor: 0.008, segments: 8, sides: 4, material: steel), Xform(translation: V3(0.3 + sx * 1.0, B.top + 0.07, 0.8)), ct) } + k.put(Prim.torus(major: 0.035, minor: 0.008, segments: 8, sides: 4, material: steel), Xform(translation: V3(2.25, C.top + 0.07, 0.6)), ch) + } + // Service door in the lower window panel: open slot beside the window, with handle and scuffs. + let ds = "door-service" + k.bx(V3(0.7, 1.2, 0.03), V3(-1.5, 1.1, zf + 0.005), dark, r: 0, name: ds); _ = alongZ + + // Handler module C: EFEM face with two load ports and status tower. + k.bx(V3(1.4, C.top - y0, 0.04), V3(2.25, (C.top + y0) / 2, zf + 0.02), "metal.powdercoat:D8D9D5", r: 0) + FabParts.loadPort(k, x: 1.95, z: zf + 0.04, frame: dark, skin: white) + FabParts.loadPort(k, x: 2.55, z: zf + 0.04, frame: dark, skin: white) + FabParts.statusTower(k, V3(2.8, C.top + 0.06, -0.6)) + // Illuminator module A: cable tray on top, data plate, coolant manifold. + k.bx(V3(1.8, 0.05, 0.3), V3(-1.95, A.top + 0.2, -0.5), steel, r: 0) + for q in 0..<3 { k.tube([V3(-2.8, A.top + 0.22 + 0.02 * Float(q % 2), -0.5 + 0.05 * Float(q)), V3(-1.1, A.top + 0.22, -0.5 + 0.05 * Float(q))], 0.018, black, sides: 6) } + k.bx(V3(0.4, 0.15, 0.004), V3(-1.5, 1.7, zf + 0.002), "plastic.matte:C8C8C2", r: 0) + if hi { + k.scuffs(V3(-1.5, 0.9, zf + 0.004), w: 0.5, h: 0.5, n: 5) + k.scuffs(V3(1.7, 0.6, zf + 0.004), w: 0.4, h: 0.4, n: 4) + k.handle(V3(-1.2, 1.1, zf + 0.02), len: 0.35) + for fx: Float in [-2.8, -1.2, 0.3, 1.8, 2.8] { for fz: Float in [-1.3, 1.3] { k.cyl(0.05, 0.02, V3(fx, 0.04, fz), steel, seg: 10) } } + } + rig = k.rig + rig.base[l] = k.m + } + groundAO(&rig, height: 0.15, floor: 0.6) + rig.states = [RigState("closed"), + RigState("covers-off", ["cover-top": 1.2, "cover-side": 1.0, "cover-handler": 1.0]), + RigState("service", ["door-service": -100])] + return rig + } +} diff --git a/Sources/RealLibrary/Props/Fab/EbeamMetrology.swift b/Sources/RealLibrary/Props/Fab/EbeamMetrology.swift new file mode 100644 index 00000000..c09b471f --- /dev/null +++ b/Sources/RealLibrary/Props/Fab/EbeamMetrology.swift @@ -0,0 +1,104 @@ +import simd +import Foundation + +/// Generic e-beam metrology tool, 3 x 2.2 x 2.5 m. Vibration-isolated base on four pneumatic isolators, chamber cabinet with +/// column dome, EFEM on the front right with a hinged door (named part `door`), load port, monitor arm, status tower. +public struct EbeamMetrology: RealArticulated { + public static let id = "ebeam-metrology" + public static let summary = "E-beam metrology tool: isolated base, chamber cabinet, e-beam column dome, EFEM with hinged door." + public static let tags = ["prop", "semiconductor", "articulated"] + public static let budget = 10000 + public static let author = "realityhd" + public static let preview = PreviewHint(azimuth: 30, elevation: 14, distance: 1.0, studio: true) + + /// Enclosure powdercoat (sRGB hex). + public var color: UInt32 = 0xECECE8 + public init() {} + + public func rig(seed: UInt64) -> Rig { + _ = seed + var rig = Rig(name: Self.id, lods: 2, switchDistances: [10]) + let white: MaterialKey = "metal.powdercoat:" + String(format: "%06X", color) + let dark: MaterialKey = "metal.powdercoat:34373C", grey: MaterialKey = "metal.powdercoat:C4C8CC" + let steel: MaterialKey = "metal.stainless", anod: MaterialKey = "metal.anodized", black: MaterialKey = "plastic.matte:1E2023" + let yel: MaterialKey = "plastic.yellow" + let zf: Float = 0.8, dx0: Float = 0.62, dw: Float = 0.82, dy0: Float = 0.45, dh: Float = 1.2 + rig.part("door", pivot: V3(dx0, dy0 + dh / 2, zf), joint: .hinge(axis: .up, -110...0, duration: 1.2)) + for l in 0..<2 { + let k = ClusterKit(rig: rig, lod: l) + let hi = k.hi + // Vibration-isolated base: four pneumatic isolators under a frame plate. + for (sx, sz) in [(Float(-1), Float(-1)), (1, -1), (-1, 1), (1, 1)] { + k.cyl(0.09, 0.2, V3(sx * 1.35, 0.1, sz * 1.1), dark, seg: 16) + k.cyl(0.11, 0.03, V3(sx * 1.35, 0.215, sz * 1.1), steel, seg: 16) + k.cyl(0.13, 0.02, V3(sx * 1.35, 0.01, sz * 1.1), black, seg: 16) + if hi { k.cyl(0.03, 0.05, V3(sx * 1.35, 0.17, sz * 1.1 + 0.1), anod, seg: 8) } + } + k.bb(V3(2.95, 0.14, 2.4), V3(0, 0.3, 0), dark, r: 0.005) + k.bb(V3(2.99, 0.02, 2.44), V3(0, 0.38, 0), anod, r: 0.003) + // Chamber cabinet x -1.45..0.55. + k.bb(V3(2.0, 0.95, 1.9), V3(-0.45, 0.875, -0.25), white, r: 0.005) + for i in 0..<3 { + let x = -1.25 + Float(i) * 0.62 + k.bb(V3(0.56, 0.9, 0.012), V3(x, 0.8, 0.655), grey, r: 0.002) + k.bb(V3(0.4, 0.012, 0.006), V3(x, 1.08, 0.664), black, r: 0.001) + } + k.handle(V3(-0.97, 0.8, 0.66), len: 0.3) + k.scuffs(V3(-0.97, 0.8, 0.71), w: 0.04, h: 0.25, n: 4) + if hi { for q in 0..<6 { k.bb(V3(0.5, 0.012, 0.004), V3(-0.45, 0.5 + Float(q) * 0.025, 0.654), black, r: 0.001) } } + k.bb(V3(2.04, 0.04, 1.94), V3(-0.45, 1.37, -0.25), anod, r: 0.004) + // Chamber top ring and column dome. + k.cyl(0.62, 0.1, V3(-0.5, 1.39, -0.25), steel, seg: 28) + let col: [V2] = [V2(0, 0), V2(0.46, 0), V2(0.46, 0.08), V2(0.36, 0.1), V2(0.36, 0.3), V2(0.42, 0.32), V2(0.42, 0.36), V2(0.34, 0.38), + V2(0.34, 0.6), V2(0.4, 0.62), V2(0.4, 0.66), V2(0.3, 0.68), V2(0.3, 0.8), V2(0.2, 0.92), V2(0.08, 0.98), V2(0, 0.99)] + k.put(Prim.lathe(col, segments: hi ? 28 : 12, seamTile: 0.4, material: "metal.powdercoat:D6D8D8"), Xform(translation: V3(-0.5, 1.44, -0.25), scale: V3(1, 0.72, 1))) + for y: Float in [1.7, 1.9] { k.put(Prim.lathe([V2(0.43, 0), V2(0.47, 0), V2(0.47, 0.022), V2(0.43, 0.022)], segments: hi ? 28 : 12, seamTile: 0.4, material: anod), Xform(translation: V3(-0.5, y - 0.4 - 0.0, -0.25))) } + k.cyl(0.1, 0.1, V3(-0.5, 2.19, -0.25), steel, seg: 14) + k.cyl(0.06, 0.1, V3(-0.5, 1.9, 0.2), black, seg: 10) + if hi { + for q in 0..<3 { k.tube([V3(-0.5 + 0.1 * Float(q), 1.95, 0.0), V3(-0.3 + 0.1 * Float(q), 2.0, 0.1), V3(0.1, 1.5, 0.3)], 0.014, q == 1 ? "rubber.tubing" : black, sides: 6) } + k.bb(V3(0.36, 0.12, 0.004), V3(-1.1, 1.3, 0.656), yel, r: 0) + } + // Roof pumps on the cabinet rear. + for x: Float in [-1.1, -0.2] { k.cyl(0.14, 0.34, V3(x, 1.57, -0.9), steel, seg: 14); k.cyl(0.17, 0.03, V3(x, 1.41, -0.9), anod, seg: 14) } + // EFEM x 0.55..1.5, z -1.0..1.25. + k.bb(V3(0.95, 1.4, 1.8), V3(1.025, 1.1, -0.1), white, r: 0.005) + k.bb(V3(1.0, 0.04, 1.84), V3(1.025, 1.82, -0.1), anod, r: 0.004) + k.bb(V3(0.92, 0.3, 0.02), V3(1.03, 0.58, zf - 0.01 + 0.0), grey, r: 0.002) + // Recess behind door and frame. + k.bb(V3(dw + 0.08, dh + 0.08, 0.03), V3(dx0 + dw / 2, dy0 + dh / 2, zf - 0.02), dark, r: 0.003) + k.bb(V3(dw - 0.04, dh - 0.04, 0.02), V3(dx0 + dw / 2, dy0 + dh / 2, zf - 0.025), "metal.powdercoat:C9CBCC", r: 0.002) + k.bb(V3(0.46, 0.3, 0.01), V3(1.03, 1.0, zf - 0.04), black, r: 0.002) + // Door: frame, glass window, handle, hinges, fingerprints, scuffs. + let n = "door" + k.bb(V3(dw, dh, 0.03), V3(dx0 + dw / 2, dy0 + dh / 2, zf + 0.015), white, r: 0.005, name: n) + k.bb(V3(dw - 0.2, dh - 0.55, 0.012), V3(dx0 + dw / 2 - 0.02, dy0 + dh / 2 + 0.12, zf + 0.034), "glass.window-tint", r: 0.002, name: n) + k.bb(V3(dw - 0.14, dh - 0.49, 0.008), V3(dx0 + dw / 2 - 0.02, dy0 + dh / 2 + 0.12, zf + 0.03), black, r: 0.002, name: n) + k.prints(V3(dx0 + dw / 2, dy0 + dh / 2 + 0.2, zf + 0.0405), w: dw - 0.3, h: dh - 0.7, n: hi ? 9 : 4, name: n) + k.handle(V3(dx0 + dw - 0.07, dy0 + dh / 2 - 0.1, zf + 0.03), len: 0.34, name: n) + k.scuffs(V3(dx0 + dw - 0.07, dy0 + dh / 2 - 0.1, zf + 0.078), w: 0.05, h: 0.28, n: 5, name: n) + k.bb(V3(dw - 0.1, 0.1, 0.004), V3(dx0 + dw / 2, dy0 + 0.1, zf + 0.032), yel, r: 0, name: n) + for hy: Float in [0.35, 1.1] { k.cyl(0.012, 0.12, V3(dx0 - 0.005, dy0 + hy, zf + 0.02), steel, seg: 8, name: n) } + k.scuffs(V3(dx0 + dw / 2, dy0 + 0.25, zf + 0.0325), w: dw * 0.7, h: 0.16, n: 6, name: n) + // Load port with FOUP on the EFEM right side face. + FabParts.loadPort(k, x: 1.2, z: 0.8, frame: dark, skin: white) + FabParts.statusTower(k, V3(1.3, 1.84, -0.6)) + // Monitor arm on the cabinet front. + k.bb(V3(0.08, 0.08, 0.2), V3(-0.15, 1.0, 0.8), steel, r: 0.003) + k.bb(V3(0.5, 0.34, 0.04), V3(-0.15, 1.15, 0.96), black, r: 0.004) + k.bb(V3(0.46, 0.3, 0.006), V3(-0.15, 1.15, 0.982), "plastic.matte:16283C", r: 0.002) + k.prints(V3(-0.15, 1.15, 0.986), w: 0.4, h: 0.26, n: hi ? 6 : 3) + k.bb(V3(0.34, 0.12, 0.03), V3(-0.15, 0.9, 0.9), black, r: 0.003) // keyboard shelf + k.scuffs(V3(-0.15, 0.9, 0.917), w: 0.25, h: 0.08, n: 3) + if hi { + k.bb(V3(0.006, 0.06, 1.4), V3(1.505, 0.5, -0.1), yel, r: 0) + for q in 0..<4 { k.bb(V3(0.012, 0.22, 0.005), V3(-1.2 + Float(q) * 0.2, 1.25, 0.656), black, r: 0) } + } + rig = k.rig + rig.base[l] = k.m + } + groundAO(&rig, height: 0.2, floor: 0.6) + rig.states = [RigState("closed"), RigState("open", ["door": -100])] + return rig + } +} diff --git a/Sources/RealLibrary/Props/Fab/EfemRobot.swift b/Sources/RealLibrary/Props/Fab/EfemRobot.swift new file mode 100644 index 00000000..869bab8c --- /dev/null +++ b/Sources/RealLibrary/Props/Fab/EfemRobot.swift @@ -0,0 +1,92 @@ +import simd +import Foundation + +/// EFEM wafer-handling robot, 1 x 1.5 x 1 m: SCARA arm on a lift column. Arm folded in a Z so the footprint stays in the +/// envelope. Named parts: column-lift (slides Y), shoulder, elbow, wrist (hinges about Y; fork carries the wafer). +public struct EfemRobot: RealArticulated { + public static let id = "efem-robot" + public static let summary = "SCARA wafer-handling robot with column, two-link arm, fork end-effector and cable carrier." + public static let tags = ["prop", "semiconductor", "articulated"] + public static let budget = 8000 + public static let author = "realityhd" + public static let preview = PreviewHint(azimuth: 35, elevation: 18, distance: 1.0, studio: true) + + /// Cover color (sRGB hex). + public var color: UInt32 = 0xEDEEEC + public init() {} + + private func cc(_ k: ClusterKit, _ r: Float, _ h: Float, _ c: V3, _ mat: MaterialKey, seg: Int = 16, name: String? = nil) { + k.cyl(r, h, c - V3(0, h / 2, 0), mat, seg: seg, name: name) + } + + public func rig(seed: UInt64) -> Rig { + _ = seed + var rig = Rig(name: Self.id, lods: 2, switchDistances: [6]) + let white: MaterialKey = "plastic.matte:" + String(format: "%06X", color) + let anod: MaterialKey = "metal.anodized", black: MaterialKey = "plastic.matte:1E2023" + let steel: MaterialKey = "metal.stainless" + let sy: Float = 1.3, sz: Float = -0.3 // shoulder pivot + let ez: Float = sz + 0.42, wz: Float = ez - 0.38 // elbow, wrist + rig.part("column-lift", pivot: V3(0, 0.8, 0), joint: .slide(axis: V3(0, 1, 0), 0...0.2, duration: 3)) + rig.part("shoulder", parent: "column-lift", pivot: V3(0, sy, sz), joint: .hinge(axis: .up, -150...150, duration: 2)) + rig.part("elbow", parent: "shoulder", pivot: V3(0, sy + 0.08, ez), joint: .hinge(axis: .up, -150...150, duration: 2)) + rig.part("wrist", parent: "elbow", pivot: V3(0, sy + 0.16, wz), joint: .hinge(axis: .up, -170...170, duration: 2)) + for l in 0..<2 { + let k = ClusterKit(rig: rig, lod: l) + let hi = k.hi + // Pedestal, base cover, footplate. + k.bb(V3(0.96, 0.03, 0.96), V3(0, 0.015, 0), anod, r: 0.003) + k.bb(V3(0.84, 0.27, 0.84), V3(0, 0.165, 0), white, r: 0.006) + k.bb(V3(0.78, 0.02, 0.78), V3(0, 0.31, 0), anod, r: 0.003) + k.bb(V3(0.2, 0.1, 0.01), V3(0.2, 0.2, 0.425), black, r: 0.002) + k.bb(V3(0.06, 0.012, 0.004), V3(0.14, 0.2, 0.432), "glass.lamp:30C050", r: 0.001) + k.bb(V3(0.84, 0.006, 0.004), V3(0, 0.1, 0.422), "metal.powdercoat:9FA3A7", r: 0) + if hi { for q in 0..<4 { cc(k, 0.012, 0.012, V3(((q % 2 == 0) ? -1 : 1) * 0.43, 0.036, ((q / 2 == 0) ? -1 : 1) * 0.43), steel, seg: 6) } } + cc(k, 0.14, 0.03, V3(0, 0.335, 0), anod, seg: 24) + // Fixed outer column sleeve behind the moving column. + cc(k, 0.115, 0.5, V3(0, 0.57, 0), white, seg: 28) + k.bb(V3(0.1, 0.5, 0.03), V3(0, 0.6, sz - 0.2), anod, r: 0.003) + // Lift column (moving). + let cl = "column-lift", sh = "shoulder", el = "elbow", wr = "wrist" + cc(k, 0.1, 0.66, V3(0, 0.93, 0), white, seg: 28, name: cl) + cc(k, 0.103, 0.02, V3(0, 0.62, 0), black, seg: 28, name: cl) + k.bb(V3(0.1, 0.07, 0.1), V3(0, sy - 0.12, sz), "metal.aluminum-brushed", r: 0.003, name: cl) + cc(k, 0.115, 0.04, V3(0, sy - 0.04, sz), anod, seg: 24, name: cl) + // Shoulder: housing and link 1. + cc(k, 0.105, 0.085, V3(0, sy - 0.0, sz), white, seg: 24, name: sh) + k.bb(V3(0.17, 0.08, 0.5), V3(0, sy + 0.0, (sz + ez) / 2), white, r: 0.006, name: sh) + cc(k, 0.075, 0.008, V3(0, sy + 0.04, sz), black, seg: 20, name: sh) + cc(k, 0.06, 0.008, V3(0, sy + 0.04, ez), black, seg: 20, name: sh) + if hi { for q in 0..<6 { let t = Float(q) / 6 * 2 * .pi; cc(k, 0.006, 0.006, V3(0.075 * cos(t), sy + 0.045, sz + 0.075 * sin(t)), steel, seg: 6, name: sh) } } + // Elbow housing and link 2. + cc(k, 0.09, 0.08, V3(0, sy + 0.08, ez), white, seg: 24, name: el) + k.bb(V3(0.14, 0.07, 0.46), V3(0, sy + 0.08, (ez + wz) / 2), white, r: 0.006, name: el) + cc(k, 0.05, 0.008, V3(0, sy + 0.12, ez), black, seg: 18, name: el) + k.bb(V3(0.05, 0.012, 0.004), V3(0.0, sy + 0.1, ez + 0.082), "glass.lamp:F0A020", r: 0.001, name: el) + // Wrist: hub and end-effector fork with wafer. + cc(k, 0.06, 0.06, V3(0, sy + 0.16, wz), anod, seg: 20, name: wr) + cc(k, 0.04, 0.01, V3(0, sy + 0.195, wz), black, seg: 16, name: wr) + k.bb(V3(0.1, 0.012, 0.14), V3(0, sy + 0.19, wz + 0.1), "metal.aluminum-brushed", r: 0.002, name: wr) + for q: Float in [-1, 1] { + k.bb(V3(0.024, 0.008, 0.32), V3(q * 0.045, sy + 0.19, wz + 0.26), steel, r: 0.001, name: wr) + for pz: Float in [0.2, 0.38] { cc(k, 0.007, 0.004, V3(q * 0.045, sy + 0.198, wz + pz), "rubber.tubing", seg: 8, name: wr) } + } + k.put(Prim.cylinder(radius: 0.15, height: 0.00078, bevel: 0, segments: hi ? 40 : 16, material: "silicon.thinfilm"), + Xform(translation: V3(0, sy + 0.206, wz + 0.2)), wr) + // Cable carrier: chain links along a curve from the base to the column back. + let pts = catmull([V3(0, 0.34, -0.2), V3(0, 0.32, -0.4), V3(0, 0.65, -0.46), V3(0, 1.0, -0.38), V3(0, 1.2, -0.26)], per: hi ? 5 : 2) + for i in 0..<(pts.count - 1) { + let a = pts[i], b = pts[i + 1], d = b - a, len = simd_length(d) + if len < 0.002 { continue } + let q = simd_quatf(from: V3(0, 0, 1), to: d / len) + k.put(cuboid(V3(0.08, 0.035, len * 0.92), material: i % 2 == 0 ? "plastic.matte:2A2C2F" : "plastic.matte:3A3C40"), + Xform(translation: (a + b) / 2, rotation: q)) + } + rig = k.rig + rig.base[l] = k.m + } + groundAO(&rig, height: 0.1, floor: 0.5) + rig.states = [RigState("idle"), RigState("reach", ["shoulder": 40, "elbow": -80, "wrist": 40])] + return rig + } +} diff --git a/Sources/RealLibrary/Props/Fab/EuvLightSource.swift b/Sources/RealLibrary/Props/Fab/EuvLightSource.swift new file mode 100644 index 00000000..f653fa20 --- /dev/null +++ b/Sources/RealLibrary/Props/Fab/EuvLightSource.swift @@ -0,0 +1,213 @@ +import simd +import Foundation + +/// EUV light source (laser-produced plasma), generic, 6 x 4 x 4 m. White CO2 laser drive cabinet on the left, a dark steel +/// tin-droplet vessel with a concave collector mirror, droplet generator, plasma glow and tin catcher, beam transport tube +/// with bellows, and heavy copper and steel cooling plumbing on the right. Named part: cutaway-cover (slides off the vessel front). +public struct EuvLightSource: RealArticulated { + public static let id = "euv-light-source" + public static let summary = "EUV light source: CO2 laser drive cabinet, tin vessel with collector mirror, cutaway cover, copper and steel plumbing." + public static let tags = ["prop", "semiconductor", "articulated"] + public static let budget = 14000 + public static let author = "realityhd" + public static let preview = PreviewHint(azimuth: 28, elevation: 16, distance: 1.0, studio: true) + + /// Cabinet powdercoat (sRGB hex). + public var color: UInt32 = 0xE9E9E4 + public init() {} + + public func rig(seed: UInt64) -> Rig { + _ = seed + var rig = Rig(name: Self.id, lods: 2, switchDistances: [12]) + let white: MaterialKey = "metal.powdercoat:" + String(format: "%06X", color) + let dark: MaterialKey = "metal.powdercoat:34373C", steel: MaterialKey = "metal.steel", cu: MaterialKey = "metal.copper" + let anod: MaterialKey = "metal.anodized", black: MaterialKey = "plastic.matte:1E2023", mirror: MaterialKey = "metal.chrome" + let y0: Float = 0.14 + // Vessel: x vx0...vx1, y vy0...vy1, z -vz...vz. Open face at +z is closed by the cutaway cover. + let vx0: Float = 0.0, vx1: Float = 2.5, vy0: Float = 0.6, vy1: Float = 3.5, vz: Float = 1.3 + let vcx = (vx0 + vx1) / 2, vcy = (vy0 + vy1) / 2 + rig.part("cutaway-cover", pivot: V3(vcx, vcy, vz), joint: .slide(axis: V3(0, 0, 1), 0...1.8, duration: 2.0)) + let alongX = simd_quatf(degrees: 90, axis: V3(0, 0, 1)), alongZ = simd_quatf(degrees: 90, axis: V3(1, 0, 0)) + + for l in 0..<2 { + let k = ClusterKit(rig: rig, lod: l) + let hi = k.hi + func pan(_ s: V3, _ c: V3, _ mat: MaterialKey, name: String? = nil) { + k.bx(s, c, mat, r: 0, name: name) + } + func pcyl(_ r: Float, _ len: Float, _ c: V3, _ mat: MaterialKey, axis: Int, seg: Int = 16, name: String? = nil) { + let q = axis == 0 ? alongX : axis == 2 ? alongZ : simd_quatf.identity + k.put(Prim.cylinder(radius: r, height: len, bevel: 0, segments: hi ? seg : max(6, seg / 2), material: mat), Xform(translation: c, rotation: q), name) + } + func flange(_ c: V3, _ r: Float, axis: Int, mat: MaterialKey = steel) { + pcyl(r, 0.035, c, mat, axis: axis, seg: 16) + if hi { for i in 0..<4 { + let a = Float(i) * .pi / 2 + let o = axis == 1 ? V3(cos(a), 0, sin(a)) * (r * 0.8) : axis == 0 ? V3(0, cos(a), sin(a)) * (r * 0.8) : V3(cos(a), sin(a), 0) * (r * 0.8) + pcyl(0.011, 0.045, c + o, black, axis: axis, seg: 6) + } } + } + func pipe(_ pts: [V3], _ r: Float, _ mat: MaterialKey, flanges: Bool = true) { + k.tube(catmull(pts, per: hi ? 3 : 2), r, mat, sides: 8) + guard hi, flanges else { return } + for p in [pts.first!, pts.last!] { _ = p } + } + FabParts.grate(k, w: 6.0, d: 4.0) + k.bx(V3(5.9, y0 - 0.04, 3.9), V3(0, (y0 + 0.05) / 2, 0), dark) + + // ---- CO2 laser drive cabinet x -3...-0.8, z -1.7...1.7, top 2.6 ---- + let cx0: Float = -3.0, cx1: Float = -0.8, ct: Float = 2.6, cz: Float = 1.7 + let ccx = (cx0 + cx1) / 2 + k.bx(V3(cx1 - cx0 - 0.1, ct - y0, 2 * cz - 0.1), V3(ccx, (ct + y0) / 2, 0), dark) + let nu = 3, nv = 3 + let du = (cx1 - cx0) / Float(nu), dv = (ct - y0) / Float(nv) + for i in 0.. Rig { + _ = seed + var rig = Rig(name: Self.id, lods: 2, switchDistances: [14]) + let white: MaterialKey = "metal.powdercoat:" + String(format: "%06X", color) + let dark: MaterialKey = "metal.powdercoat:34373C" + let anod: MaterialKey = "metal.anodized" + let steel: MaterialKey = "metal.steel" + let black: MaterialKey = "plastic.matte:222426" + let y0: Float = 0.14 + + func bx(_ s: V3, _ c: V3, _ mat: MaterialKey, r: Float = 0.004) -> (Surface, Xform) { + (r > 0.003 && s.min() > 0.06 ? Prim.extrude(Shape2D.rect(s.x, s.y), depth: s.z, bevel: min(r, s.min() * 0.4), bevelSegments: 1, material: mat) : cuboid(s, material: mat), + Xform(translation: c)) + } + func cyl(_ r: Float, _ h: Float, _ c: V3, _ mat: MaterialKey, seg: Int = 16, rot: simd_quatf = .identity) -> (Surface, Xform) { + (Prim.cylinder(radius: r, height: h, bevel: r < 0.1 ? 0 : 0.004, segments: r < 0.1 ? min(seg, 8) : min(seg, 20), material: mat), Xform(translation: c, rotation: rot)) + } + /// Panel grid on one face. axis 0 = x normal, 1 = y, 2 = z. u runs +x/+z/+x, v runs y/z/z. + func face(_ out: ((Surface, Xform)) -> Void, center: V3, axis: Int, u: Float, v: Float, nu: Int, nv: Int, + mat: MaterialKey, t: Float = 0.03, skip: (Int, Int) -> Bool = { _, _ in false }, bolts: Bool = true) { + let gap: Float = 0.008 + let du = u / Float(nu), dv = v / Float(nv) + for i in 0.. Void, c: V3, w: Float, h: Float, n: Int, detail: Bool) { + out(bx(V3(w, h, 0.02), c, dark)) + guard detail else { return } + for k in 0.. Void = { m.add($0.0, $0.1) } + + // Cleanroom grate floor: dark slab, tile seams every 0.6 m, bar grating pattern. + m.add(Prim.extrude(Shape2D.rect(14.0, 4.0), depth: 0.05, bevel: 0.004, bevelSegments: 1, material: "metal.grate-iron"), + Xform(translation: V3(0, 0.025, 0), rotation: simd_quatf(degrees: 90, axis: V3(1, 0, 0)))) + if hi { + for k in 1..<23 { add(bx(V3(0.012, 0.004, 4.0), V3(-7 + Float(k) * 0.6, 0.051, 0), black, r: 0.001)) } + for k in 1..<7 { add(bx(V3(14.0, 0.004, 0.012), V3(0, 0.051, -2 + Float(k) * 0.6), black, r: 0.001)) } + for k in 0..<28 { add(bx(V3(0.02, 0.003, 3.9), V3(-6.9 + Float(k) * 0.5, 0.0515, 0), black, r: 0.0005)) } + } + + // Modules: dark core, recessed plinth, panelled shell. + for mod in [A, B, C] { + let w = mod.x1 - mod.x0, d = mod.zf - mod.zb, cx = (mod.x0 + mod.x1) / 2, cz = (mod.zf + mod.zb) / 2 + add(bx(V3(w - 0.1, mod.top - y0, d - 0.1), V3(cx, (mod.top + y0) / 2, cz), dark)) + add(bx(V3(w - 0.3, y0 - 0.04, d - 0.3), V3(cx, (y0 + 0.05) / 2, cz), dark)) + } + // EFEM (wafer handler) steps the front face back. + let nuA = 4, nuB = 5 + // Front/back faces. + let skipFrontA: (Int, Int) -> Bool = { i, j in i == 1 && j < 2 || i == 1 && j == 1 } + face(out, center: V3((A.x0 + A.x1) / 2, (A.top + y0) / 2, A.zf - 0.015), axis: 2, u: A.x1 - A.x0, v: A.top - y0, nu: nuA, nv: 3, + mat: white, skip: { i, j in i == 2 && j < 2 }) + face(out, center: V3((A.x0 + A.x1) / 2, (A.top + y0) / 2, A.zb + 0.015), axis: 2, u: A.x1 - A.x0, v: A.top - y0, nu: nuA, nv: 3, mat: white) + _ = skipFrontA + face(out, center: V3((B.x0 + B.x1) / 2, (B.top + y0) / 2, B.zf - 0.015), axis: 2, u: B.x1 - B.x0, v: B.top - y0, nu: nuB, nv: 3, mat: white) + face(out, center: V3((B.x0 + B.x1) / 2, (B.top + y0) / 2, B.zb + 0.015), axis: 2, u: B.x1 - B.x0, v: B.top - y0, nu: nuB, nv: 3, mat: white) + // C: back, and front over the door bay (x 2.6...efemX0). + face(out, center: V3((C.x0 + C.x1) / 2, (C.top + y0) / 2, C.zb + 0.015), axis: 2, u: C.x1 - C.x0, v: C.top - y0, nu: 5, nv: 3, mat: white) + face(out, center: V3((C.x0 + efemX0) / 2, (C.top + y0) / 2, C.zf - 0.015), axis: 2, u: efemX0 - C.x0, v: C.top - y0, nu: 3, nv: 3, mat: white, + skip: { _, j in j < 2 }) + // EFEM front face and step side. + add(bx(V3(7.0 - efemX0 - 0.1, C.top - y0, 0.05), V3((efemX0 + 7.0) / 2, (C.top + y0) / 2, efemZ - 0.03), dark)) + face(out, center: V3((efemX0 + 7.0) / 2, (C.top + y0) / 2, efemZ), axis: 2, u: 7.0 - efemX0, v: C.top - y0, nu: 3, nv: 3, mat: white) + face(out, center: V3(efemX0 + 0.015, (C.top + y0) / 2, (efemZ + C.zf) / 2), axis: 0, u: C.zf - efemZ, v: C.top - y0, nu: 1, nv: 3, mat: white) + // End faces. + face(out, center: V3(7.0 - 0.015, (C.top + y0) / 2, (efemZ + C.zb) / 2), axis: 0, u: efemZ - C.zb, v: C.top - y0, nu: 3, nv: 3, mat: white) + face(out, center: V3(A.x0 + 0.015, (A.top + y0) / 2, 0), axis: 0, u: A.zf - A.zb, v: A.top - y0, nu: 3, nv: 3, mat: white, + skip: { i, j in i == 1 && j == 1 }) + // Seam dark filler between B and C/A already covered by module cores. + + // Vents: lower louvre banks on A back, B ends, C back. + vent(out, c: V3(-4.0, 0.55, A.zb - 0.005), w: 1.2, h: 0.5, n: hi ? 9 : 1, detail: hi) + vent(out, c: V3(0.2, 0.55, B.zb - 0.005), w: 1.4, h: 0.5, n: hi ? 9 : 1, detail: hi) + vent(out, c: V3(4.8, 0.55, C.zb - 0.005), w: 1.4, h: 0.5, n: hi ? 9 : 1, detail: hi) + vent(out, c: V3(-1.0, 0.55, B.zf + 0.005), w: 1.2, h: 0.5, n: hi ? 8 : 1, detail: hi) + + // Door recesses (dark interior behind the opening doors). + add(bx(V3(1.0, 1.9, 0.03), V3(-3.5, 1.15, A.zf - 0.1), dark)) + add(bx(V3(2.2, 1.9, 0.03), V3(3.75, 1.15, C.zf - 0.1), dark)) + if hi { + for k in 0..<4 { add(bx(V3(0.9, 0.05, 0.3), V3(-3.5, 0.5 + Float(k) * 0.45, A.zf - 0.28), anod)) } + for k in 0..<3 { add(bx(V3(2.0, 0.04, 0.25), V3(3.75, 0.5 + Float(k) * 0.6, C.zf - 0.28), anod)) } + add(bx(V3(0.05, 1.9, 0.3), V3(3.75, 1.15, C.zf - 0.28), steel)) + } + + // Plinth leveling feet on the grate: pad, stud, locknut. + let footXs: [[Float]] = [[A.x0 + 0.25, -4.0, A.x1 - 0.1], [B.x0 + 0.1, 0.1, B.x1 - 0.1], [C.x0 + 0.1, 4.8, C.x1 - 0.25]] + for (mi, mod) in [A, B, C].enumerated() { + for fx in footXs[mi] { + for fz in [mod.zf - 0.25, mod.zb + 0.25] { + add(cyl(0.07, 0.02, V3(fx, 0.05, fz), steel, seg: hi ? 14 : 6)) + add(cyl(0.022, 0.07, V3(fx, 0.07, fz), steel, seg: hi ? 10 : 5)) + if hi { add(cyl(0.035, 0.015, V3(fx, 0.115, fz), anod, seg: 6)) } + } + } + } + // Hazard strip and caution label on the source module plinth (story detail). + add(bx(V3(3.0, 0.1, 0.006), V3(-4.0, 0.3, A.zf + 0.001), "plastic.yellow")) + if hi { for k in 0..<14 { add(bx(V3(0.07, 0.1, 0.007), V3(-5.3 + Float(k) * 0.2, 0.3, A.zf + 0.0015), black)) } } + + // Source duct at rear (-X): flanged bellows to the source vessel. + var prof: [V2] = [V2(0, 0), V2(0.68, 0), V2(0.68, 0.06), V2(0.5, 0.06), V2(0.5, 0.12)] + let nr = hi ? 7 : 3 + for k in 0.. Void = { rig.add($0.0, $0.1, to: name) } + face(out, center: V3(cx, y1 - 0.015, cz), axis: 1, u: w, v: d, nu: nu, nv: nvTop, mat: white) + face(out, center: V3(cx, y0h + h / 2, zf - 0.015), axis: 2, u: w, v: h, nu: nu, nv: 1, mat: white) + face(out, center: V3(cx, y0h + h / 2, zb + 0.015), axis: 2, u: w, v: h, nu: nu, nv: 1, mat: white) + face(out, center: V3(x0 + 0.015, y0h + h / 2, cz), axis: 0, u: d, v: h, nu: 2, nv: 1, mat: white) + face(out, center: V3(x1 - 0.015, y0h + h / 2, cz), axis: 0, u: d, v: h, nu: 2, nv: 1, mat: white) + // Lifting eyes and edge trim. + for sx: Float in [-1, 1] { + for sz: Float in [-1, 1] { + rig.add(Prim.torus(major: 0.03, minor: 0.008, segments: 8, sides: 4, material: steel), + Xform(translation: V3(cx + sx * (w / 2 - 0.25), y1 + 0.008, cz + sz * (d / 2 - 0.25))), to: name, lods: 0...0) + } + } + } + hood("cover-hood", x0: B.x0, x1: B.x1, y0h: B.top + 0.06, y1: 4.0, zf: B.zf, zb: B.zb, nu: 5, nvTop: 4, lift: 2.2) + hood("cover-source", x0: A.x0, x1: A.x1, y0h: A.top, y1: 3.4, zf: A.zf, zb: A.zb, nu: 4, nvTop: 3, lift: 1.6) + hood("cover-handler", x0: C.x0, x1: efemX0 + 2.1, y0h: C.top, y1: 2.38, zf: C.zf, zb: C.zb, nu: 4, nvTop: 3, lift: 1.2) + // Lid sides are 8 cm so the handler cover reads as a slim lid; ensure roof items sit above it. + + // ---- Doors ---- + func door(_ name: String, cx: Float, w: Float, zFace: Float, hingeLeft: Bool, vents: Bool) { + let hy: Float = 1.15, h: Float = 1.9 + let hx = cx + (hingeLeft ? -w / 2 : w / 2) + rig.part(name, pivot: V3(hx, hy, zFace), joint: .hinge(axis: .up, hingeLeft ? -110...0 : 0...110, duration: 1.2)) + rig.add(Prim.extrude(Shape2D.rect(w - 0.01, h), depth: 0.04, bevel: 0.006, bevelSegments: 1, material: white), + Xform(translation: V3(cx, hy, zFace - 0.01)), to: name) + if vents { + for k in 0..<7 { + rig.add(Prim.extrude(Shape2D.rect(w - 0.3, 0.02), depth: 0.012, bevel: 0.002, bevelSegments: 1, material: dark), + Xform(translation: V3(cx, hy + 0.55 + Float(k) * 0.045, zFace + 0.012)), to: name, lods: 0...0) + } + } + let hdx = cx + (hingeLeft ? 1 : -1) * (w / 2 - 0.1) + rig.add(Prim.extrude(Shape2D.rect(0.03, 0.3), depth: 0.025, bevel: 0.004, bevelSegments: 1, material: anod), + Xform(translation: V3(hdx, hy - 0.1, zFace + 0.025)), to: name) + for k in 0..<3 { + rig.add(Prim.cylinder(radius: 0.02, height: 0.03, bevel: 0.003, segments: 10, material: steel), + Xform(translation: V3(hx + (hingeLeft ? 0.01 : -0.01), hy - 0.7 + Float(k) * 0.7, zFace - 0.02), + rotation: simd_quatf(degrees: 90, axis: V3(1, 0, 0))), to: name, lods: 0...0) + } + rig.add(Prim.extrude(Shape2D.rect(0.2, 0.12), depth: 0.006, bevel: 0.002, bevelSegments: 1, material: "plastic.polycarbonate-tint"), + Xform(translation: V3(cx, hy + 0.2, zFace + 0.012)), to: name, lods: 0...0) + } + door("door-service-a", cx: -3.5, w: 1.0, zFace: A.zf, hingeLeft: true, vents: true) + door("door-handler-l", cx: 3.25, w: 1.1, zFace: C.zf, hingeLeft: true, vents: false) + door("door-handler-r", cx: 4.35, w: 1.1, zFace: C.zf, hingeLeft: false, vents: true) + + // ---- Wafer stage (under the hood) ---- + rig.part("wafer-stage", pivot: V3(0.9, 2.76 + 0.33, 0.3), joint: .slide(axis: V3(1, 0, 0), -0.8...0.8, duration: 3.0)) + let sy: Float = 2.76 + 0.34 + func sadd(_ b: (Surface, Xform), lods: ClosedRange? = nil) { rig.add(b.0, b.1, to: "wafer-stage", lods: lods) } + sadd(bx(V3(1.0, 0.14, 0.9), V3(0.9, sy + 0.07, 0.3), anod, r: 0.01)) + sadd(bx(V3(0.8, 0.06, 0.7), V3(0.9, sy + 0.17, 0.3), "metal.aluminum-brushed", r: 0.006)) + sadd(cyl(0.2, 0.03, V3(0.9, sy + 0.2, 0.3), "metal.aluminum-brushed", seg: 40)) + sadd(cyl(0.15, 0.008, V3(0.9, sy + 0.232, 0.3), "silicon.thinfilm", seg: 40)) + sadd(bx(V3(0.02, 0.05, 0.72), V3(0.43, sy + 0.2, 0.3), "metal.steel"), lods: 0...0) + sadd(bx(V3(0.72, 0.05, 0.02), V3(0.9, sy + 0.2, 0.67), "metal.steel"), lods: 0...0) + for sx: Float in [-1, 1] { sadd(bx(V3(0.05, 0.05, 0.9), V3(0.9 + sx * 0.5, sy + 0.04, 0.3), black), lods: 0...0) } + sadd((Prim.tube([V3(1.4, sy + 0.08, 0.3), V3(1.6, sy + 0.3, 0.3), V3(1.9, sy + 0.2, 0.3), V3(2.1, sy + 0.05, 0.3)], radii: [0.02, 0.02, 0.02, 0.02], sides: 8, seamTile: 0.3, material: "rubber.tubing"), .identity), lods: 0...0) + + groundAO(&rig, height: 0.15, floor: 0.6) + rig.states = [RigState("closed"), + RigState("covers-off", ["cover-hood": 2.2, "cover-source": 1.6, "cover-handler": 1.2]), + RigState("doors-open", ["door-service-a": -100, "door-handler-l": -100, "door-handler-r": 100]), + RigState("stage-exposed", ["cover-hood": 2.2, "wafer-stage": 0.6])] + return rig + } +} diff --git a/Sources/RealLibrary/Props/Fab/FabBevel.swift b/Sources/RealLibrary/Props/Fab/FabBevel.swift new file mode 100644 index 00000000..126d1dd2 --- /dev/null +++ b/Sources/RealLibrary/Props/Fab/FabBevel.swift @@ -0,0 +1,39 @@ +import simd +import Foundation + +/// Bevelled box, wear and fingerprint helpers for fab tools. Every edge gets a 2-5 mm bevel. +extension ClusterKit { + /// Box with all edges bevelled at `r` clamped to 2-5 mm (thin plates under 12 mm use sharp cuboids). + public func bb(_ s: V3, _ c: V3, _ mat: MaterialKey, r: Float = 0.003, yaw: Float = 0, name: String? = nil) { + let lim = min(s.x, min(s.y, s.z)) + let xf = Xform(translation: c, rotation: simd_quatf(degrees: yaw, axis: V3(0, 1, 0))) + if lim < 0.012 { put(cuboid(s, material: mat), xf, name); return } + let b = min(min(max(r, 0.002), 0.005), lim * 0.4) + put(Prim.extrude(Shape2D.roundedRect(s.x, s.y, radius: b * 1.5, segments: 2), depth: s.z, bevel: b, bevelSegments: 1, material: mat), xf, name) + } + /// Scuffs: bare-metal streaks and grime patches on a +Z facing surface at `c`, spread over `w` x `h`. + public func scuffs(_ c: V3, w: Float, h: Float, n: Int = 5, name: String? = nil) { + for i in 0.. Surface { + Prim.extrude(Shape2D.rect(s.x, s.y), depth: s.z, bevel: 0, bevelSegments: 1, material: mat) + } + + public func build(seed: UInt64) -> LODModel { + var rng = SeededRNG(seed: seed) + var m = Model(name: Self.id) + let wh = "metal.powdercoat:F4F4F2", al = "metal.aluminum-brushed" + // Frame bars, back tray, amber diffuser, prismatic ribs, end caps. + for s: Float in [-1, 1] { + m.add(bx(V3(1.2, 0.03, 0.025), 0.004, wh), Xform(translation: V3(0, 0.015, s * 0.2875))) + m.add(bx(V3(0.025, 0.03, 0.55), 0.004, wh), Xform(translation: V3(s * 0.5875, 0.015, 0))) + } + m.add(bx(V3(1.16, 0.02, 0.56), 0.003, wh), Xform(translation: V3(0, 0.035, 0))) + m.add(bx(V3(1.15, 0.008, 0.55), 0.002, "emissive.amber"), Xform(translation: V3(0, 0.008, 0))) + for i in 0..<11 { + m.add(bx(V3(0.006, 0.004, 0.55), 0.001, "emissive.amber"), Xform(translation: V3(-0.5 + Float(i) * 0.1, 0.002, 0))) + } + for sx: Float in [-1, 1] { for sz: Float in [-1, 1] { + m.add(Prim.cylinder(radius: 0.008 / 2, height: 0.008 * 0.5, bevel: 0, segments: 6, material: al), Xform(translation: V3(sx * 0.5875, 0.03, sz * 0.2875))) + } } + m.add(bx(V3(0.08, 0.012, 0.04), 0.002, al), Xform(translation: V3(0.3 + rng.float(-0.01...0.01), 0.044, 0))) + // Laid face-up so the diffuser reads from above. + m = m.transformed(Xform(rotation: simd_quatf(degrees: 180, axis: V3(1, 0, 0)))) + let b = m.bounds; m = m.transformed(Xform(translation: V3(0, -b.min.y, 0))) + return LODModel(m) + } +} diff --git a/Sources/RealLibrary/Props/Fab/FfuCeilingTile.swift b/Sources/RealLibrary/Props/Fab/FfuCeilingTile.swift new file mode 100644 index 00000000..c538dfd5 --- /dev/null +++ b/Sources/RealLibrary/Props/Fab/FfuCeilingTile.swift @@ -0,0 +1,59 @@ +import simd +import Foundation + +/// Fan filter unit: white sheet-metal plenum box, HEPA pleat pack behind a slotted grille, fan collar. +public struct FfuCeilingTile: RealAsset { + public static let id = "ffu-ceiling-tile" + public static let summary = "Fan filter unit: white sheet-metal plenum box, HEPA pleat pack behind a slotted grille, fan collar." + public static let tags = ["prop", "semiconductor"] + public static let budget = 1200 + public static let author = "realityhd" + + /// Overall size in meters. + public var size = V3(1.2, 0.3, 0.6) + public init() {} + + private func bx(_ s: V3, _ r: Float, _ mat: String) -> Surface { + Prim.extrude(Shape2D.rect(s.x, s.y), depth: s.z, bevel: 0, bevelSegments: 1, material: mat) + } + + public func build(seed: UInt64) -> LODModel { + var rng = SeededRNG(seed: seed) + var m = Model(name: Self.id) + let wh = "metal.powdercoat:F4F4F2", al = "metal.aluminum-brushed" + // Housing box above the flange, bottom frame at y 0. + m.add(bx(V3(1.16, 0.24, 0.56), 0.004, wh), Xform(translation: V3(0, 0.17, 0))) + m.add(bx(V3(1.2, 0.03, 0.6), 0.004, wh), Xform(translation: V3(0, 0.015, 0))) + // HEPA pleat pack: zigzag section extruded along Z, inside the frame opening. + var pts: [V2] = [] + let n = 14, w: Float = 1.12, pitch = w / Float(n) + for i in 0...n { pts.append(V2(-w / 2 + Float(i) * pitch, i % 2 == 0 ? 0.0 : 0.03)) } + for i in stride(from: n, through: 0, by: -1) { pts.append(V2(-w / 2 + Float(i) * pitch, i % 2 == 0 ? 0.012 : 0.042)) } + m.add(Prim.extrude(pts, depth: 0.52, bevel: 0, bevelSegments: 1, material: "filter.hepa"), + Xform(translation: V3(0, 0.03, 0))) + m.add(bx(V3(1.14, 0.012, 0.54), 0.002, "filter.hepa"), Xform(translation: V3(0, 0.026, 0))) + // Slotted grille: aluminum bars along Z across the face, plus a perimeter rim. + for i in 0..<8 { + let x = -0.55 + Float(i) * 0.157 + m.add(bx(V3(0.012, 0.01, 0.56), 0.002, al), Xform(translation: V3(x, 0.005, 0))) + } + for s: Float in [-1, 1] { + m.add(bx(V3(1.2, 0.012, 0.02), 0.003, al), Xform(translation: V3(0, 0.006, s * 0.29))) + m.add(bx(V3(0.02, 0.012, 0.6), 0.003, al), Xform(translation: V3(s * 0.59, 0.006, 0))) + } + // Fan collar and duct spigot on top, lid ribs. + m.add(Prim.cylinder(radius: 0.14, height: 0.03, bevel: 0.004, segments: 20, material: wh), Xform(translation: V3(0, 0.27, 0))) + m.add(Prim.cylinder(radius: 0.1, height: 0.012, bevel: 0.002, segments: 20, material: "metal.stainless"), Xform(translation: V3(0, 0.287, 0))) + for sx: Float in [-1, 1] { + m.add(bx(V3(0.03, 0.02, 0.56), 0.003, wh), Xform(translation: V3(sx * 0.4, 0.29, 0))) + } + // Side rating plate and flange screws. + m.add(bx(V3(0.1, 0.05, 0.003), 0.0005, "metal.stainless"), Xform(translation: V3(0.3, 0.17, 0.281))) + for sx: Float in [-1, 1] { for sz: Float in [-1, 1] { + m.add(Prim.cylinder(radius: 0.01 / 2, height: 0.01 * 0.5, bevel: 0, segments: 6, material: al), Xform(translation: V3(sx * 0.57, 0.03, sz * 0.27))) + } } + _ = rng.float() + let b = m.bounds; m = m.transformed(Xform(translation: V3(0, -b.min.y, 0))) + return LODModel(m) + } +} diff --git a/Sources/RealLibrary/Props/Fab/Foup.swift b/Sources/RealLibrary/Props/Fab/Foup.swift new file mode 100644 index 00000000..5d37413b --- /dev/null +++ b/Sources/RealLibrary/Props/Fab/Foup.swift @@ -0,0 +1,90 @@ +import simd +import Foundation + +/// FOUP: SEMI E47.1 300 mm front-opening pod, smoke polycarbonate, light handling wear. +public struct Foup: RealAsset { + public static let id = "foup" + public static let summary = "25-slot front-opening pod for 300 mm wafers with top flange, side handles and door." + public static let tags = ["prop", "semiconductor", "handheld", "articulated"] + public static let budget = 5000 + public static let author = "realityhd" + + /// Overall size in meters (x width incl. handles, y height incl. flange, z depth incl. door). + public var size = V3(0.4, 0.34, 0.34) + /// Wafers loaded (0...25). + public var wafers = 25 + /// Shell material key. + public var shellMaterial = "plastic.polycarbonate-tint" + /// Door and frame material key. + public var doorMaterial = "plastic.matte:3A3C40" + public init() {} + + /// Beveled box from an extruded rectangle (about 40 tris). + private func bx(_ s: V3, _ r: Float, _ mat: String) -> Surface { + Prim.extrude(Shape2D.rect(s.x, s.y), depth: s.z, bevel: min(r, s.min() * 0.4), bevelSegments: 1, material: mat) + } + + public func build(seed: UInt64) -> LODModel { + var rng = SeededRNG(seed: seed) + var m = Model(name: Self.id) + let w: Float = 0.316, shellY0: Float = 0.014, shellY1: Float = 0.300 + let z0: Float = -0.16, z1: Float = 0.16, zc = (z0 + z1) / 2 + // Kinematic coupling plate with three V-groove pads. + m.add(bx(V3(0.30, 0.014, 0.30), 0.003, doorMaterial), + Xform(translation: V3(0, 0.007, zc))) + for (i, p) in [V3(-0.07, 0, 0.05), V3(0.07, 0, 0.05), V3(0, 0, -0.09)].enumerated() { + m.add(Prim.cylinder(radius: 0.012, height: 0.003, bevel: 0.001, segments: 10, material: "metal.aluminum-brushed"), + Xform(translation: V3(p.x, -0.0005, zc + p.z)).jittered(&rng)) + _ = i + } + // Shell: open-front box walls (back, top, bottom, sides) in translucent polycarbonate. + let h = shellY1 - shellY0, t: Float = 0.004 + m.add(bx(V3(w, t, z1 - z0), 0.0015, shellMaterial), Xform(translation: V3(0, shellY1 - t / 2, zc))) + m.add(bx(V3(w, t, z1 - z0), 0.0015, shellMaterial), Xform(translation: V3(0, shellY0 + t / 2, zc))) + m.add(bx(V3(w, h, t), 0.0015, shellMaterial), Xform(translation: V3(0, shellY0 + h / 2, z0 + t / 2))) + for s: Float in [-1, 1] { + m.add(bx(V3(t, h, z1 - z0), 0.0015, shellMaterial), Xform(translation: V3(s * (w - t) / 2, shellY0 + h / 2, zc))) + // Vertical stiffening ribs on the outside of each side wall. + for k in 0..<3 { + let zz = z0 + 0.06 + Float(k) * 0.10 + m.add(bx(V3(0.004, h - 0.03, 0.005), 0.0015, shellMaterial), + Xform(translation: V3(s * (w / 2 + 0.002), shellY0 + h / 2, zz))) + } + // Side carry handle. + var hd = barHandle(length: 0.12, standoff: 0.036, radius: 0.007, material: doorMaterial) + hd.recomputeNormals() + m.add(hd, Xform(translation: V3(s * (w / 2 + 0.002), 0.17, zc), rotation: simd_quatf(degrees: s > 0 ? 90 : -90, axis: V3(0, 1, 0)))) + } + // Wafer support teeth, two per side. + for s: Float in [-1, 1] { + for zz: Float in [0.0] { + m.add(bx(V3(0.014, 0.25, 0.012), 0.002, "plastic.matte:E8E4D8"), + Xform(translation: V3(s * (w / 2 - t - 0.007), 0.16, zc + zz))) + } + } + // Wafers. + for i in 0.. Rig { + _ = seed + var rig = Rig(name: Self.id, lods: 2, switchDistances: [12]) + let white: MaterialKey = "metal.powdercoat:" + String(format: "%06X", color) + let grey: MaterialKey = "metal.powdercoat:C4C8CC", dark: MaterialKey = "metal.powdercoat:34373C" + let steel: MaterialKey = "metal.stainless", anod: MaterialKey = "metal.anodized", black: MaterialKey = "plastic.matte:1E2023" + let glass: MaterialKey = "glass.window-tint", pc: MaterialKey = "plastic.polycarbonate-tint" + let y0: Float = 0.12, top: Float = 3.55, hw: Float = 3.0, zf: Float = 1.0, zb: Float = -1.0 + let wy0: Float = 1.75, wy1: Float = 3.3, wx: Float = 2.7, zl: Float = 0.58 + let rz: Float = -0.1, rx: Float = 0.6, ly: Float = 2.0 + rig.part("robot-carriage", pivot: V3(rx, y0, rz), joint: .slide(axis: V3(1, 0, 0), -2.6...2.6, duration: 6)) + rig.part("robot-lift", parent: "robot-carriage", pivot: V3(rx, ly, rz), joint: .slide(axis: V3(0, 1, 0), -1.4...1.2, duration: 4)) + rig.part("robot-arm", parent: "robot-lift", pivot: V3(rx, ly + 0.1, rz), joint: .slide(axis: V3(0, 0, -1), 0...0.5, duration: 2)) + for l in 0..<2 { + let k = ClusterKit(rig: rig, lod: l) + let hi = k.hi + // Floor plinth and shell. Back wall, side walls, roof, lower front, upper front band, front pillars. + k.bb(V3(6.0, y0, 2.0), V3(0, y0 / 2, 0), dark, r: 0.005) + k.bb(V3(6.0, top - y0, 0.04), V3(0, (top + y0) / 2, zb + 0.02), white, r: 0.004) + for s: Float in [-1, 1] { + k.bb(V3(0.04, top - y0, 2.0), V3(s * (hw - 0.02), (top + y0) / 2, 0), white, r: 0.004) + k.bb(V3(0.3, wy1 - wy0 + 0.1, 0.08), V3(s * 2.85, (wy0 + wy1) / 2, zf - 0.04), white, r: 0.004) + } + k.bb(V3(6.0, 0.05, 2.0), V3(0, top + 0.025, 0), white, r: 0.005) + k.bb(V3(6.04, 0.02, 2.04), V3(0, top + 0.06, 0), grey, r: 0.004) + k.bb(V3(6.0, wy0 - y0 - 0.05, 0.04), V3(0, (wy0 + y0 - 0.05) / 2, zl), white, r: 0.004) + k.bb(V3(6.0, top - wy1 + 0.0, 0.05), V3(0, (top + wy1) / 2, zf - 0.025), white, r: 0.004) + k.bb(V3(6.0, 0.05, zf - zl), V3(0, wy0 - 0.075, (zf + zl) / 2), grey, r: 0.003) + k.bb(V3(5.4, 0.05, 0.06), V3(0, wy0 - 0.025, zf - 0.03), anod, r: 0.003) + // Glass front window with frame and fingerprints. + k.bb(V3(2 * wx, wy1 - wy0, 0.012), V3(0, (wy0 + wy1) / 2, zf - 0.02), glass, r: 0.0) + k.bb(V3(2 * wx + 0.1, 0.05, 0.07), V3(0, wy1 + 0.0, zf - 0.035), anod, r: 0.003) + for q: Float in [-2.7, -0.9, 0.9, 2.7] { k.bb(V3(0.05, wy1 - wy0, 0.07), V3(q, (wy0 + wy1) / 2, zf - 0.035), anod, r: 0.003) } + k.bb(V3(0.4, 0.4, 0.002), V3(0, 3.0, zf - 0.07), "plastic.matte:E8E8E4", r: 0) + k.prints(V3(-1.8, 2.4, zf - 0.011), w: 1.6, h: 0.8, n: hi ? 7 : 0) + // Interior: back racks, uprights, lighting. + k.bb(V3(5.4, wy1 - wy0 - 0.1, 0.02), V3(0, (wy0 + wy1) / 2, zb + 0.05), "metal.powdercoat:B8BCC0", r: 0.002) + k.bb(V3(5.4, 0.04, 0.5), V3(0, wy0 - 0.02 + 0.0, -0.7), grey, r: 0.002) + let cols = 9, rows = 4 + let px: Float = 0.58, pyy: Float = 0.38, ry0: Float = wy0 + 0.1 + for r in 0...rows { + k.bx(V3(5.4, 0.011, 0.46), V3(0, ry0 + Float(r) * pyy, -0.73), steel, r: 0) + } + for c in 0...cols { k.bx(V3(0.03, Float(rows) * pyy + 0.03, 0.46), V3((Float(c) - Float(cols) / 2) * px, ry0 + Float(rows) * pyy / 2, -0.73), steel, r: 0) } + for r in 0.. Rig { + _ = seed + var rig = Rig(name: Self.id, lods: 2, switchDistances: [8]) + let white: MaterialKey = "metal.powdercoat:" + String(format: "%06X", color) + let dark: MaterialKey = "metal.powdercoat:34373C", grey: MaterialKey = "metal.powdercoat:C4C8CC", inner: MaterialKey = "metal.powdercoat:D6D8D6" + let steel: MaterialKey = "metal.stainless", anod: MaterialKey = "metal.anodized", black: MaterialKey = "plastic.matte:1E2023" + let yel: MaterialKey = "plastic.yellow", glass: MaterialKey = "glass.window-tint" + let zf: Float = 1.25, zb: Float = -1.25, x0: Float = -1.5, xs: Float = 0.8, x1: Float = 1.5 + // Door opening in the module front: x -1.3...0.55, y 0.5...1.75. Hinge on the left edge. + let ox0: Float = -1.3, ox1: Float = 0.55, oy0: Float = 0.5, oy1: Float = 1.75 + let dw = ox1 - ox0, dh = oy1 - oy0 + rig.part("door", pivot: V3(ox0, 0, zf + 0.02), joint: .hinge(axis: V3(0, 1, 0), -110...0, duration: 1.4)) + for l in 0..<2 { + let k = ClusterKit(rig: rig, lod: l) + let hi = k.hi + let mw = xs - x0, mc = (x0 + xs) / 2 + // Plinth, process module shell (side walls, back, front frame around the opening), floor. + k.bb(V3(2.96, 0.1, 2.46), V3(0, 0.05, 0), black, r: 0.004) + k.bb(V3(0.04, 1.9, 2.5), V3(x0 + 0.02, 1.05, 0), white, r: 0.004) + k.bb(V3(0.04, 1.9, 2.5), V3(xs - 0.02, 1.05, 0), white, r: 0.004) + k.bb(V3(mw, 1.9, 0.04), V3(mc, 1.05, zb + 0.02), white, r: 0.004) + k.bb(V3(mw, 0.5 - 0.1, 0.04), V3(mc, 0.3, zf - 0.02), white, r: 0.004) // front lower + k.bb(V3(mw, 2.0 - oy1, 0.04), V3(mc, (oy1 + 2.0) / 2, zf - 0.02), white, r: 0.004) // front upper + k.bb(V3(ox0 - x0, dh, 0.04), V3((x0 + ox0) / 2, (oy0 + oy1) / 2, zf - 0.02), white, r: 0.004) + k.bb(V3(xs - ox1, dh, 0.04), V3((ox1 + xs) / 2, (oy0 + oy1) / 2, zf - 0.02), white, r: 0.004) + k.bb(V3(mw - 0.08, 0.03, 2.46), V3(mc, 0.115, 0), inner, r: 0.003) // floor + // Stepped roof: low shoulder front and back, tall centre block over the bond head, rear service tier. + k.bb(V3(mw, 0.06, 2.5), V3(mc, 2.0, 0), white, r: 0.005) + k.bb(V3(mw - 0.2, 0.17, 1.3), V3(mc, 2.115, 0.1), white, r: 0.005) + k.bb(V3(mw - 0.22, 0.012, 1.34), V3(mc, 2.2, 0.1), anod, r: 0.003) + k.bb(V3(mw - 0.3, 0.14, 0.5), V3(mc, 2.1, zb + 0.4), grey, r: 0.004) + for q in 0..<(hi ? 6 : 3) { k.bb(V3(0.5, 0.008, 0.012), V3(mc - 0.2, 2.176, zb + 0.22 + Float(q) * 0.07), dark, r: 0) } // roof louvres + k.bb(V3(mw + 0.02, 0.05, 0.05), V3(mc, 1.96, zf + 0.012), anod, r: 0.003) // front cornice + // Side cabinet: taller than the plinth, two panel doors with handles, vent louvres, control screen. + let cw = x1 - xs + k.bb(V3(cw, 1.7, 2.46), V3((xs + x1) / 2, 0.95, 0), white, r: 0.005) + k.bb(V3(cw + 0.02, 0.04, 2.5), V3((xs + x1) / 2, 1.82, 0), grey, r: 0.004) + k.bb(V3(cw - 0.14, 1.2, 0.012), V3((xs + x1) / 2, 0.8, zf - 0.006), grey, r: 0.003) + k.handle(V3(xs + 0.12, 0.9, zf - 0.012), len: 0.26) + k.scuffs(V3(xs + 0.12, 0.9, zf + 0.037), w: 0.03, h: 0.2, n: 4) + k.bb(V3(0.5, 0.3, 0.05), V3(1.16, 1.5, zf + 0.02), black, r: 0.004) // screen housing + k.bb(V3(0.44, 0.24, 0.006), V3(1.16, 1.5, zf + 0.047), "plastic.matte:16283C", r: 0.002) + k.prints(V3(1.16, 1.5, zf + 0.0505), w: 0.4, h: 0.2, n: hi ? 5 : 3) + for q in 0..<(hi ? 8 : 3) { k.bb(V3(0.4, 0.01, 0.01), V3(1.16, 0.3 + Float(q) * 0.03, zf + 0.002), dark, r: 0) } + for q in 0..<(hi ? 10 : 4) { k.bb(V3(0.01, 0.4, 0.01), V3(xs + 0.0, 1.25, -0.2 - Float(q) * 0.05), dark, r: 0) } + k.bb(V3(cw, 0.07, 2.46), V3((xs + x1) / 2, 0.135, 0), black, r: 0.003) + // Hazard tape and equipment label plate on module front. + k.bb(V3(0.9, 0.06, 0.004), V3(-0.7, 0.26, zf + 0.002), yel, r: 0) + k.bb(V3(0.16, 0.08, 0.004), V3(0.35, 0.3, zf + 0.002), anod, r: 0) + FabParts.statusTower(k, V3((xs + x1) / 2, 1.84, zb + 0.5)) + // Interior: back-wall frame, wafer stage on the floor, lower chuck with wafer, loading chuck, gantry and bond head. + k.bb(V3(mw - 0.12, 1.0, 0.06), V3(mc, 1.1, zb + 0.07), dark, r: 0.004) + k.bb(V3(1.2, 0.18, 0.9), V3(-0.4, 0.24, 0.3), steel, r: 0.004) // granite-look stage base + k.bb(V3(0.9, 0.06, 0.6), V3(-0.4, 0.37, 0.3), anod, r: 0.004) // XY stage + let seg = hi ? 28 : 12 + for (cx, cz, state) in [(Float(-0.4), Float(0.3), "silicon.thinfilm-patterned"), (Float(-0.95), Float(0.3), "silicon.thinfilm")] as [(Float, Float, MaterialKey)] { + k.put(Prim.lathe([V2(0, 0), V2(0.2, 0), V2(0.2, 0.05), V2(0.17, 0.065), V2(0, 0.065)], segments: seg, seamTile: 0.4, material: anod), Xform(translation: V3(cx, 0.4, cz))) + k.put(Prim.lathe([V2(0, 0), V2(0.15, 0), V2(0.15, 0.0008), V2(0, 0.0008)], segments: seg, seamTile: 0.4, material: state), Xform(translation: V3(cx, 0.466, cz))) + k.put(Prim.lathe([V2(0.17, 0), V2(0.2, 0), V2(0.2, 0.004), V2(0.17, 0.004)], segments: seg, seamTile: 0.4, material: steel), Xform(translation: V3(cx, 0.4655, cz))) + } + k.bb(V3(0.1, 0.5, 0.1), V3(-0.4, 0.9, zb + 0.35), dark, r: 0.004) // pillar + k.bb(V3(1.0, 0.14, 0.12), V3(-0.4, 1.3, zb + 0.45), grey, r: 0.004) // bridge + k.bb(V3(0.3, 0.3, 0.3), V3(-0.4, 1.38, 0.0), anod, r: 0.004) // bond head housing + k.cyl(0.05, 0.18, V3(-0.4, 1.17, 0.3), steel, seg: 16) // piston + k.put(Prim.lathe([V2(0, 0), V2(0.2, 0), V2(0.2, 0.05), V2(0.17, 0.065), V2(0, 0.065)], segments: seg, seamTile: 0.4, material: anod), Xform(translation: V3(-0.4, 1.0, 0.3))) + k.put(Prim.lathe([V2(0, 0), V2(0.15, 0), V2(0.15, 0.0008), V2(0, 0.0008)], segments: seg, seamTile: 0.4, material: "silicon.thinfilm-metal"), Xform(translation: V3(-0.4, 0.999, 0.3))) + k.bb(V3(0.14, 0.1, 0.4), V3(-0.4, 1.3, 0.1), anod, r: 0.004) // head arm + if hi { for q: Float in [-1, 1] { k.tube([V3(-0.4 + q * 0.08, 1.3, zb + 0.5), V3(-0.4 + q * 0.14, 1.0, zb + 0.5), V3(-0.4 + q * 0.14, 0.45, zb + 0.5)], 0.008, black, sides: 6) } } + k.bb(V3(0.016, 0.05, 0.016), V3(-0.4, 0.72, 0.3), steel, r: 0) // alignment pins + // Door: frame bars, window glass, hinge barrel, handle, fingerprints, door label. + let n = "door", dxc = (ox0 + ox1) / 2, dyc = (oy0 + oy1) / 2, dz = zf + 0.02, b: Float = 0.1 + k.bb(V3(dw, b, 0.04), V3(dxc, oy0 + b / 2, dz), white, r: 0.004, name: n) + k.bb(V3(dw, b, 0.04), V3(dxc, oy1 - b / 2, dz), white, r: 0.004, name: n) + for sx: Float in [-1, 1] { k.bb(V3(b, dh - 2 * b + 0.01, 0.04), V3(dxc + sx * (dw - b) / 2, dyc, dz), white, r: 0.004, name: n) } + k.bb(V3(dw - 2 * b + 0.01, dh - 2 * b + 0.01, 0.008), V3(dxc, dyc, dz), glass, r: 0.001, name: n) + k.bb(V3(dw - 2 * b + 0.04, 0.014, 0.012), V3(dxc, oy0 + b, dz + 0.014), steel, r: 0.002, name: n) + k.put(Prim.cylinder(radius: 0.014, height: dh - 0.1, bevel: 0.002, segments: hi ? 12 : 6, material: steel), Xform(translation: V3(ox0 - 0.008, dyc, dz)), n) + k.handle(V3(ox1 - 0.07, dyc, dz + 0.02), len: 0.4, name: n) + k.scuffs(V3(ox1 - 0.07, dyc, dz + 0.068), w: 0.03, h: 0.25, n: 4, name: n) + k.bb(V3(0.4, 0.05, 0.004), V3(dxc - 0.3, oy0 + 0.05, dz + 0.022), yel, r: 0, name: n) + for q in 0..<(hi ? 8 : 3) { + k.put(Prim.extrude(Shape2D.roundedRect(0.02, 0.028, radius: 0.0095, segments: 2), depth: 0.0006, bevel: 0, bevelSegments: 1, material: "plastic.matte:A8B0B4"), + Xform(translation: V3(dxc - 0.5 + Float((q * 37) % 11) * 0.09, dyc - 0.25 + Float((q * 53) % 9) * 0.06, dz + 0.0045)), n) + } + rig = k.rig + rig.base[l] = k.m + } + groundAO(&rig, height: 0.15, floor: 0.6) + rig.states = [RigState("closed"), RigState("open", ["door": -90])] + return rig + } +} diff --git a/Sources/RealLibrary/Props/Fab/IonImplanter.swift b/Sources/RealLibrary/Props/Fab/IonImplanter.swift new file mode 100644 index 00000000..6a07cc1b --- /dev/null +++ b/Sources/RealLibrary/Props/Fab/IonImplanter.swift @@ -0,0 +1,129 @@ +import simd +import Foundation + +/// Medium-current ion implanter, generic, 8 x 2.5 x 3 m. High-voltage source housing with ceramic insulator column and +/// warning labels, white paneled beamline with an analyzer magnet bulge on the back, roof pumps, end station with EFEM +/// and three FOUP load ports, status tower. Named parts: panel-1...panel-3 (front beamline access panels, hinged). +public struct IonImplanter: RealArticulated { + public static let id = "ion-implanter" + public static let summary = "Ion implanter: HV source housing with warning labels, beamline enclosure, analyzer magnet bulge, end station, load ports, hinged panels." + public static let tags = ["prop", "semiconductor", "articulated"] + public static let budget = 14000 + public static let author = "realityhd" + public static let preview = PreviewHint(azimuth: 25, elevation: 14, distance: 1.0, studio: true) + + /// Enclosure powdercoat (sRGB hex). + public var color: UInt32 = 0xEDEDE9 + public init() {} + + public func rig(seed: UInt64) -> Rig { + _ = seed + var rig = Rig(name: Self.id, lods: 2, switchDistances: [10]) + let white: MaterialKey = "metal.powdercoat:" + String(format: "%06X", color) + let dark: MaterialKey = "metal.powdercoat:34373C", inner: MaterialKey = "metal.powdercoat:C4C8CC" + let steel: MaterialKey = "metal.stainless", anod: MaterialKey = "metal.anodized", black: MaterialKey = "plastic.matte:1E2023" + let yel: MaterialKey = "plastic.yellow" + let y0: Float = 0.12, bz1: Float = 0.4, bz0: Float = -0.9, bh: Float = 1.75 + let pxs: [Float] = [-2.6, -1.3, 0.0] // panel left edges, width 1.25 + for (i, x) in pxs.enumerated() { + rig.part("panel-\(i + 1)", pivot: V3(x, 0.9, bz1), joint: .hinge(axis: .up, -110...0, duration: 1.2)) + } + for l in 0..<2 { + let k = ClusterKit(rig: rig, lod: l) + let hi = k.hi + FabParts.grate(k, w: 8.0, d: 3.0) + k.bx(V3(7.8, y0 - 0.04, 2.5), V3(0, (y0 + 0.05) / 2, -0.2), dark) + // HV source housing x -4..-2.7. + k.bx(V3(1.3, 1.8 - y0, 1.6), V3(-3.35, (1.8 + y0) / 2, -0.3), white, r: 0.006) + k.bx(V3(1.34, 0.04, 1.64), V3(-3.35, 1.82, -0.3), anod, r: 0.004) + k.bx(V3(0.9, 0.9, 0.02), V3(-3.35, 1.0, 0.51), dark, r: 0.003) // service door recess + k.bx(V3(0.86, 0.86, 0.01), V3(-3.35, 1.0, 0.525), inner, r: 0.002) + for q: Float in [-1, 1] { k.bx(V3(0.04, 0.12, 0.03), V3(-3.35 + q * 0.42, 1.0, 0.545), steel, r: 0.002) } + if hi { for q in 0..<8 { k.bx(V3(0.012, 0.3, 0.012), V3(-3.6 + Float(q) * 0.05, 1.55, 0.512), black, r: 0) } } + // Ceramic insulator column with ribs and top terminal. + var ins: [V2] = [V2(0, 0)] + for j in 0..<6 { let y = Float(j) * 0.085; ins += [V2(0.1, y), V2(0.1, y + 0.02), V2(0.17, y + 0.035), V2(0.17, y + 0.055), V2(0.1, y + 0.07)] } + ins += [V2(0.12, 0.54), V2(0, 0.54)] + k.put(Prim.lathe(ins, segments: hi ? 16 : 8, seamTile: 0.4, material: "plastic.matte:E8E4D8"), Xform(translation: V3(-3.35, 1.84, -0.3))) + k.cyl(0.26, 0.06, V3(-3.35, 2.41, -0.3), steel, seg: hi ? 18 : 8) + // Warning labels: yellow triangle, black rim, bolt bar. + for (lx, ly, lz) in [(Float(-3.0), Float(1.55), Float(0.512)), (Float(-3.7), Float(1.55), Float(0.512))] { + let tri: [V2] = [V2(0, 0.09), V2(-0.105, -0.09), V2(0.105, -0.09)] + k.put(Prim.extrude(tri.map { V2($0.x * 1.0, $0.y) }, depth: 0.004, bevel: 0, bevelSegments: 1, material: yel), Xform(translation: V3(lx, ly, lz))) + let tr2: [V2] = [V2(0, 0.065), V2(-0.075, -0.07), V2(0.075, -0.07)] + k.put(Prim.extrude(tr2, depth: 0.003, bevel: 0, bevelSegments: 1, material: black), Xform(translation: V3(lx, ly - 0.003, lz + 0.003))) + k.bx(V3(0.025, 0.07, 0.003), V3(lx, ly - 0.005, lz + 0.006), yel, r: 0) + } + k.bx(V3(0.36, 0.08, 0.004), V3(-3.35, 1.78, 0.512), yel, r: 0) + k.bx(V3(0.3, 0.012, 0.004), V3(-3.35, 1.65, 0.516), black, r: 0) + // Beamline enclosure x -2.7..1.3, front panels. + k.bx(V3(4.0, bh - y0, bz1 - bz0), V3(-0.7, (bh + y0) / 2, (bz1 + bz0) / 2), dark, r: 0.004) + k.bx(V3(4.04, 0.04, bz1 - bz0 + 0.04), V3(-0.7, bh + 0.02, (bz1 + bz0) / 2), white, r: 0.004) + k.bx(V3(4.0, 0.3 - y0 + 0.02, 0.02), V3(-0.7, (0.3 + y0) / 2, bz1 + 0.01), white, r: 0.003) + k.bx(V3(4.0, bh - 1.5, 0.02), V3(-0.7, (bh + 1.5) / 2, bz1 + 0.01), white, r: 0.003) + for (i, x) in pxs.enumerated() { + let nm = "panel-\(i + 1)" + k.put(cuboid(V3(1.23, 1.2, 0.025), material: white), Xform(translation: V3(x + 0.625, 0.9, bz1 + 0.0125)), nm) + k.put(cuboid(V3(1.15, 1.1, 0.01), material: "metal.powdercoat:E6E7E3"), Xform(translation: V3(x + 0.625, 0.9, bz1 + 0.03)), nm) + k.put(cuboid(V3(0.03, 0.2, 0.03), material: steel), Xform(translation: V3(x + 1.15, 0.95, bz1 + 0.04)), nm) + if hi { k.put(cuboid(V3(0.3, 0.012, 0.004), material: black), Xform(translation: V3(x + 0.625, 1.25, bz1 + 0.036)), nm) } + } + for x in [-2.65, -1.35, -0.05, 1.25] as [Float] { k.bx(V3(0.05, 1.2, 0.03), V3(x, 0.9, bz1 + 0.01), white, r: 0.002) } + if hi { + k.bx(V3(0.5, 0.1, 0.004), V3(-1.9, 1.6, bz1 + 0.022), yel, r: 0) // HV warning on beamline upper band + for q in 0..<4 { k.bx(V3(0.012, 0.22, 0.005), V3(-0.6 + Float(q) * 0.5, 1.62, bz1 + 0.022), black, r: 0) } + } + // Analyzer magnet bulge on the back: housing, coil rings, pole cap, beam tubes. + let mc = V3(-1.2, 0, -0.7) + let mag: [V2] = [V2(0, 0.3), V2(0.75, 0.3), V2(0.8, 0.36), V2(0.8, 1.9), V2(0.74, 1.96), V2(0.5, 1.96), V2(0, 1.96)] + k.put(Prim.lathe(mag, segments: hi ? 28 : 12, seamTile: 0.4, material: white), Xform(translation: mc)) + for y: Float in [0.5, 1.7] { k.put(Prim.lathe([V2(0.78, y), V2(0.84, y), V2(0.84, y + 0.18), V2(0.78, y + 0.18)], segments: hi ? 28 : 12, seamTile: 0.4, material: "metal.copper"), Xform(translation: mc)) } + k.cyl(0.4, 0.04, V3(mc.x, 1.98, mc.z), anod, seg: hi ? 20 : 8) + k.cyl(0.08, 0.2, V3(mc.x, 2.1, mc.z), steel, seg: 10) + k.cyl(0.1, 0.12, V3(mc.x + 0.9, 1.1, mc.z + 0.2), steel, seg: 12) + k.put(Prim.cylinder(radius: 0.09, height: 0.5, bevel: 0, segments: hi ? 14 : 6, material: steel), Xform(translation: V3(-2.55, 1.1, -0.45), rotation: simd_quatf(degrees: 90, axis: V3(0, 0, 1)))) + // Roof pumps, tray and scan magnet housing. + for (i, x) in [Float(-2.0), 0.3, 1.0].enumerated() where i != 0 || hi || true { + k.cyl(0.17, 0.4, V3(x, bh + 0.24, -0.25), steel, seg: hi ? 14 : 6) + k.cyl(0.2, 0.03, V3(x, bh + 0.065, -0.25), steel, seg: hi ? 14 : 6) + k.cyl(0.2, 0.03, V3(x, bh + 0.44, -0.25), anod, seg: hi ? 14 : 6) + } + k.bx(V3(1.0, 0.3, 0.5), V3(0.0, bh + 0.19, 0.1) + V3(-0.2, 0, 0), "metal.powdercoat:A8ACB0", r: 0.004) + if hi { for q in 0..<3 { k.tube([V3(-2.5 + Float(q) * 0.15, bh + 0.05, 0.05), V3(-2.5 + Float(q) * 0.15, bh + 0.35, -0.2), V3(-3.0, 2.0 - Float(q) * 0.05, -0.3)], 0.012, q == 1 ? "rubber.tubing" : "plastic.matte:2A2C2F", sides: 5) } } + // End station x 1.3..4, with process chamber dome and EFEM. + let eh: Float = 2.05 + k.bx(V3(2.7, eh - y0, bz1 - bz0 - 0.1), V3(2.65, (eh + y0) / 2, (bz1 + bz0) / 2 + 0.05), white, r: 0.006) + k.bx(V3(2.74, 0.04, bz1 - bz0), V3(2.65, eh + 0.02, (bz1 + bz0) / 2 + 0.05), anod, r: 0.004) + let dome: [V2] = [V2(0, 0), V2(0.75, 0), V2(0.75, 0.15), V2(0.6, 0.3), V2(0.3, 0.38), V2(0, 0.4)] + k.put(Prim.lathe(dome, segments: hi ? 24 : 10, seamTile: 0.4, material: "metal.powdercoat:C9CBCC"), Xform(translation: V3(2.2, eh + 0.04, -0.4))) + k.cyl(0.1, 0.04, V3(2.2, eh + 0.46, -0.4), "glass.window-tint", seg: 14) + // EFEM x 2.0..4.0, z 0.4..1.0. + let ezf: Float = 0.96, eh2: Float = 1.85 + k.bx(V3(2.0, eh2 - y0, ezf - bz1), V3(3.0, (eh2 + y0) / 2, (ezf + bz1) / 2), dark, r: 0.005) + for cxp: Float in [2.3, 2.9, 3.5] { + k.bx(V3(0.58, 0.52, 0.02), V3(cxp, y0 + 0.26, ezf + 0.008), white, r: 0.003) + k.bx(V3(0.58, 0.45, 0.02), V3(cxp, eh2 - 0.225, ezf + 0.008), white, r: 0.003) + } + k.bx(V3(0.2, 0.52, 0.02), V3(3.9, y0 + 0.26, ezf + 0.008), white, r: 0.003) + k.bx(V3(0.2, 0.45, 0.02), V3(3.9, eh2 - 0.225, ezf + 0.008), white, r: 0.003) + k.bx(V3(0.02, eh2 - y0 - 0.02, ezf - bz1), V3(4.0, (eh2 + y0) / 2, (ezf + bz1) / 2), white, r: 0.004, yaw: 0) + k.bx(V3(2.04, 0.04, ezf - bz1 + 0.04), V3(3.0, eh2 + 0.02, (ezf + bz1) / 2), white, r: 0.004) + for px: Float in [2.55, 3.1, 3.65] { FabParts.loadPort(k, x: px, z: ezf, frame: dark, skin: white) } + FabParts.statusTower(k, V3(3.8, eh2 + 0.04, 0.7)) + if hi { + k.bx(V3(0.006, 0.06, 0.8), V3(4.02, 0.4, 0.7), yel, r: 0.0005) + k.bx(V3(0.1, 0.06, 0.004), V3(2.2, 1.55, ezf + 0.02), "plastic.matte:EEEEEA", r: 0.0005) + k.bx(V3(0.5, 0.16, 0.004), V3(3.1, 0.34, ezf + 0.02), "plastic.matte:C8C8C2", r: 0.0005) + // Floor hazard border around HV source. + for (c, s) in [(V3(-3.35, 0.052, 0.62), V3(1.6, 0.004, 0.06)), (V3(-3.35, 0.052, -1.22), V3(1.6, 0.004, 0.06)), + (V3(-3.95, 0.052, -0.3), V3(0.06, 0.004, 1.9))] { k.bx(s, c, yel, r: 0) } + for fx: Float in [-3.8, -2.0, 0.3, 2.3, 3.8] { k.cyl(0.05, 0.018, V3(fx, 0.05, 0.6), steel, seg: 12) } + } + rig = k.rig + rig.base[l] = k.m + } + groundAO(&rig, height: 0.15, floor: 0.6) + rig.states = [RigState("closed"), RigState("panels-open", ["panel-1": -90, "panel-2": -90, "panel-3": -90])] + return rig + } +} diff --git a/Sources/RealLibrary/Props/Fab/LithoTrack.swift b/Sources/RealLibrary/Props/Fab/LithoTrack.swift new file mode 100644 index 00000000..d113255d --- /dev/null +++ b/Sources/RealLibrary/Props/Fab/LithoTrack.swift @@ -0,0 +1,293 @@ +import simd +import Foundation + +/// Litho coater/developer track, generic TEL/SCREEN-class, 8 x 2.5 x 3 m. White powdercoat process module with +/// seamed panels, tinted service windows over spin-coat bowls, a front EFEM with four FOUP load ports and docked +/// FOUPs, status tower, swing-arm touchscreen, leveling feet on a cleanroom grate. Named parts: panel-window-front-l, +/// panel-window-back-1...3, panel-end-l, panel-end-r, panel-roof-a, panel-roof-b. +public struct LithoTrack: RealArticulated { + public static let id = "litho-track" + public static let summary = "Coater/developer track: white paneled modules, tinted service windows with spin-coat bowls, 4 FOUP load ports, status tower, touchscreen." + public static let tags = ["prop", "semiconductor", "articulated"] + public static let budget = 14000 + public static let author = "realityhd" + public static let preview = PreviewHint(azimuth: 28, elevation: 14, distance: 1.0, studio: true) + + /// Enclosure powdercoat (sRGB hex). + public var color: UInt32 = 0xEDEDE9 + public init() {} + + public func rig(seed: UInt64) -> Rig { + _ = seed + var rig = Rig(name: Self.id, lods: 2, switchDistances: [10]) + let white: MaterialKey = "metal.powdercoat:" + String(format: "%06X", color) + let dark: MaterialKey = "metal.powdercoat:34373C" + let inner: MaterialKey = "metal.powdercoat:C4C8CC" + let anod: MaterialKey = "metal.anodized" + let steel: MaterialKey = "metal.stainless" + let black: MaterialKey = "plastic.matte:1E2023" + let y0: Float = 0.12 + let top: Float = 2.25 // process module roof + let efTop: Float = 2.10 // EFEM roof + let pzf: Float = 0.5, pzb: Float = -1.5 // process module z extent + let ezf: Float = 1.0 // EFEM front face + let ports: [Float] = [-2.025, -0.675, 0.675, 2.025] + let gap: Float = 0.006 + + // Named articulated panels. + rig.part("panel-end-l", pivot: V3(-4, 1.2, -0.5), joint: .slide(axis: V3(-1, 0, 0), 0...0.4, duration: 1.2)) + rig.part("panel-end-r", pivot: V3(4, 1.2, -0.5), joint: .slide(axis: V3(1, 0, 0), 0...0.4, duration: 1.2)) + rig.part("panel-roof-a", pivot: V3(-1, top, -0.5), joint: .slide(axis: V3(0, 1, 0), 0...0.5, duration: 1.2)) + rig.part("panel-roof-b", pivot: V3(1, top, -1.0), joint: .slide(axis: V3(0, 1, 0), 0...0.5, duration: 1.2)) + let winRows: [Float] = [0.45, 0.45, 0.9, 0.33] // back face: window row index 2 + let backCols: [Float] = [0.5, 1.7, 0.95, 1.7, 0.95, 1.7, 0.5] + let backWinX: [Float] = [-4 + 0.5, -4 + 0.5 + 1.7 + 0.95, -4 + 0.5 + 2 * (1.7 + 0.95)] + let frontWinX: Float = 2.7 + 0.1 - 6.7 // front-l window left edge = -4 + 0.1 + _ = frontWinX + let flX: Float = -4 + 0.1 + rig.part("panel-window-front-l", pivot: V3(flX, 1.47, pzf), joint: .hinge(axis: .up, -110...0, duration: 1.2)) + for i in 0..<3 { + rig.part("panel-window-back-\(i + 1)", pivot: V3(backWinX[i], 1.47, pzb), joint: .hinge(axis: .up, 0...110, duration: 1.2)) + } + + for l in 0..<2 { + let hi = l == 0 + var m = Model(name: Self.id) + func put(_ s: Surface, _ x: Xform, _ name: String? = nil) { + if let n = name { rig.add(s, x, to: n, lods: l...l) } else { m.add(s, x) } + } + func bx(_ s: V3, _ c: V3, _ mat: MaterialKey, r: Float = 0.004, name: String? = nil) { + let surf = r > 0.003 && s.min() > 0.06 + ? Prim.extrude(Shape2D.rect(s.x, s.y), depth: s.z, bevel: min(r, s.min() * 0.4), bevelSegments: 1, material: mat) + : cuboid(s, material: mat) + put(surf, Xform(translation: c), name) + } + func cyl(_ r: Float, _ h: Float, _ c: V3, _ mat: MaterialKey, seg: Int = 16, rot: simd_quatf = .identity) { + put(Prim.cylinder(radius: r, height: h, bevel: 0, segments: hi ? seg : max(5, seg / 2), material: mat), Xform(translation: c, rotation: rot)) + } + /// Panel grid. axis 0: x normal (a = z), 1: y normal (a = x, b = z), 2: z normal (a = x, b = y). + func grid(axis: Int, fixed: Float, a0: Float, cols: [Float], b0: Float, rows: [Float], mat: MaterialKey, + t: Float = 0.03, bev: Bool = false, skip: (Int, Int) -> Bool = { _, _ in false }, name: (Int, Int) -> String? = { _, _ in nil }) { + var a = a0 + for (i, cw) in cols.enumerated() { + var b = b0 + for (j, rh) in rows.enumerated() { + if !skip(i, j) { + let ac = a + cw / 2, bc = b + rh / 2 + var c = V3(ac, bc, fixed), rot = simd_quatf.identity + if axis == 0 { c = V3(fixed, bc, ac); rot = simd_quatf(degrees: 90, axis: V3(0, 1, 0)) } + if axis == 1 { c = V3(ac, fixed, bc); rot = simd_quatf(degrees: 90, axis: V3(1, 0, 0)) } + let ps = bev && hi + ? Prim.extrude(Shape2D.rect(cw - gap, rh - gap), depth: t, bevel: 0.004, bevelSegments: 1, material: mat) + : cuboid(V3(cw - gap, rh - gap, t), material: mat) + put(ps, Xform(translation: c, rotation: rot), name(i, j)) + } + b += rh + } + a += cw + } + } + + // Cleanroom grate floor. + m.add(Prim.extrude(Shape2D.rect(8.0, 3.0), depth: 0.05, bevel: 0.004, bevelSegments: 1, material: "metal.grate-iron"), + Xform(translation: V3(0, 0.025, 0), rotation: simd_quatf(degrees: 90, axis: V3(1, 0, 0)))) + if hi { + for k in 1..<13 { bx(V3(0.012, 0.004, 3.0), V3(-4 + Float(k) * 0.6, 0.051, 0), black, r: 0.001) } + for k in 1..<5 { bx(V3(8.0, 0.004, 0.012), V3(0, 0.051, -1.5 + Float(k) * 0.6), black, r: 0.001) } + } + + // Dark cores: process module, EFEM, recessed plinth. + bx(V3(7.9, top - y0 - 0.04, 0.9), V3(0, (top + y0 - 0.04) / 2, -0.5), dark) + bx(V3(5.3, efTop - y0 - 0.04, ezf - pzf - 0.04), V3(0, (efTop + y0 - 0.04) / 2, (pzf + ezf) / 2 - 0.02), dark) + bx(V3(7.5, y0 - 0.04, 1.7), V3(0, (y0 + 0.05) / 2, (pzf + pzb) / 2 + 0.2), dark) + bx(V3(5.0, y0 - 0.04, 0.4), V3(0, (y0 + 0.05) / 2, 0.75), dark) + + // Back face (z = pzb): three windows over sill and header. + grid(axis: 2, fixed: pzb + 0.015, a0: -4, cols: backCols, b0: y0, rows: winRows, mat: white, + skip: { i, j in j == 2 && (i == 1 || i == 3 || i == 5) }) + // Front-left module face (z = pzf): window over sill; front-right: panels only. + grid(axis: 2, fixed: pzf - 0.015, a0: -4, cols: [0.1, 1.1, 0.1], b0: y0, rows: [0.9, 0.9, 0.33], mat: white, bev: true, + skip: { i, j in i == 1 && j == 1 }) + grid(axis: 2, fixed: pzf - 0.015, a0: 2.7, cols: [0.65, 0.65], b0: y0, rows: [0.9, 0.9, 0.33], mat: white, bev: true) + // End faces (x = +-4). + grid(axis: 0, fixed: -4 + 0.015, a0: pzb, cols: [1.0, 1.0], b0: y0, rows: [0.58, 0.8, 0.75], mat: white, + name: { i, j in i == 1 && j == 1 ? "panel-end-l" : nil }) + grid(axis: 0, fixed: 4 - 0.015, a0: pzb, cols: [1.0, 1.0], b0: y0, rows: [0.58, 0.8, 0.75], mat: white, + name: { i, j in i == 0 && j == 1 ? "panel-end-r" : nil }) + // Roof panels. + grid(axis: 1, fixed: top - 0.015, a0: -4, cols: [2.0, 2.0, 2.0, 2.0], b0: pzb, rows: [1.0, 1.0], mat: white, + name: { i, j in i == 1 && j == 1 ? "panel-roof-a" : (i == 2 && j == 0 ? "panel-roof-b" : nil) }) + // EFEM: front panels, side panels, roof. + grid(axis: 2, fixed: ezf - 0.015, a0: -2.7, cols: [1.35, 1.35, 1.35, 1.35], b0: y0, rows: [0.5, 1.0, 0.48], mat: white, bev: true) + for s: Float in [-1, 1] { + grid(axis: 0, fixed: s * (2.7 - 0.015), a0: pzf, cols: [0.5], b0: y0, rows: [1.0, 0.98], mat: white) + } + grid(axis: 1, fixed: efTop - 0.015, a0: -2.7, cols: [1.35, 1.35, 1.35, 1.35], b0: pzf, rows: [0.5], mat: white) + + // Windows: hatch parts (frame + tinted glass) and lit chamber interiors with spin-coat bowls. + func hatch(_ name: String, xl: Float, w: Float, zFace: Float, out: Float) { + let yb: Float = 1.02, h: Float = 0.9, yc = yb + h / 2, xc = xl + w / 2 + put(cuboid(V3(w - 0.05, h - 0.05, 0.012), material: "glass.window-tint"), Xform(translation: V3(xc, yc, zFace)), name) + put(cuboid(V3(w, 0.045, 0.03), material: anod), Xform(translation: V3(xc, yb + h - 0.0225, zFace)), name) + put(cuboid(V3(w, 0.045, 0.03), material: anod), Xform(translation: V3(xc, yb + 0.0225, zFace)), name) + for sx: Float in [0, 1] { + put(cuboid(V3(0.045, h, 0.03), material: anod), Xform(translation: V3(xl + sx * (w - 0.045) + 0.0225, yc, zFace)), name) + } + put(cuboid(V3(0.022, 0.22, 0.02), material: steel), Xform(translation: V3(xl + w - 0.08, yc, zFace + out * 0.022)), name) + put(cuboid(V3(0.022, 0.03, 0.016), material: anod), Xform(translation: V3(xl + 0.0, yc + 0.3, zFace - out * 0.0)), name) + } + func bowl(_ c: V3, arm: Float) { + let prof: [V2] = [V2(0, 0), V2(0.12, 0), V2(0.19, 0.07), V2(0.2, 0.1), V2(0.185, 0.1), V2(0.17, 0.06), V2(0.07, 0.03), V2(0, 0.03)] + put(Prim.lathe(prof, segments: hi ? 16 : 8, seamTile: 0.4, material: steel), Xform(translation: c)) + cyl(0.07, 0.04, c + V3(0, 0.03, 0), steel, seg: 14) + put(Prim.lathe([V2(0, 0.003), V2(0.15, 0.003), V2(0.15, 0), V2(0, 0)], segments: hi ? 14 : 8, material: "silicon.thinfilm"), + Xform(translation: c + V3(0, 0.073, 0))) + if hi { + put(Prim.tube([c + V3(arm * 0.55, 0.38, 0.1), c + V3(arm * 0.3, 0.38, 0.1), c + V3(arm * 0.05, 0.3, 0.06), c + V3(0, 0.2, 0)], + radii: [0.011, 0.011, 0.011, 0.007], sides: 6, seamTile: 0.2, material: steel), .identity) + cyl(0.012, 0.03, c + V3(0, 0.17, 0), black, seg: 8) + } + } + func chamber(xl: Float, w: Float, zFace: Float, dir: Float, bowls: Int) { + // dir: +1 interior extends toward +z (back face), -1 toward -z (front face). + let d: Float = 0.5, xc = xl + w / 2, zc = zFace + dir * d / 2 + bx(V3(w, 0.03, d), V3(xc, 1.17, zc), "metal.powdercoat:9EA3A8", r: 0.002) + bx(V3(w, 0.9, 0.02), V3(xc, 1.47, zFace + dir * d), "glass.lamp:AEB8BF", r: 0.002) + bx(V3(0.02, 0.9, d), V3(xl + 0.01, 1.47, zc), inner, r: 0.002) + bx(V3(0.02, 0.9, d), V3(xl + w - 0.01, 1.47, zc), inner, r: 0.002) + bx(V3(w - 0.1, 0.012, 0.3), V3(xc, 1.91, zFace + dir * 0.25), "glass.lamp:F4F8FF", r: 0.001) + for k in 0.. 0 ? 0.0 : 0.0), 0.35, s > 0 ? -0.9 : -0.1), dark, r: 0.001) + if hi { for k in 0..<7 { bx(V3(0.012, 0.022, 0.74), V3(s * 4.0 + s * 0.006, 0.2 + Float(k) * 0.045, s > 0 ? -0.9 : -0.1), white, r: 0.001) } } + } + + // Leveling feet on the grate: pad, stud, locknut. + for fx: Float in [-3.7, -1.2, 1.2, 3.7] { + for fz: Float in [-1.38, 0.38] { + cyl(0.05, 0.018, V3(fx, 0.05, fz), steel, seg: 14) + cyl(0.016, 0.055, V3(fx, 0.068, fz), steel, seg: 8) + if hi { cyl(0.026, 0.012, V3(fx, 0.095, fz), anod, seg: 6) } + } + } + for fx: Float in [-2.4, 0, 2.4] { + cyl(0.05, 0.018, V3(fx, 0.05, 0.85), steel, seg: 12) + cyl(0.016, 0.055, V3(fx, 0.068, 0.85), steel, seg: 8) + } + // Corner posts, quarter-turn latches, kick-row grime, roof plenum. + let postMat: MaterialKey = "metal.powdercoat:C9CBCC" + for (px, pz) in [(-4.0 + 0.02, pzb + 0.02), (4.0 - 0.02, pzb + 0.02), (4.0 - 0.02, pzf - 0.02), (-4.0 + 0.02, pzf - 0.02)] as [(Float, Float)] { + bx(V3(0.05, top - y0, 0.05), V3(px, (top + y0) / 2, pz), postMat, r: 0.002) + } + bx(V3(8.02, 0.05, 0.02), V3(0, y0 + 0.025, pzb - 0.005), "metal.powdercoat:B9BBBB", r: 0.001) + if hi { + for lx in [-3.2, -1.2, 1.2, 3.2] as [Float] { for ly in [0.35, 1.6] as [Float] { + bx(V3(0.022, 0.022, 0.01), V3(lx, ly, pzb - 0.003), steel, r: 0.001) + } } + for lx in [-2.7, -0.6, 0.6, 2.7] as [Float] { bx(V3(0.022, 0.022, 0.01), V3(lx, 2.15, pzb - 0.003), steel, r: 0.001) } + for ez in [-1.2, -0.2] as [Float] { for ey in [0.3, 2.0] as [Float] { + bx(V3(0.01, 0.022, 0.022), V3(-4.003, ey, ez), steel, r: 0.001) + bx(V3(0.01, 0.022, 0.022), V3(4.003, ey, ez), steel, r: 0.001) + } } + for lx in [-2.0, -0.7, 0.7, 2.0, 3.0] as [Float] { bx(V3(0.022, 0.022, 0.01), V3(lx, 0.3, ezf + 0.0), steel, r: 0.001) } + // scuffs and smears near the load ports and plinth + for px in ports { bx(V3(0.5, 0.18, 0.004), V3(px, 0.34, ezf + 0.0005), "plastic.matte:C8C8C2", r: 0.0005) } + bx(V3(2.2, 0.12, 0.004), V3(-1.0, 0.18, pzb - 0.0005), "plastic.matte:BCBCB6", r: 0.0005) + } + if hi { for k in 0..<10 { bx(V3(0.012, 0.25 + 0.1 * Float(k % 3), 0.003), V3(-2.5 + Float(k) * 0.55, 1.75 - 0.05 * Float(k % 3), ezf + 0.0005), "plastic.matte:CFCFC9", r: 0.0003) } } + bx(V3(3.0, 0.14, 0.4), V3(-0.5, top + 0.07, -1.2), white, r: 0.004) + bx(V3(3.0, 0.012, 0.3), V3(-0.5, top + 0.146, -1.2), dark, r: 0.001) + // Story detail: hazard strip on the lower right end and an equipment tag. + bx(V3(0.006, 0.06, 1.2), V3(4.008, 0.44, -0.9), "plastic.yellow", r: 0.0005) + bx(V3(0.1, 0.06, 0.004), V3(3.55, 1.0, pzf + 0.002), "plastic.matte:EEEEEA", r: 0.0005) + + // EFEM load ports with docked FOUPs. + let fy: Float = 0.98 + for px in ports { + bx(V3(0.46, fy - y0, 0.34), V3(px, (fy + y0) / 2, ezf + 0.17), white, r: 0.006) + bx(V3(0.5, 0.03, 0.46), V3(px, fy - 0.015, ezf + 0.23), anod, r: 0.004) + bx(V3(0.58, 0.68, 0.025), V3(px, fy + 0.34, ezf + 0.0125), dark, r: 0.004) // door opener plate + bx(V3(0.38, 0.32, 0.01), V3(px, fy + 0.2, ezf + 0.03), "metal.powdercoat:1A1C1F", r: 0.002) // port opening + bx(V3(0.06, 0.016, 0.006), V3(px + 0.22, fy + 0.6, ezf + 0.028), "glass.lamp:40E060", r: 0.001) + bx(V3(0.06, 0.016, 0.006), V3(px - 0.22, fy + 0.6, ezf + 0.028), px < 0 ? "glass.lamp:F0A020" : "glass.lamp:2A3A30", r: 0.001) + if hi { for k in 0..<3 { + let a = Float(k) * 2 * .pi / 3 + cyl(0.01, 0.012, V3(px + 0.08 * sin(a), fy, ezf + 0.2 + 0.07 * cos(a)), steel, seg: 8) + } } + // FOUP: smoke shell, door frame facing the EFEM, top flange, side handles, wafer stack. + let zc = ezf + 0.2 + bx(V3(0.316, 0.29, 0.28), V3(px, fy + 0.017 + 0.145, zc + 0.01), "plastic.polycarbonate-tint", r: 0.004) + bx(V3(0.33, 0.30, 0.012), V3(px, fy + 0.017 + 0.15, zc - 0.135), "plastic.matte:2A2C2F", r: 0.003) + bx(V3(0.3, 0.012, 0.3), V3(px, fy + 0.011, zc), "plastic.matte:2A2C2F", r: 0.002) + cyl(0.013, 0.03, V3(px, fy + 0.307, zc), "plastic.matte:3A3C40", seg: 12) + bx(V3(0.19, 0.008, 0.09), V3(px, fy + 0.341, zc), "plastic.matte:3A3C40", r: 0.002) + for s: Float in [-1, 1] { bx(V3(0.012, 0.02, 0.1), V3(px + s * 0.18, fy + 0.19, zc), "plastic.matte:3A3C40", r: 0.002) } + if hi { + for k in 0..<3 { bx(V3(0.29, 0.07, 0.29), V3(px, fy + 0.08 + Float(k) * 0.085, zc + 0.01), "silicon.thinfilm", r: 0.0) } + bx(V3(0.05, 0.03, 0.001), V3(px + 0.09, fy + 0.27, zc - 0.1425), "plastic.matte:EEEEEA", r: 0.0003) + } + } + + // EFEM roof fan filter unit. + bx(V3(5.0, 0.07, 0.4), V3(0, efTop + 0.035, 0.75), dark, r: 0.004) + if hi { for k in 0..<12 { bx(V3(4.9, 0.01, 0.014), V3(0, efTop + 0.075, 0.57 + Float(k) * 0.03), black, r: 0.0005) } } + + // Roof: exhaust ducts and cable tray. + for dx: Float in [-3.0, -2.1] { cyl(0.12, 0.2, V3(dx, top, -1.05), steel, seg: 18) } + for dx: Float in [-3.0, -2.1] { cyl(0.14, 0.018, V3(dx, top + 0.2, -1.05), steel, seg: 18) } + for s: Float in [-1, 1] { bx(V3(3.0, 0.06, 0.016), V3(0.5, top + 0.04, -0.3 + s * 0.16), anod, r: 0.002) } + let nrung = hi ? 8 : 4 + for k in 0.. Surface { + Prim.extrude(Shape2D.rect(s.x, s.y), depth: s.z, bevel: 0, bevelSegments: 1, material: mat) + } + + public func build(seed: UInt64) -> LODModel { + var rng = SeededRNG(seed: seed) + var m = Model(name: Self.id) + let al = "metal.aluminum-brushed", st = "metal.stainless", L = size.x + // Top plate, two walls, inward bottom lips (gap 0.08 for the vehicle hoist). + m.add(bx(V3(L, 0.03, 0.20), 0.004, al), Xform(translation: V3(0, 0.185, 0))) + for s: Float in [-1, 1] { + m.add(bx(V3(L, 0.15, 0.014), 0.003, al), Xform(translation: V3(0, 0.105, s * 0.093))) + m.add(bx(V3(L, 0.014, 0.06), 0.003, al), Xform(translation: V3(0, 0.037, s * 0.07))) + // Copper power-feed bar on the inner wall face. + m.add(bx(V3(L - 0.02, 0.02, 0.006), 0.002, "metal.copper"), Xform(translation: V3(0, 0.10, s * 0.083))) + // Running surface strip on lip. + m.add(bx(V3(L - 0.01, 0.006, 0.03), 0.002, "plastic.matte:2A2C2F"), Xform(translation: V3(0, 0.047, s * 0.062))) + } + // Splice plate midway and rail end caps with bolts. + for sx: Float in [-0.5, 0.5] where sx != 0 { + m.add(bx(V3(0.02, 0.16, 0.012), 0.002, st), Xform(translation: V3(sx * (L - 0.012), 0.105, 0.093))) + } + m.add(bx(V3(0.24, 0.04, 0.2), 0.004, st), Xform(translation: V3(0, 0.205 - 0.015, 0))) + // Hanger brackets: cross bar with two nuts on threaded rods. + for hx: Float in [-1.0, 1.0] { + m.add(bx(V3(0.05, 0.012, 0.2), 0.002, st), Xform(translation: V3(hx, 0.206, 0))) + for sz: Float in [-1, 1] { + m.add(Prim.cylinder(radius: 0.005, height: 0.012, bevel: 0.0005, segments: 6, material: st), + Xform(translation: V3(hx, 0.2, sz * 0.085))) + } + } + _ = rng.float() + let b = m.bounds; m = m.transformed(Xform(translation: V3(0, -b.min.y, 0))) + return LODModel(m) + } +} diff --git a/Sources/RealLibrary/Props/Fab/OhtVehicle.swift b/Sources/RealLibrary/Props/Fab/OhtVehicle.swift new file mode 100644 index 00000000..580c016c --- /dev/null +++ b/Sources/RealLibrary/Props/Fab/OhtVehicle.swift @@ -0,0 +1,65 @@ +import simd +import Foundation + +/// OHT hoist vehicle: Daifuku/Murata class overhead hoist transport, white covers, clean room. +public struct OhtVehicle: RealAsset { + public static let id = "oht-vehicle" + public static let summary = "Overhead hoist transport vehicle with four-wheel carriage and belt hoist gripper." + public static let tags = ["prop", "semiconductor", "articulated"] + public static let budget = 6000 + public static let author = "realityhd" + + /// Overall size in meters (x width, y height, z length along the rail). + public var size = V3(0.6, 0.6, 0.9) + /// Hoist lowering in meters below the stowed position (belts extend, gripper descends). + public var hoistDrop: Float = 0 + /// Cover material key. + public var coverMaterial = "plastic.gloss:E4E6E8" + public init() {} + + private func bx(_ s: V3, _ r: Float, _ mat: String) -> Surface { + Prim.extrude(Shape2D.rect(s.x, s.y), depth: s.z, bevel: min(r, s.min() * 0.4), bevelSegments: 1, material: mat) + } + + public func build(seed: UInt64) -> LODModel { + var rng = SeededRNG(seed: seed) + var m = Model(name: Self.id) + let al = "metal.aluminum-brushed", dark = "plastic.matte:2A2C2F" + let gy: Float = 0.02 - hoistDrop // gripper plate bottom + // Main cover body. + m.add(bx(V3(0.60, 0.30, 0.84), 0.02, coverMaterial), Xform(translation: V3(0, 0.30, 0))) + // Underside recess frame and slide unit. + m.add(bx(V3(0.54, 0.04, 0.78), 0.006, al), Xform(translation: V3(0, 0.17, 0))) + // Travel carriage on top: chassis and four wheels (axis along X). + m.add(bx(V3(0.30, 0.06, 0.70), 0.008, al), Xform(translation: V3(0, 0.48, 0))) + for sz: Float in [-1, 1] { + for sx: Float in [-1, 1] { + m.add(Prim.cylinder(radius: 0.04, height: 0.03, bevel: 0.004, segments: 14, material: "rubber.tubing"), + Xform(translation: V3(sx * 0.15 + (sx > 0 ? 0.015 : -0.015) + 0, 0.57, sz * 0.27), rotation: simd_quatf(degrees: 90, axis: V3(0, 0, 1))).jittered(&rng)) + } + m.add(bx(V3(0.06, 0.12, 0.05), 0.006, al), Xform(translation: V3(0, 0.54, sz * 0.27))) + m.add(bx(V3(0.36, 0.016, 0.016), 0.003, "metal.steel"), Xform(translation: V3(0, 0.57, sz * 0.27))) + } + // Status lamp and sensor on the front. + m.add(Prim.cylinder(radius: 0.012, height: 0.01, bevel: 0.002, segments: 10, material: "glass.lamp"), + Xform(translation: V3(0.15, 0.36, 0.42), rotation: simd_quatf(degrees: 90, axis: V3(1, 0, 0)))) + m.add(bx(V3(0.06, 0.03, 0.01), 0.003, dark), Xform(translation: V3(-0.12, 0.30, 0.425))) + // Four hoist belts from cover underside to the gripper. + let top: Float = 0.15 + for sx: Float in [-1, 1] { + for sz: Float in [-1, 1] { + let len = max(0.01, top - gy - 0.02) + m.add(bx(V3(0.012, len, 0.0025), 0.0005, "plastic.matte:3A3A3C"), + Xform(translation: V3(sx * 0.12, gy + 0.02 + len / 2, sz * 0.12))) + } + } + // Gripper: plate, four claws, flange pads. + m.add(bx(V3(0.30, 0.02, 0.20), 0.004, al), Xform(translation: V3(0, gy + 0.025, 0))) + for sx: Float in [-1, 1] { + m.add(bx(V3(0.012, 0.03, 0.14), 0.002, "metal.steel"), Xform(translation: V3(sx * 0.075, gy + 0.005, 0))) + } + _ = gy + groundAO(&m) + return LODModel(m) + } +} diff --git a/Sources/RealLibrary/Props/Fab/PlasmaEtchCluster.swift b/Sources/RealLibrary/Props/Fab/PlasmaEtchCluster.swift new file mode 100644 index 00000000..dd614347 --- /dev/null +++ b/Sources/RealLibrary/Props/Fab/PlasmaEtchCluster.swift @@ -0,0 +1,39 @@ +import simd +import Foundation + +/// Plasma etch cluster tool, generic Lam/AMAT-class, 4 x 2.5 x 4 m. Hex vacuum transfer chamber, four bead-blasted +/// aluminum etch chambers with RF match boxes and gas sticks, two load locks, EFEM with three FOUP load ports, +/// cable bundles, status tower. Named parts: lid-1...lid-4 (hinged chamber lids with match boxes). +public struct PlasmaEtchCluster: RealArticulated { + public static let id = "plasma-etch-cluster" + public static let summary = "Etch cluster: hex transfer chamber, 4 bead-blasted aluminum chambers with RF match boxes, 2 load locks, EFEM, gas sticks, status tower." + public static let tags = ["prop", "semiconductor", "articulated"] + public static let budget = 14000 + public static let author = "realityhd" + public static let preview = PreviewHint(azimuth: 30, elevation: 22, distance: 1.0, studio: true) + + /// EFEM and load lock skin powdercoat (sRGB hex). + public var color: UInt32 = 0xEDEDE9 + /// Number of stainless gas sticks per chamber. + public var gasSticks = 6 + public init() {} + + public func rig(seed: UInt64) -> Rig { + _ = seed + var rig = Rig(name: Self.id, lods: 2, switchDistances: [10]) + var spec = ClusterSpec() + spec.skinMat = MaterialKey(stringLiteral: "metal.powdercoat:" + String(format: "%06X", color)) + spec.gasSticks = gasSticks + ClusterTool.declareParts(&rig, spec) + for l in 0..<2 { + let k = ClusterKit(rig: rig, lod: l) + ClusterTool.assemble(k, spec, status: V3(1.3, 0, 1.3)) + rig = k.rig + rig.base[l] = k.m + } + groundAO(&rig, height: 0.15, floor: 0.6) + rig.states = [RigState("closed"), + RigState("lids-open", (1...4).reduce(into: [String: Float]()) { $0["lid-\($1)"] = -100 })] + return rig + } +} diff --git a/Sources/RealLibrary/Props/Fab/ProbeStation.swift b/Sources/RealLibrary/Props/Fab/ProbeStation.swift new file mode 100644 index 00000000..eb272eff --- /dev/null +++ b/Sources/RealLibrary/Props/Fab/ProbeStation.swift @@ -0,0 +1,101 @@ +import simd +import Foundation + +/// Generic wafer probe station, 1.5 x 1.8 x 1.5 m. Isolation table, granite plate, XY stage with vacuum chuck and wafer, platen with +/// four manipulators, probe card holder on a lift (named part `probe-card-lift`) and a microscope head on a rear boom. +public struct ProbeStation: RealArticulated { + public static let id = "probe-station" + public static let summary = "Probe station: isolation table, chuck with wafer, manipulators, probe card lift, microscope head." + public static let tags = ["prop", "semiconductor", "articulated"] + public static let budget = 9000 + public static let author = "realityhd" + public static let preview = PreviewHint(azimuth: 30, elevation: 16, distance: 1.0, studio: true) + + /// Table powdercoat (sRGB hex). + public var color: UInt32 = 0xB9BDBF + public init() {} + + public func rig(seed: UInt64) -> Rig { + _ = seed + var rig = Rig(name: Self.id, lods: 2, switchDistances: [8]) + let grey: MaterialKey = "metal.powdercoat:" + String(format: "%06X", color) + let dark: MaterialKey = "metal.powdercoat:34373C", white: MaterialKey = "metal.powdercoat:E8E9E5" + let steel: MaterialKey = "metal.stainless", anod: MaterialKey = "metal.anodized", black: MaterialKey = "plastic.matte:1E2023" + let yel: MaterialKey = "plastic.yellow" + let py: Float = 1.3 + rig.part("probe-card-lift", pivot: V3(0, py + 0.03, 0), joint: .slide(axis: V3(0, 1, 0), 0...0.12, duration: 1.5)) + for l in 0..<2 { + let k = ClusterKit(rig: rig, lod: l) + let hi = k.hi + // Isolation table: four legs on pneumatic feet, top plate, lower shelf. + for (sx, sz) in [(Float(-1), Float(-1)), (1, -1), (-1, 1), (1, 1)] { + k.bb(V3(0.07, 0.7, 0.07), V3(sx * 0.66, 0.43, sz * 0.66), grey, r: 0.003) + k.cyl(0.07, 0.07, V3(sx * 0.66, 0.0, sz * 0.66), black, seg: 14) + k.cyl(0.05, 0.03, V3(sx * 0.66, 0.07, sz * 0.66), steel, seg: 12) + } + k.bb(V3(1.4, 0.05, 1.38), V3(0, 0.25, 0), grey, r: 0.004) + k.bb(V3(1.5, 0.07, 1.5), V3(0, 0.815, 0), dark, r: 0.005) + k.bb(V3(1.46, 0.015, 1.46), V3(0, 0.855, 0), steel, r: 0.002) + k.scuffs(V3(0.1, 0.82, 0.755), w: 0.5, h: 0.04, n: 6) + k.bb(V3(0.4, 0.1, 0.25), V3(-0.35, 0.24, 0.0), white, r: 0.004) // table electronics tray + k.bb(V3(0.36, 0.01, 0.004), V3(-0.35, 0.26, 0.128), black, r: 0) + // Granite plate and XY stage. + k.bb(V3(1.1, 0.1, 0.9), V3(0, 0.915, 0), "metal.powdercoat:5A5D60", r: 0.005) + k.bb(V3(0.56, 0.1, 0.56), V3(0, 1.015, 0), grey, r: 0.004) + k.bb(V3(0.6, 0.014, 0.6), V3(0, 1.075, 0), anod, r: 0.003) + // Chuck: stepped lathe with vacuum rings, wafer on top. + let ch: [V2] = [V2(0, 0), V2(0.17, 0), V2(0.17, 0.035), V2(0.155, 0.04), V2(0, 0.04)] + k.put(Prim.lathe(ch, segments: hi ? 24 : 12, seamTile: 0.4, material: "metal.anodized"), Xform(translation: V3(0, 1.082, 0))) + if hi { for r: Float in [0.05, 0.1, 0.14] { k.put(Prim.lathe([V2(r, 0), V2(r + 0.004, 0), V2(r + 0.004, 0.001), V2(r, 0.001)], segments: 28, seamTile: 0.4, material: black), Xform(translation: V3(0, 1.1225, 0))) } } + k.put(Prim.lathe([V2(0, 0), V2(0.15, 0), V2(0.15, 0.0008), V2(0, 0.0008)], segments: hi ? 24 : 12, seamTile: 0.4, material: "silicon.thinfilm"), Xform(translation: V3(0, 1.1235, 0))) + // Platen on four posts with a frame opening over the chuck. + for (sx, sz) in [(Float(-1), Float(-1)), (1, -1), (-1, 1), (1, 1)] { k.bb(V3(0.05, py - 1.1, 0.05), V3(sx * 0.46, (py + 1.1) / 2, sz * 0.34), steel, r: 0.003) } + k.bb(V3(1.0, 0.03, 0.22), V3(0, py, 0.28 + 0.1), anod, r: 0.003) + k.bb(V3(1.0, 0.03, 0.22), V3(0, py, -0.38), anod, r: 0.003) + for sx: Float in [-1, 1] { k.bb(V3(0.3, 0.03, 0.54), V3(sx * 0.35, py, -0.0 + 0.0), anod, r: 0.003) } + k.scuffs(V3(0, py, 0.51), w: 0.7, h: 0.015, n: 6) + // Probe card lift: ring, card, rails, lever handle. + let n = "probe-card-lift" + k.put(Prim.lathe([V2(0.14, 0), V2(0.2, 0), V2(0.2, 0.03), V2(0.14, 0.03)], segments: hi ? 24 : 12, seamTile: 0.4, material: steel), Xform(translation: V3(0, py - 0.0, 0)), n) + k.put(Prim.lathe([V2(0, 0.03), V2(0.17, 0.03), V2(0.17, 0.045), V2(0, 0.045)], segments: hi ? 24 : 12, seamTile: 0.4, material: "plastic.matte:2E6B46"), Xform(translation: V3(0, py - 0.0, 0)), n) + if hi { for q in 0..<8 { let a = Float(q) * .pi / 4; k.put(cuboid(V3(0.012, 0.004, 0.012), material: "metal.brass-aged"), Xform(translation: V3(cos(a) * 0.12, py + 0.047, sin(a) * 0.12)), n) } } + for sx: Float in [-1, 1] { k.bb(V3(0.03, 0.04, 0.4), V3(sx * 0.22, py + 0.02, 0), anod, r: 0.003, name: n) } + k.bb(V3(0.44, 0.03, 0.03), V3(0, py + 0.02, 0.2), anod, r: 0.003, name: n) + k.bb(V3(0.02, 0.02, 0.18), V3(0.22, py + 0.04, 0.3), black, r: 0.003, name: n) + k.bb(V3(0.03, 0.03, 0.06), V3(0.22, py + 0.04, 0.42), black, r: 0.004, name: n) + // Manipulators: base, arm, needle holder, needle tube. + for (i, (sx, sz)) in [(Float(-1), Float(-0.8)), (1, -0.8), (-1, 0.8), (1, 0.8)].enumerated() { + let bx = sx * 0.42, bz = sz * 0.2 + sz * 0.0 + k.bb(V3(0.1, 0.07, 0.1), V3(bx + sx * 0.1, py + 0.05, bz), black, r: 0.004) + k.bb(V3(0.05, 0.05, 0.05), V3(bx + sx * 0.04, py + 0.07, bz), steel, r: 0.003) + k.tube([V3(bx, py + 0.075, bz), V3(sx * 0.12, py + 0.06, bz * 0.5), V3(sx * 0.03, py + 0.052, bz * 0.12)], 0.0015, "metal.stainless", sides: 4) + if hi && i == 1 { k.bb(V3(0.02, 0.02, 0.08), V3(bx + sx * 0.12, py + 0.1, bz), anod, r: 0.003) } + } + // Rear column and boom, microscope head. + k.bb(V3(0.14, 0.86, 0.14), V3(0, 1.37, -0.5), grey, r: 0.004) + k.bb(V3(0.16, 0.05, 0.16), V3(0, 0.96, -0.5), dark, r: 0.003) + k.bb(V3(0.12, 0.1, 0.55), V3(0, 1.58, -0.22), grey, r: 0.004) + let mic: [V2] = [V2(0, 0), V2(0.03, 0), V2(0.035, 0.05), V2(0.05, 0.06), V2(0.055, 0.3), V2(0.045, 0.32), V2(0, 0.32)] + k.put(Prim.lathe(mic, segments: hi ? 20 : 10, seamTile: 0.4, material: "metal.powdercoat:1E2023"), Xform(translation: V3(0, 1.4, 0))) + k.put(Prim.lathe([V2(0.04, 0), V2(0.07, 0), V2(0.07, 0.02), V2(0.04, 0.02)], segments: hi ? 20 : 10, seamTile: 0.4, material: "glass.lamp:F4F8FF"), Xform(translation: V3(0, 1.4, 0))) + k.bb(V3(0.1, 0.12, 0.14), V3(0, 1.74, 0.0), black, r: 0.004) + for sx: Float in [-1, 1] { k.put(Prim.cylinder(radius: 0.022, height: 0.12, bevel: 0, segments: hi ? 12 : 6, material: black), Xform(translation: V3(sx * 0.05, 1.75, 0.08), rotation: simd_quatf(degrees: 35, axis: V3(1, 0, 0)))) } + k.bb(V3(0.06, 0.06, 0.1), V3(0.09, 1.58, -0.05), steel, r: 0.003) // focus knob block + k.scuffs(V3(0.0, 1.45, 0.0), w: 0, h: 0, n: 1) + // Monitor on arm at the right of the table. + k.bb(V3(0.06, 0.4, 0.06), V3(0.62, 1.07, 0.45), steel, r: 0.003) + k.bb(V3(0.4, 0.28, 0.035), V3(0.55, 1.35, 0.5), black, r: 0.004) + k.bb(V3(0.36, 0.24, 0.006), V3(0.55, 1.35, 0.521), "plastic.matte:16283C", r: 0.002) + k.prints(V3(0.55, 1.35, 0.5245), w: 0.3, h: 0.2, n: hi ? 6 : 3) + if hi { + k.bb(V3(0.16, 0.004, 0.004), V3(-0.35, 0.82, 0.754), yel, r: 0) + k.bb(V3(1.4, 0.006, 0.004), V3(0, 0.84, 0.756), yel, r: 0) + } + rig = k.rig + rig.base[l] = k.m + } + groundAO(&rig, height: 0.15, floor: 0.6) + rig.states = [RigState("down"), RigState("lifted", ["probe-card-lift": 0.12])] + return rig + } +} diff --git a/Sources/RealLibrary/Props/Fab/PvdCluster.swift b/Sources/RealLibrary/Props/Fab/PvdCluster.swift new file mode 100644 index 00000000..bde952cf --- /dev/null +++ b/Sources/RealLibrary/Props/Fab/PvdCluster.swift @@ -0,0 +1,77 @@ +import simd +import Foundation + +/// PVD sputter cluster tool, generic, 4 x 2.5 x 4 m. Variant of ClusterTool: four aluminum chambers with magnetron +/// cathode lids (magnet housing, rotation motor, HV feedthrough, water lines), tall cryopump cylinders under the +/// gas trays with gate valves, helium lines to the floor. Named parts: lid-1...lid-4. +public struct PvdCluster: RealArticulated { + public static let id = "pvd-cluster" + public static let summary = "PVD cluster: hex transfer chamber, 4 sputter chambers with magnetron cathode lids, tall cryopumps, load locks, EFEM." + public static let tags = ["prop", "semiconductor", "articulated"] + public static let budget = 14000 + public static let author = "realityhd" + public static let preview = PreviewHint(azimuth: 30, elevation: 22, distance: 1.0, studio: true) + + /// EFEM and load lock skin powdercoat (sRGB hex). + public var color: UInt32 = 0xE4E6E8 + /// Gas sticks per chamber. + public var gasSticks = 3 + public init() {} + + public func rig(seed: UInt64) -> Rig { + _ = seed + var rig = Rig(name: Self.id, lods: 2, switchDistances: [10]) + var spec = ClusterSpec() + spec.skinMat = MaterialKey(stringLiteral: "metal.powdercoat:" + String(format: "%06X", color)) + spec.gasSticks = gasSticks + spec.matchBoxes = false + spec.chamberExtra = { k, c, d, t, top, ln, _ in + let steel: MaterialKey = "metal.stainless", brass: MaterialKey = "metal.brass", black: MaterialKey = "plastic.matte:1E2023" + let yaw = 90 - Float(atan2(d.z, d.x)) * 180 / .pi + let lt = top + 0.115 + // Magnetron cathode: housing, cooling cap, rotation motor, HV feedthrough, water lines. + k.cyl(0.19, 0.03, V3(c.x, lt + 0.015, c.z), steel, seg: k.hi ? 12 : 6, name: ln) + k.cyl(0.165, 0.2, V3(c.x, lt + 0.13, c.z), "metal.anodized", seg: k.hi ? 12 : 6, name: ln) + k.cyl(0.175, 0.016, V3(c.x, lt + 0.238, c.z), steel, seg: k.hi ? 12 : 6, name: ln) + k.cyl(0.055, 0.16, V3(c.x, lt + 0.33, c.z), "metal.powdercoat:2A2C2F", seg: 9, name: ln) + let hv = c + d * 0.1 + k.cyl(0.03, 0.1, V3(hv.x, lt + 0.3, hv.z), "plastic.matte:E8E4D8", seg: 9, name: ln) + k.cyl(0.038, 0.02, V3(hv.x, lt + 0.355, hv.z), brass, seg: 9, name: ln) + k.cyl(0.02, 0.07, V3(hv.x, lt + 0.4, hv.z), black, seg: 8, name: ln) + for q: Float in [-1, 1] { + let w = c - d * 0.08 + t * q * 0.11 + k.cyl(0.014, 0.12, V3(w.x, lt + 0.3, w.z), "rubber.tubing", seg: 8, name: ln) + k.bx(V3(0.03, 0.03, 0.03), V3(w.x, lt + 0.37, w.z), brass, r: 0.003, yaw: yaw, name: ln) + } + k.bx(V3(0.24, 0.02, 0.02), V3(c.x + t.x * 0.0, lt + 0.45, c.z + t.z * 0.0) - d * 0.1, black, r: 0.002, yaw: yaw, name: ln) + // Tall cryopump under the gas tray, gate valve to the chamber, helium lines to the floor. + let gc = c + d * (0.46 + 0.2), py: Float = 0.12 + let ph = top + 0.12 - py + k.cyl(0.15, ph, V3(gc.x, py + ph / 2, gc.z), steel, seg: k.hi ? 12 : 6) + k.cyl(0.18, 0.03, V3(gc.x, py + ph - 0.015, gc.z), steel, seg: k.hi ? 12 : 6) + k.cyl(0.18, 0.025, V3(gc.x, py + 0.0125, gc.z), steel, seg: k.hi ? 12 : 6) + let gv = c + d * 0.58 + k.bx(V3(0.14, 0.2, 0.2), V3(gv.x, top - 0.16, gv.z), "metal.anodized", r: 0.004, yaw: yaw) + k.bx(V3(0.06, 0.06, 0.05), V3(gv.x + t.x * 0.1, top - 0.16, gv.z + t.z * 0.1), black, r: 0.003, yaw: yaw) + k.tube([V3(gc.x, top - 0.16, gc.z), V3(gv.x, top - 0.16, gv.z)], 0.04, steel, sides: 10) + if k.hi { + for q: Float in [-1, 1] { + let p = gc + t * q * 0.1 + V3(0, py + 0.16, 0) + k.cyl(0.022, 0.08, V3(p.x, p.y, p.z), black, seg: 7) + k.tube([gc + t * q * 0.1 + V3(0, py + 0.2, 0), gc + t * q * 0.3 + V3(0, py + 0.2, 0), gc + t * q * 0.44 + V3(0, 0.05, 0)], 0.014, black, sides: 5) + } + } + } + ClusterTool.declareParts(&rig, spec) + for l in 0..<2 { + let k = ClusterKit(rig: rig, lod: l) + ClusterTool.assemble(k, spec, status: V3(1.3, 0, 1.3)) + rig = k.rig + rig.base[l] = k.m + } + groundAO(&rig, height: 0.15, floor: 0.6) + rig.states = [RigState("closed"), + RigState("lids-open", (1...4).reduce(into: [String: Float]()) { $0["lid-\($1)"] = -100 })] + return rig + } +} diff --git a/Sources/RealLibrary/Props/Fab/ToolStatusTower.swift b/Sources/RealLibrary/Props/Fab/ToolStatusTower.swift new file mode 100644 index 00000000..4eeb2a08 --- /dev/null +++ b/Sources/RealLibrary/Props/Fab/ToolStatusTower.swift @@ -0,0 +1,53 @@ +import simd +import Foundation + +/// Four-lamp tool status tower, 0.05 x 0.4 x 0.05 m: bracket, pole, stacked translucent lamps red/amber/green/blue (bottom to top), buzzer cap. +public struct ToolStatusTower: RealAsset { + public static let id = "tool-status-tower" + public static let summary = "Tool status tower: mounting bracket, pole, four stacked translucent lamps, buzzer cap, lamp state knob." + public static let tags = ["prop", "semiconductor"] + public static let budget = 600 + public static let author = "realityhd" + + /// Lamp state bitmask: bit 0 red, 1 amber, 2 green, 3 blue. Set bits glow (`glass.lamp`), clear bits are dark translucent. + public var lampState: Int = 0b0100 + public init() {} + + private func cyl(_ r: Float, _ h: Float, _ y: Float, _ mat: MaterialKey, _ seg: Int = 12) -> (Surface, Xform) { + (Prim.lathe([V2(0, 0), V2(r, 0), V2(r, h), V2(0, h)], segments: seg, seamTile: 0.4, material: mat), Xform(translation: V3(0, y + h / 2, 0))) + } + private func box(_ s: V3, _ c: V3, _ mat: MaterialKey) -> (Surface, Xform) { + (cuboid(s, material: mat), Xform(translation: c)) + } + + public func build(seed: UInt64) -> LODModel { + _ = seed + var m = Model(name: Self.id) + func add(_ p: (Surface, Xform)) { m.add(p.0, p.1) } + let steel: MaterialKey = "metal.stainless", anod: MaterialKey = "metal.anodized", black: MaterialKey = "plastic.matte:1E2023" + // L bracket: base plate with slotted holes, upright, gusset. + add(box(V3(0.05, 0.004, 0.05), V3(0, 0.002, 0), steel)) + add(box(V3(0.036, 0.034, 0.004), V3(0, 0.021, -0.02), steel)) + add(box(V3(0.004, 0.026, 0.02), V3(0.014, 0.017, -0.01), steel)) + add(box(V3(0.004, 0.026, 0.02), V3(-0.014, 0.017, -0.01), steel)) + add(cyl(0.012, 0.01, 0.004, anod, 8)) + add(cyl(0.0065, 0.07, 0.014, steel, 6)) + // Lamps: base ring, dark inner core with LED disc, translucent lens, grooves, divider ring. + let cols = ["E03020", "F0A020", "30C050", "2A70E8"] + var y: Float = 0.08 + for (i, c) in cols.enumerated() { + let lit = lampState & (1 << i) != 0 + add(cyl(0.0255, 0.006, y, black, 10)) + add(cyl(0.012, 0.06, y + 0.006, lit ? MaterialKey(stringLiteral: "glass.lamp:" + c) : "plastic.matte:2A2C2F", 6)) + let lens = cyl(0.0245, 0.058, y + 0.006, MaterialKey(stringLiteral: "plastic.translucent:" + c), 10) + add(lens) + y += 0.068 + } + // Buzzer: grille cap with slots, top knob. + add(cyl(0.0255, 0.044, y, "plastic.matte:2A2C2F", 10)) + add(cyl(0.0125, 0.008, y + 0.044, black, 8)) + let b = m.bounds + m = m.transformed(Xform(translation: V3(0, -b.min.y, 0))) + return LODModel(m) + } +} diff --git a/Sources/RealLibrary/Props/Fab/Wafer300mm.swift b/Sources/RealLibrary/Props/Fab/Wafer300mm.swift new file mode 100644 index 00000000..111c2c29 --- /dev/null +++ b/Sources/RealLibrary/Props/Fab/Wafer300mm.swift @@ -0,0 +1,120 @@ +import simd +import Foundation + +/// 300 mm silicon wafer: polished prime wafer, 775 um thick, notch and laser ID, selectable process state. +public struct Wafer300mm: RealAsset { + public static let id = "wafer-300mm" + public static let summary = "300 mm silicon wafer with notch, selectable process state." + public static let tags = ["prop", "semiconductor", "handheld"] + public static let budget = 2000 + public static let author = "realityhd" + + /// Wafer diameter in meters. + public var diameter: Float = 0.3 + /// Wafer thickness in meters (SEMI 775 um). + public var thickness: Float = 0.000775 + /// Process state: bare, oxide, patterned or metal. + public var state = "patterned" + /// Die pitch in meters for the patterned state (26 x 33 mm field). + public var dieSize = V2(0.026, 0.033) + public init() {} + + public func build(seed: UInt64) -> LODModel { + var rng = SeededRNG(seed: seed) + var m = Model(name: Self.id) + let mat: String + switch state { + case "oxide": mat = "silicon.thinfilm-oxide" + case "patterned": mat = "silicon.thinfilm" + case "metal": mat = "silicon.thinfilm-metal" + default: mat = "silicon.thinfilm" + } + let r = diameter / 2 + // Outline with a V notch (3 mm deep, enlarged for visibility) at +Z after the flat rotation. + var pts: [V2] = [] + let n = 96 + let notchA: Float = 0.04 + for i in 0.. V3 { V3(cx + u * c - v * s, y, cz + u * s + v * c) } + let q = [pt(-hx, hz), pt(hx, hz), pt(hx, -hz), pt(-hx, -hz)].map { marks.add($0, nn, V2($0.x, $0.z)) } + marks.quad(q[0], q[1], q[2], q[3]) + } + for row in 0..<2 { + for c in 0..<12 { + if rr.float() < 0.2 { continue } + mq(0.006 + Float(c) * 0.0022, r - 0.0105 - Float(row) * 0.0028, 0.0009, 0.001, 0) + } + } + for _ in 0..<7 { + let a = rr.float() * 2 * .pi, rad = 0.04 + rr.float() * 0.1 + mq(cos(a) * rad, sin(a) * rad, 0.008 + rr.float() * 0.02, 0.00025, a + .pi / 2 + (rr.float() - 0.5) * 0.4) + } + marks.computeTangents() + m.add(marks, Xform(translation: .zero)) + if state == "patterned" { + // Reticle fields of 2 x 2 dies; each die takes one of four interference tints. + var groups = (0..<4).map { Surface(material: "silicon.die-" + ["a", "b", "c", "d"][$0]) } + var scribe = Surface(material: "silicon.thinfilm") + let gap: Float = 0.0006 + let cols = Int((diameter - 0.012) / dieSize.x), rows = Int((diameter - 0.012) / dieSize.y) + for i in 0.. r - 0.0042 { continue } + let rad = simd_length(V2(x, z)) / r + let g = min(3, Int(rad * 3.2) + ((i / 2 + j / 2) % 2 == 0 ? 0 : 0)) + // Wider lane at reticle field borders. + let gl: Float = (i % 2 == 0 ? gap * 2 : gap), gr: Float = (i % 2 == 1 ? gap * 2 : gap) + let gt: Float = (j % 2 == 0 ? gap * 2 : gap), gb: Float = (j % 2 == 1 ? gap * 2 : gap) + let x0 = x - dieSize.x / 2 + gl / 2 + 0.0004, x1 = x + dieSize.x / 2 - gr / 2 - 0.0004 + let z0 = z - dieSize.y / 2 + gt / 2 + 0.0004, z1 = z + dieSize.y / 2 - gb / 2 - 0.0004 + let y = thickness + 0.00002 + let nrm = V3(0, 1, 0) + // Die body split into array core and a darker-tinted periphery strip. + let split = z0 + (z1 - z0) * 0.22 + for (za, zb, gi) in [(z0, split, (g + 1) % 4), (split, z1, g)] { + let i0 = groups[gi].add(V3(x0, y, zb), nrm, V2(x0, zb)), i1 = groups[gi].add(V3(x1, y, zb), nrm, V2(x1, zb)) + let i2 = groups[gi].add(V3(x1, y, za), nrm, V2(x1, za)), i3 = groups[gi].add(V3(x0, y, za), nrm, V2(x0, za)) + groups[gi].quad(i0, i1, i2, i3) + } + _ = scribe + } + } + for k in 0..<4 { groups[k].computeTangents(); m.add(groups[k], Xform(translation: .zero)) } + } + _ = rng.float() + return LODModel(m) + } +} diff --git a/Sources/RealLibrary/Props/Fab/WetBench.swift b/Sources/RealLibrary/Props/Fab/WetBench.swift new file mode 100644 index 00000000..eba92411 --- /dev/null +++ b/Sources/RealLibrary/Props/Fab/WetBench.swift @@ -0,0 +1,134 @@ +import simd +import Foundation + +/// Semiconductor wet bench, generic, 4 x 2.2 x 1.5 m. White PVDF/polypropylene cabinet and deck with four recessed chemical +/// baths and two DI rinse tanks, rear exhaust plenum, clear sash (named part "sash") sliding up in a stainless frame, +/// control panel column at the right end, chemical splash marks on the deck. +public struct WetBench: RealArticulated { + public static let id = "wet-bench" + public static let summary = "Wet bench: white PVDF deck with recessed baths, DI rinse tanks, clear sliding sash, exhaust plenum, control panel." + public static let tags = ["prop", "semiconductor", "articulated"] + public static let budget = 10000 + public static let author = "realityhd" + public static let preview = PreviewHint(azimuth: 28, elevation: 20, distance: 1.0, studio: true) + + /// Deck and cabinet plastic (sRGB hex tint of plastic.pvdf is not applied; panels use this tint on plastic.matte accents). + public var accent: UInt32 = 0x2F5E8C + public init() {} + + public func rig(seed: UInt64) -> Rig { + _ = seed + var rig = Rig(name: Self.id, lods: 2, switchDistances: [10]) + let pv: MaterialKey = "plastic.pvdf", steel: MaterialKey = "metal.stainless", anod: MaterialKey = "metal.anodized" + let black: MaterialKey = "plastic.matte:1E2023", glass: MaterialKey = "glass.window-tint" + let ptfe: MaterialKey = "plastic.matte:8EA2AC" + rig.part("sash", pivot: V3(-0.2, 1.5, 0.66), joint: .slide(axis: V3(0, 1, 0), 0...0.75, duration: 3)) + let dy: Float = 0.9 // deck top + for l in 0..<2 { + let k = ClusterKit(rig: rig, lod: l) + let hi = k.hi + // Cabinet base x -2..2, z -0.75..0.75, toe kick, doors with gaps. + k.bx(V3(3.9, 0.1, 1.4), V3(0, 0.06, 0), "metal.powdercoat:34373C", r: 0.003) + k.bx(V3(4.0, dy - 0.04 - 0.1, 1.5), V3(0, (dy - 0.04 + 0.1) / 2, 0), pv, r: 0.006) + for q in 0..<8 { + let x = -1.75 + Float(q) * 0.5 + k.bx(V3(0.012, 0.7, 0.01), V3(x + 0.25, 0.5, 0.755), black, r: 0) + k.bx(V3(0.012, 0.12, 0.012), V3(x + (q % 2 == 0 ? 0.42 : 0.08), 0.62, 0.77), steel, r: 0.002) + } + // Deck slab built around recesses. Deck x -2..2, z -0.75..0.75, y dy-0.04..dy. + let bx0: [Float] = [-1.9, -1.25, -0.6, 0.05], bw: Float = 0.55 + let dx0: [Float] = [0.75, 1.25], dw: Float = 0.42 + let rz0: Float = -0.3, rz1: Float = 0.42 + var cuts: [(Float, Float)] = bx0.map { ($0, $0 + bw) } + dx0.map { ($0, $0 + dw) } + cuts.sort { $0.0 < $1.0 } + var xcur: Float = -2.0 + let dyc = dy - 0.02 + for (a, b) in cuts { + if a > xcur { k.bx(V3(a - xcur, 0.04, 1.5), V3((a + xcur) / 2, dyc, 0), pv, r: 0.004) } + // Front and rear strips beside the recess. + k.bx(V3(b - a, 0.04, rz0 + 0.75), V3((a + b) / 2, dyc, (-0.75 + rz0) / 2), pv, r: 0.004) + k.bx(V3(b - a, 0.04, 0.75 - rz1), V3((a + b) / 2, dyc, (0.75 + rz1) / 2), pv, r: 0.004) + xcur = b + } + k.bx(V3(2.0 - xcur, 0.04, 1.5), V3((2.0 + xcur) / 2, dyc, 0), pv, r: 0.004) + // Bath liners: four walls, floor, liquid surface, overflow weir. + let liquids: [MaterialKey] = ["plastic.matte:3A6A8A", "plastic.matte:8A6A3A", "plastic.matte:5A8A6A", "plastic.matte:7A5A8A", "glass.clear", "glass.clear"] + for (i, (a, b)) in cuts.enumerated() { + let w = b - a, cx = (a + b) / 2, d = rz1 - rz0, cz = (rz0 + rz1) / 2 + k.bx(V3(w, 0.02, d), V3(cx, dy - 0.38, cz), ptfe, r: 0) + k.bx(V3(0.015, 0.36, d), V3(a + 0.0075, dy - 0.2, cz), ptfe, r: 0) + k.bx(V3(0.015, 0.36, d), V3(b - 0.0075, dy - 0.2, cz), ptfe, r: 0) + k.bx(V3(w, 0.36, 0.015), V3(cx, dy - 0.2, rz0 + 0.0075), ptfe, r: 0) + k.bx(V3(w, 0.36, 0.015), V3(cx, dy - 0.2, rz1 - 0.0075), ptfe, r: 0) + k.bx(V3(w - 0.03, 0.004, d - 0.03), V3(cx, dy - 0.07 - Float(i % 3) * 0.01, cz), liquids[i], r: 0) + k.bx(V3(w + 0.02, 0.008, 0.02), V3(cx, dy + 0.004, rz0 - 0.012), anod, r: 0.001) + k.bx(V3(w + 0.02, 0.008, 0.02), V3(cx, dy + 0.004, rz1 + 0.012), anod, r: 0.001) + k.bx(V3(w - 0.08, 0.003, 0.05), V3(cx, dy + 0.001, rz0 + 0.028), "metal.stainless", r: 0) // bath grating + if hi { + k.bx(V3(w + 0.03, 0.012, 0.012), V3(cx, dy + 0.006, rz1 + 0.008), anod, r: 0.002) + k.cyl(0.018, 0.006, V3(cx, dy - 0.385, cz), steel, seg: 10) // drain + if i < 4 { k.cyl(0.02, 0.1, V3(a + 0.05, dy - 0.3, rz0 + 0.04), "quartz", seg: 8) } // heater / immersion probe + } + } + // Wafer cassette resting in bath 2: PFA carrier. + k.bx(V3(0.24, 0.18, 0.2), V3(-1.25 + 0.275, dy - 0.13, 0.06), "plastic.matte:E8E4D8", r: 0.004) + if hi { for q in 0..<5 { k.bx(V3(0.24, 0.004, 0.19), V3(-0.975, dy - 0.2 + Float(q) * 0.03, 0.06), "silicon.thinfilm", r: 0) } } + // Rear wall x -2..2 and side walls, z -0.75..0.75, y dy..1.95. + let wt: Float = 0.04, top: Float = 1.95 + k.bx(V3(4.0, top - dy, wt), V3(0, (top + dy) / 2, -0.75 + wt / 2), pv, r: 0.004) + k.bx(V3(wt, top - dy, 1.5), V3(-2 + wt / 2, (top + dy) / 2, 0), pv, r: 0.004) + k.bx(V3(wt, top - dy, 1.5), V3(2 - wt / 2, (top + dy) / 2, 0), pv, r: 0.004) + k.bx(V3(3.92, 0.03, 1.46), V3(0, top - 0.015, 0), pv, r: 0.003) + // Header above the sash and sash track uprights. + k.bx(V3(3.9, 0.35, 0.05), V3(0, top - 0.175, 0.7), pv, r: 0.004) + k.bx(V3(0.06, 0.65, 0.05), V3(-1.55, 1.52, 0.7), steel, r: 0.002) + k.bx(V3(0.06, 0.65, 0.05), V3(1.15, 1.52, 0.7), steel, r: 0.002) + // Sash: clear panel in stainless frame, pull handle (named part). + let s = "sash" + k.bx(V3(2.7, 0.6, 0.01), V3(-0.2, 1.5, 0.66), glass, r: 0, name: s) + for (c, sz) in [(V3(-0.2, 1.8, 0.66), V3(2.76, 0.04, 0.03)), (V3(-0.2, 1.2, 0.66), V3(2.76, 0.04, 0.03)), + (V3(-1.55, 1.5, 0.66), V3(0.04, 0.6, 0.03)), (V3(1.15, 1.5, 0.66), V3(0.04, 0.6, 0.03))] { k.bx(sz, c, steel, r: 0.002, name: s) } + k.put(barHandle(length: 0.5, standoff: 0.04, radius: 0.012, material: steel), Xform(translation: V3(-0.2, 1.2, 0.68)), s) + // Panel seams and vents on side and rear walls. + for q in 0..<5 { k.bx(V3(0.012, top - dy - 0.4, 0.004), V3(-1.6 + Float(q) * 0.8, 1.35, -0.752), black, r: 0) } + for sx: Float in [-1, 1] { + k.bx(V3(0.004, 0.012, 1.46), V3(sx * 2.002, 1.4, 0), black, r: 0) + for q in 0..<6 { k.bx(V3(0.004, 0.02, 0.5), V3(sx * 2.002, 1.55 + Float(q) * 0.04, -0.3), black, r: 0) } + } + k.bx(V3(0.004, 0.3, 0.3), V3(2.003, 1.15, 0.4), "plastic.matte:EEEEEA", r: 0) + // Exhaust plenum on top, duct collar. + k.bx(V3(3.6, 0.2, 0.7), V3(0, top + 0.1, -0.35), pv, r: 0.01) + k.bx(V3(3.6, 0.02, 0.5), V3(0, top + 0.21, -0.35), steel, r: 0.002) + k.cyl(0.2, 0.04, V3(-0.4, top + 0.2, -0.35), steel, seg: hi ? 20 : 8) + k.cyl(0.18, 0.01, V3(1.2, top + 0.21, -0.35), black, seg: hi ? 20 : 8) + if hi { for q in 0..<9 { k.bx(V3(0.04, 0.012, 0.3), V3(-1.55 + Float(q) * 0.38, top - 0.13, -0.5), black, r: 0) } } + // Rear wall: DI gooseneck faucets over DI tanks, N2 guns on hoses, spray bars. + for x in dx0 { + let cx = x + dw / 2 + k.tube(catmull([V3(cx, dy + 0.1, -0.7), V3(cx, dy + 0.45, -0.68), V3(cx, dy + 0.5, -0.5), V3(cx, dy + 0.4, rz0 + 0.2)], per: hi ? 4 : 2), 0.012, steel, sides: 8) + k.cyl(0.016, 0.04, V3(cx, dy + 0.33, rz0 + 0.2), anod, seg: 8) + k.bx(V3(0.1, 0.1, 0.05), V3(cx, dy + 0.1, -0.7), steel, r: 0.003) + } + k.tube(catmull([V3(0.8, 1.2, -0.72), V3(0.9, 1.0, -0.6), V3(1.5, 1.05, -0.45), V3(1.55, 0.95, -0.2)], per: hi ? 4 : 2), 0.007, "rubber.tubing", sides: 6) + // Control panel column at the right end x 1.65..1.95, sloped screen, e-stop, buttons, status lamps. + let px: Float = 1.8 + k.bx(V3(0.3, 0.46, 0.3), V3(px, dy + 0.23, 0.4), pv, r: 0.006) + k.bx(V3(0.26, 0.2, 0.012), V3(px, dy + 0.4, 0.552), black, r: 0.004) + k.bx(V3(0.22, 0.15, 0.004), V3(px, dy + 0.4, 0.559), "glass.lamp:2A6AC0", r: 0.001) + for q in 0..<4 { k.bx(V3(0.035, 0.035, 0.012), V3(px - 0.09 + Float(q) * 0.06, dy + 0.27, 0.556), q == 3 ? "glass.lamp:E03020" : "plastic.matte:3A3C40", r: 0.004) } + k.cyl(0.026, 0.02, V3(px, dy + 0.46, 0.2), "glass.lamp:E03020", seg: 12) + k.cyl(0.015, 0.1, V3(px, dy + 0.46, 0.12), black, seg: 8) + // Splash marks and stains (deck level, mm thick) and hazard labels. + let stains: [(Float, Float, Float, Float, String)] = [(-1.62, 0.58, 0.4, 0.16, "C8B070"), (-0.95, 0.56, 0.3, 0.12, "9A7040"), (-0.3, 0.6, 0.36, 0.1, "B8B4A8"), + (0.38, 0.51, 0.16, 0.1, "7A9A8A"), (-1.1, -0.45, 0.28, 0.07, "C0A860"), (0.9, 0.6, 0.12, 0.08, "A0B8C8")] + for (x, z, w, d, c) in stains { k.bx(V3(w, 0.0008, d), V3(x, dy + 0.0006, z), MaterialKey(stringLiteral: "plastic.matte:" + c), r: 0) } + for (i, c) in ["E8C21A", "E03020", "E8C21A", "2F8FC0"].enumerated() { k.bx(V3(0.07, 0.07, 0.003), V3(-1.62 + Float(i) * 0.65, dy + 0.3, -0.725), MaterialKey(stringLiteral: "plastic.matte:" + c), r: 0.001) } + k.bx(V3(3.0, 0.012, 0.006), V3(0, 0.3, 0.76), "plastic.yellow", r: 0.0005) + rig = k.rig + rig.base[l] = k.m + } + groundAO(&rig, height: 0.15, floor: 0.6) + rig.states = [RigState("closed"), RigState("open", ["sash": 0.75])] + return rig + } +} diff --git a/Sources/RealLibrary/Props/Props.swift b/Sources/RealLibrary/Props/Props.swift index 365f8488..f15c7e8d 100644 --- a/Sources/RealLibrary/Props/Props.swift +++ b/Sources/RealLibrary/Props/Props.swift @@ -927,6 +927,50 @@ public enum Props { FiberCleaner.self, CableLabel.self, VelcroTie.self, + Foup.self, + OhtVehicle.self, + Wafer300mm.self, + EuvScannerHighna.self, + LithoTrack.self, + PlasmaEtchCluster.self, + CvdAldCluster.self, + PvdCluster.self, + CmpTool.self, + IonImplanter.self, + DiffusionFurnace.self, + WetBench.self, + OhtRail.self, + FfuCeilingTile.self, + CleanroomFloorGrate.self, + FabYellowLightPanel.self, + EbeamMetrology.self, + ProbeStation.self, + DicingSaw.self, + HybridBonder.self, + ToolStatusTower.self, + FoupStocker.self, + EfemRobot.self, + EuvLightSource.self, + DuvImmersionScanner.self, + PackageSubstrate.self, + ChipPackageFinal.self, + HBMStack.self, + DieBlankReticle.self, + InterposerGlass.self, + HoloTable.self, + DieBlockTile.self, + DesignWorkbench2035.self, + TransistorGaaCutaway.self, + InterconnectStackCutaway.self, + WaferDisplayStand.self, + BondWireMacro.self, + OrbitalDCModule.self, + SpaceRadiatorPanel.self, + SolarArrayWing.self, + LunarDCHabitat.self, + MarsDCDome.self, + GroundStationDish.self, + EarthSitePin.self, // realityhd:prop ] } diff --git a/Sources/RealMaterials/Library/TerawattChip.swift b/Sources/RealMaterials/Library/TerawattChip.swift new file mode 100644 index 00000000..1660cb7c --- /dev/null +++ b/Sources/RealMaterials/Library/TerawattChip.swift @@ -0,0 +1,113 @@ +import RealCore + +public extension MaterialLibrary { + /// Semiconductor packaging: mirror-polished silicon die, ABF build-up laminate, plated gold and nickel, + /// SAC solder, underfill epoxy. + static let terawattChip: [MaterialSpec] = [ + // Polished device-side silicon: dark blue-grey mirror with faint metal-layer interference. + MaterialSpec(key: "silicon.die", program: .brushedMetal).with { + $0.colorA = linear(0x2B3140); $0.colorB = linear(0x4A4F66) + $0.knobs = V4(0.15, 0.1, 0.1, 0.05); $0.seed = 9801; $0.tileSize = 0.01; $0.normalStrength = 0.15 + $0.hasMetallicMap = true; $0.metallic = 0.85; $0.roughness = 0.14 + }, + // Diced silicon edge and backside grind: lighter, matte grey. + MaterialSpec(key: "silicon.die-edge", program: .brushedMetal).with { + $0.colorA = linear(0x7C8088); $0.colorB = linear(0x585C64) + $0.knobs = V4(0.5, 0.5, 0.3, 0.2); $0.seed = 9802; $0.tileSize = 0.01; $0.normalStrength = 0.5 + $0.hasMetallicMap = true; $0.metallic = 0.6; $0.roughness = 0.45 + }, + // ABF build-up laminate under green-black solder mask: satin, faint weave print-through. + MaterialSpec(key: "laminate.abf", program: .plastic).with { + $0.colorA = linear(0x1F3A2A); $0.knobs = V4(0.2, 0.15, 0.4, 0); $0.seed = 9803 + $0.tileSize = 0.02; $0.resolution = 512; $0.normalStrength = 0.4 + }, + // ENIG gold pad plating. + MaterialSpec(key: "metal.gold", program: .polishedMetal).with { + $0.colorA = linear(0xE2B84E); $0.colorB = linear(0x8A6A22); $0.colorC = linear(0x8A6A22) + $0.knobs = V4(0.08, 0.0, 0, 0.18); $0.seed = 9804; $0.tileSize = 0.02; $0.normalStrength = 0.25 + $0.hasMetallicMap = true; $0.metallic = 1; $0.roughness = 0.2 + }, + // Nickel-plated copper lid (integrated heat spreader): satin, hairline machining marks. + MaterialSpec(key: "metal.nickel", program: .brushedMetal).with { + $0.colorA = linear(0xC4C3BE); $0.colorB = linear(0x8E8D88) + $0.knobs = V4(0.6, 0.3, 0.4, 0.3); $0.seed = 9805; $0.tileSize = 0.03; $0.normalStrength = 0.4 + $0.hasMetallicMap = true; $0.metallic = 1; $0.roughness = 0.3 + }, + // SAC305 solder: dull silver-grey. + MaterialSpec(key: "metal.solder", program: .brushedMetal).with { + $0.colorA = linear(0xA7A9AC); $0.colorB = linear(0x6B6D70) + $0.knobs = V4(0.2, 0.5, 0.3, 0.1); $0.seed = 9806; $0.tileSize = 0.005; $0.normalStrength = 0.3 + $0.hasMetallicMap = true; $0.metallic = 1; $0.roughness = 0.35 + }, + // Capillary underfill epoxy: dark amber-black fillet. + MaterialSpec(key: "plastic.underfill", program: .plastic).with { + $0.colorA = linear(0x2A2218); $0.knobs = V4(0.1, 0.1, 0.3, 0); $0.seed = 9807 + $0.tileSize = 0.01; $0.resolution = 512; $0.normalStrength = 0.2 + }, + // Glass-core interposer: pale blue-green, slightly translucent-looking satin glass with copper RDL beneath. + MaterialSpec(key: "glass.substrate", program: .brushedMetal).with { + $0.colorA = linear(0xB4C6C1); $0.colorB = linear(0x8FA39F) + $0.knobs = V4(0.15, 0.2, 0.1, 0.05); $0.seed = 9808; $0.tileSize = 0.01; $0.normalStrength = 0.1 + $0.hasMetallicMap = true; $0.metallic = 0.25; $0.roughness = 0.12 + }, + // Clear glass-core interposer: pale blue-green, mostly transparent, TGV columns visible through it. + MaterialSpec(key: "glass.substrate-clear", program: nil).with { + $0.baseColor = V3(0.62, 0.74, 0.72); $0.roughness = 0.04; $0.specular = 0.6; $0.mode = .transparent; $0.opacity = 0.32; $0.twoSided = true + }, + // Dark display glass: near-black, glossy, opaque, clearcoated. + MaterialSpec(key: "glass.display", program: nil).with { + $0.baseColor = V3(0.012, 0.016, 0.022); $0.roughness = 0.03; $0.specular = 0.8; $0.clearcoat = 1 + }, + // Holo-table grid lines: dim cyan emissive. + MaterialSpec(key: "emissive.holo-grid", program: nil).with { + $0.baseColor = V3(0.05, 0.3, 0.38); $0.mode = .emissive; $0.emissive = V3(0.15, 0.8, 1); $0.emissiveIntensity = 1.2 + }, + // Die block tile emissive tints, one per block type, kept dim. + MaterialSpec(key: "emissive.die-tensor", program: nil).with { + $0.baseColor = V3(0.2, 0.08, 0.35); $0.mode = .emissive; $0.emissive = V3(0.6, 0.25, 1); $0.emissiveIntensity = 0.5 + }, + MaterialSpec(key: "emissive.die-sram", program: nil).with { + $0.baseColor = V3(0.04, 0.2, 0.3); $0.mode = .emissive; $0.emissive = V3(0.1, 0.7, 1); $0.emissiveIntensity = 0.45 + }, + MaterialSpec(key: "emissive.die-noc", program: nil).with { + $0.baseColor = V3(0.3, 0.2, 0.04); $0.mode = .emissive; $0.emissive = V3(1, 0.7, 0.15); $0.emissiveIntensity = 0.5 + }, + MaterialSpec(key: "emissive.die-hbmphy", program: nil).with { + $0.baseColor = V3(0.05, 0.3, 0.12); $0.mode = .emissive; $0.emissive = V3(0.2, 1, 0.4); $0.emissiveIntensity = 0.45 + }, + MaterialSpec(key: "emissive.die-io", program: nil).with { + $0.baseColor = V3(0.3, 0.08, 0.1); $0.mode = .emissive; $0.emissive = V3(1, 0.3, 0.35); $0.emissiveIntensity = 0.45 + }, + // Design workbench holo plane: pale cyan translucent-looking emissive sheet. + MaterialSpec(key: "emissive.holo-plane", program: nil).with { + $0.baseColor = V3(0.1, 0.35, 0.45); $0.mode = .emissive; $0.emissive = V3(0.3, 0.85, 1); $0.emissiveIntensity = 0.8 + }, + // Hafnium oxide high-k gate dielectric: pale yellow-green translucent-looking satin film. + MaterialSpec(key: "oxide.hafnium", program: .brushedMetal).with { + $0.colorA = linear(0xC9D08A); $0.colorB = linear(0x9AA463) + $0.knobs = V4(0.1, 0.1, 0.1, 0.05); $0.seed = 9820; $0.tileSize = 0.01; $0.normalStrength = 0.1 + $0.hasMetallicMap = true; $0.metallic = 0.1; $0.roughness = 0.25 + }, + // Tungsten gate fill and contact plugs: dark blue-grey satin metal. + MaterialSpec(key: "metal.tungsten", program: .brushedMetal).with { + $0.colorA = linear(0x6E7480); $0.colorB = linear(0x484C56) + $0.knobs = V4(0.3, 0.3, 0.3, 0.2); $0.seed = 9821; $0.tileSize = 0.01; $0.normalStrength = 0.25 + $0.hasMetallicMap = true; $0.metallic = 1; $0.roughness = 0.32 + }, + // Low-k interlayer dielectric (carbon-doped oxide): translucent-looking pale amber-grey. + MaterialSpec(key: "oxide.lowk", program: .plastic).with { + $0.colorA = linear(0x8F8A78); $0.knobs = V4(0.1, 0.1, 0.3, 0); $0.seed = 9822 + $0.tileSize = 0.02; $0.resolution = 512; $0.normalStrength = 0.1 + }, + // Patterned wafer face: thin-film interference, violet-blue mirror; per-die hue via tint suffix. + MaterialSpec(key: "silicon.thinfilm", program: .brushedMetal).with { + $0.colorA = linear(0x5A4A9C); $0.colorB = linear(0x2F6A80) + $0.knobs = V4(0.1, 0.1, 0.1, 0.05); $0.seed = 9830; $0.tileSize = 0.02; $0.normalStrength = 0.08 + $0.hasMetallicMap = true; $0.metallic = 0.9; $0.roughness = 0.1 + }, + // Cast acrylic: clear with a faint cool edge tint. + MaterialSpec(key: "plastic.acrylic", program: nil).with { + $0.baseColor = V3(0.55, 0.72, 0.8); $0.roughness = 0.03; $0.specular = 0.7; $0.mode = .transparent; $0.opacity = 0.16; $0.twoSided = true + }, + ] +} diff --git a/Sources/RealMaterials/Library/TerawattFab.swift b/Sources/RealMaterials/Library/TerawattFab.swift new file mode 100644 index 00000000..0b44a69b --- /dev/null +++ b/Sources/RealMaterials/Library/TerawattFab.swift @@ -0,0 +1,104 @@ +import Foundation +import RealCore + +public extension MaterialLibrary { + /// Terawatt Fab family: wafer thin-film states and smoke polycarbonate for FOUP shells. + static let terawattFab: [MaterialSpec] = [ + // Smoke-tinted polycarbonate (FOUP shell): translucent, glossy, faint grey-brown cast. + MaterialSpec(key: "plastic.polycarbonate-tint", program: nil).with { + $0.baseColor = V3(0.10, 0.10, 0.11); $0.roughness = 0.12; $0.specular = 0.5 + $0.mode = .transparent; $0.opacity = 0.62; $0.twoSided = true + }, + // Equipment service window: light blue-grey tint, mostly transparent so chamber contents read. + MaterialSpec(key: "glass.window-tint", program: nil).with { + $0.baseColor = V3(0.30, 0.38, 0.42); $0.roughness = 0.04; $0.specular = 0.6 + $0.mode = .transparent; $0.opacity = 0.22; $0.twoSided = true + }, + // Bare polished silicon: dark mirror, blue-grey cast. + MaterialSpec(key: "silicon.thinfilm", program: .polishedMetal).with { + $0.colorA = linear(0x7C8290); $0.colorB = linear(0x4A505C); $0.colorC = linear(0x8E96A8) + $0.knobs = V4(0.2, 0.01, 8, 0.06); $0.seed = 3101; $0.tileSize = 0.3; $0.normalStrength = 0.05 + $0.hasMetallicMap = true; $0.metallic = 1; $0.roughness = 0.06 + }, + // Oxide film: violet-gold interference tint. + MaterialSpec(key: "silicon.thinfilm-oxide", program: .polishedMetal).with { + $0.colorA = linear(0x6C5A9A); $0.colorB = linear(0x3C6C8C); $0.colorC = linear(0xB08A5A) + $0.knobs = V4(0.3, 0.01, 6, 0.07); $0.seed = 3102; $0.tileSize = 0.3; $0.normalStrength = 0.05 + $0.hasMetallicMap = true; $0.metallic = 1; $0.roughness = 0.07 + }, + // Patterned: green-teal diffraction from the die grid. + MaterialSpec(key: "silicon.thinfilm-patterned", program: .polishedMetal).with { + $0.colorA = linear(0x4E9A7A); $0.colorB = linear(0x7A5AA0); $0.colorC = linear(0xC0A040) + $0.knobs = V4(0.5, 0.02, 14, 0.09); $0.seed = 3103; $0.tileSize = 0.1; $0.normalStrength = 0.1 + $0.hasMetallicMap = true; $0.metallic = 1; $0.roughness = 0.09 + }, + // Metal-coated: bright copper-silver mirror. + MaterialSpec(key: "silicon.thinfilm-metal", program: .polishedMetal).with { + $0.colorA = linear(0xC8A890); $0.colorB = linear(0x9A8070); $0.colorC = linear(0xD8D0C8) + $0.knobs = V4(0.2, 0.01, 8, 0.1); $0.seed = 3104; $0.tileSize = 0.3; $0.normalStrength = 0.05 + $0.hasMetallicMap = true; $0.metallic = 1; $0.roughness = 0.1 + }, + // Per-die interference variants, scribe lane, exclusion rim and handling marks. + MaterialSpec(key: "silicon.die-a", program: .polishedMetal).with { + $0.colorA = linear(0x4FB0A8); $0.colorB = linear(0x9A60C8); $0.colorC = linear(0xE0B848) + $0.knobs = V4(0.35, 0.02, 18, 0.08); $0.seed = 3111; $0.tileSize = 0.026; $0.normalStrength = 0.04 + $0.hasMetallicMap = true; $0.metallic = 0.55; $0.roughness = 0.22 + }, + MaterialSpec(key: "silicon.die-b", program: .polishedMetal).with { + $0.colorA = linear(0x5C90E0); $0.colorB = linear(0xC860A8); $0.colorC = linear(0x58C8B8) + $0.knobs = V4(0.35, 0.02, 20, 0.08); $0.seed = 3112; $0.tileSize = 0.026; $0.normalStrength = 0.04 + $0.hasMetallicMap = true; $0.metallic = 0.55; $0.roughness = 0.22 + }, + MaterialSpec(key: "silicon.die-c", program: .polishedMetal).with { + $0.colorA = linear(0xC890C0); $0.colorB = linear(0x58A0D8); $0.colorC = linear(0xE0A860) + $0.knobs = V4(0.35, 0.02, 16, 0.08); $0.seed = 3113; $0.tileSize = 0.026; $0.normalStrength = 0.04 + $0.hasMetallicMap = true; $0.metallic = 0.55; $0.roughness = 0.22 + }, + MaterialSpec(key: "silicon.die-d", program: .polishedMetal).with { + $0.colorA = linear(0x58C8A0); $0.colorB = linear(0xA890D8); $0.colorC = linear(0xE0A070) + $0.knobs = V4(0.35, 0.02, 22, 0.08); $0.seed = 3114; $0.tileSize = 0.026; $0.normalStrength = 0.04 + $0.hasMetallicMap = true; $0.metallic = 0.55; $0.roughness = 0.22 + }, + // Bare silicon: bright grey mirror for the edge exclusion ring and bevel. + MaterialSpec(key: "silicon.bare", program: .polishedMetal).with { + $0.colorA = linear(0xE8ECF2); $0.colorB = linear(0xC0C6D0); $0.colorC = linear(0xE0E4EA) + $0.knobs = V4(0.1, 0.01, 6, 0.04); $0.seed = 3121; $0.tileSize = 0.3; $0.normalStrength = 0.03 + $0.hasMetallicMap = true; $0.metallic = 0.5; $0.roughness = 0.2 + }, + // Faint handling marks and laser dots: slightly rougher, lighter. + MaterialSpec(key: "silicon.mark", program: .polishedMetal).with { + $0.colorA = linear(0xD8DCE4); $0.colorB = linear(0xB8BEC8); $0.colorC = linear(0xF0F2F6) + $0.knobs = V4(0.1, 0.01, 6, 0.04); $0.seed = 3122; $0.tileSize = 0.05; $0.normalStrength = 0.1 + $0.hasMetallicMap = true; $0.metallic = 1; $0.roughness = 0.35 + }, + // CMP polishing pad: grey-beige closed-cell polyurethane, matte, faint slurry-stained noise. + MaterialSpec(key: "plastic.pad", program: .plastic).with { + $0.colorA = linear(0x8C8A84); $0.knobs = V4(0.6, 0.5, 0.5, 0); $0.tileSize = 0.12; $0.resolution = 512; $0.seed = 3140 + $0.roughness = 0.85 + }, + // Fused quartz (furnace boats, tubes): translucent milky-clear, glossy. + MaterialSpec(key: "quartz", program: nil).with { + $0.baseColor = V3(0.78, 0.82, 0.84); $0.roughness = 0.1; $0.specular = 0.5 + $0.mode = .transparent; $0.opacity = 0.7; $0.twoSided = true + }, + // PVDF / polypropylene wet-bench deck: off-white, semi-matte, fine moulded grain. + MaterialSpec(key: "plastic.pvdf", program: .plastic).with { + $0.colorA = linear(0xD8D7CE); $0.knobs = V4(0.15, 0.1, 0.35, 0); $0.seed = 3150; $0.tileSize = 0.3; $0.resolution = 512 + $0.normalStrength = 0.25; $0.roughness = 0.42 + }, + // HEPA/ULPA filter pack: off-white glass-fibre media, matte, fine pleat grain. + MaterialSpec(key: "filter.hepa", program: .plastic).with { + $0.colorA = linear(0xE6E6E0); $0.knobs = V4(0.5, 0.4, 0.5, 0); $0.seed = 3160; $0.tileSize = 0.1; $0.resolution = 512 + $0.normalStrength = 0.6; $0.roughness = 0.92 + }, + // Tower lamp lens: milky translucent plastic, tint via hex suffix. + MaterialSpec(key: "plastic.translucent", program: nil).with { + $0.baseColor = V3(0.8, 0.8, 0.8); $0.roughness = 0.3; $0.specular = 0.5 + $0.mode = .transparent; $0.opacity = 0.55; $0.twoSided = true + }, + // Yellow-room lighting diffuser: amber, self-lit. + MaterialSpec(key: "emissive.amber", program: nil).with { + $0.baseColor = V3(1, 0.55, 0.05); $0.mode = .emissive; $0.emissive = V3(1, 0.5, 0.03); $0.emissiveIntensity = 2.2 + }, + ] +} diff --git a/Sources/RealMaterials/Library/TerawattSite.swift b/Sources/RealMaterials/Library/TerawattSite.swift new file mode 100644 index 00000000..515f3f45 --- /dev/null +++ b/Sources/RealMaterials/Library/TerawattSite.swift @@ -0,0 +1,65 @@ +import RealCore + +public extension MaterialLibrary { + /// Off-world sites: thermal-control white paint, gold MLI, space solar cells and Kapton, radiator coolant glow. + static let terawattSite: [MaterialSpec] = [ + // Z93-style white thermal control coating on aluminum: matte warm white, faint orbital darkening. + MaterialSpec(key: "thermal.radiator-white", program: .paintedMetal).with { + $0.colorA = linear(0xE6E6E0); $0.colorB = linear(0xC9C8C0) + $0.knobs = V4(0.3, 0.2, 0.4, 0.1); $0.seed = 9901; $0.tileSize = 0.6; $0.normalStrength = 0.3 + $0.hasMetallicMap = true; $0.metallic = 0.1; $0.roughness = 0.62 + }, + // Radiator coolant lines running hot: blackbody orange emissive on dark metal. + MaterialSpec(key: "thermal.radiator-glow", program: nil).with { + $0.baseColor = V3(0.1, 0.06, 0.04); $0.metallic = 0.6; $0.roughness = 0.4 + $0.mode = .emissive; $0.emissive = RealHeat.blackbody(kelvin: 1150); $0.emissiveIntensity = 1.6 + }, + // Gold-coated Kapton multilayer insulation blanket: crinkled facets, sharp creases. + MaterialSpec(key: "mli.blanket", program: .foilCrinkle).with { + $0.colorA = linear(0xD9A24A); $0.colorB = linear(0x5A3A12); $0.colorC = linear(0x2E1C08) + $0.knobs = V4(14, 1.1, 0.16, 1.0); $0.seed = 9902; $0.tileSize = 0.6; $0.normalStrength = 3 + $0.hasMetallicMap = true; $0.metallic = 1; $0.roughness = 0.2 + }, + // Aluminized Mylar outer layer MLI: silver-white crinkle. + MaterialSpec(key: "mli.silver", program: .foilCrinkle).with { + $0.colorA = linear(0xD6DADF); $0.colorB = linear(0x565B64); $0.colorC = linear(0x2E3238) + $0.knobs = V4(12, 0.9, 0.18, 0.8); $0.seed = 9903; $0.tileSize = 0.5; $0.normalStrength = 2.5 + $0.hasMetallicMap = true; $0.metallic = 1; $0.roughness = 0.22 + }, + // Triple-junction cell grid on Kapton, 8 x 4 cm cells. + MaterialSpec(key: "solar.cell", program: .solarCell).with { + $0.colorA = linear(0x1A2A5C); $0.colorB = linear(0xB9BEC6); $0.colorC = linear(0xA8671F) + $0.knobs = V4(8, 16, 0.045, 0.35); $0.seed = 9904; $0.tileSize = 0.64; $0.normalStrength = 0.6 + $0.hasMetallicMap = true; $0.metallic = 0.3; $0.roughness = 0.2 + }, + // Kapton substrate and panel back: amber polyimide film. + MaterialSpec(key: "solar.kapton", program: .plastic).with { + $0.colorA = linear(0xB06E22); $0.knobs = V4(0.2, 0.2, 0.3, 0); $0.seed = 9905 + $0.tileSize = 0.3; $0.resolution = 512; $0.normalStrength = 0.2 + }, + // Lunar regolith: fine grey-brown basaltic dust with sharp-edged fragments, no wind ripples. + MaterialSpec(key: "regolith.lunar", program: .gravel).with { + $0.colorA = linear(0x6F6D69); $0.colorB = linear(0x4A4947); $0.colorC = linear(0x8C8A85) + $0.knobs = V4(0.95, 0.35, 0.6, 0); $0.seed = 9911; $0.tileSize = 1.2; $0.normalStrength = 2.4; $0.resolution = 2048 + $0.antiTile = true; $0.roughness = 0.97 + }, + // Compacted regolith on berm slopes and rover tracks: lighter, finer, matte. + MaterialSpec(key: "regolith.lunar-fine", program: .sand).with { + $0.colorA = linear(0x84827D); $0.colorB = linear(0x5A5855); $0.colorC = linear(0x9A9893) + $0.knobs = V4(0.4, 0, 0.05, 90); $0.seed = 9912; $0.tileSize = 1.5; $0.normalStrength = 1.2; $0.resolution = 2048 + $0.antiTile = true; $0.roughness = 0.98 + }, + // Martian dust and sand: iron-oxide red-orange with soft ripples and dark basalt grains. + MaterialSpec(key: "dust.mars", program: .sand).with { + $0.colorA = linear(0xA9623A); $0.colorB = linear(0x6B3A24); $0.colorC = linear(0xC8855A) + $0.knobs = V4(1.0, 0, 0.22, 22); $0.seed = 9913; $0.tileSize = 2.2; $0.normalStrength = 2.4; $0.resolution = 2048 + $0.antiTile = true; $0.roughness = 0.95 + }, + // Dust settled on panels and metal: thin ochre-red film, rougher and flatter than the ground. + MaterialSpec(key: "dust.mars-film", program: .sand).with { + $0.colorA = linear(0xB87850); $0.colorB = linear(0x8A5636); $0.colorC = linear(0xD09A70) + $0.knobs = V4(0.5, 0, 0.1, 60); $0.seed = 9914; $0.tileSize = 0.8; $0.normalStrength = 0.9; $0.resolution = 1024 + $0.roughness = 0.92 + }, + ] +} diff --git a/Sources/RealMaterials/MaterialSpec.swift b/Sources/RealMaterials/MaterialSpec.swift index 90d0a899..a4b748fd 100644 --- a/Sources/RealMaterials/MaterialSpec.swift +++ b/Sources/RealMaterials/MaterialSpec.swift @@ -34,6 +34,7 @@ public enum TextureProgram: Int32, Sendable, CaseIterable { case barkSycamore case fishSkin, fishFin, fishEye case perforatedSheet + case solarCell } /// A fully specified PBR material: which texture program, its colors/knobs, and how RealityKit should @@ -114,7 +115,7 @@ public func linear(_ hex: UInt32, _ a: Float = 1) -> V4 { /// Specs live in `Library/.swift`; add a family array to `all` when creating a new file. public enum MaterialLibrary { /// Every built-in spec, in catalog order. Keys are unique (tested). - public static let all: [MaterialSpec] = bark + foliage + stone + wood + metal + mineral + plastic + masonry + emissive + plants + industrial + ground + conifer + rock + fabric + woodExtra + water + groundBlend + groundBlends + wild + craft + office + sports + sportsGear + medical + medicalSurgical + medicalDiagnostic + medicalBedside + medicalPatient + medicalTheatre + medicalClinic + medicalHospitalStructures + medicalHospitalLobby + anatomy + woodshop + pantry + kitchen + cookware + food + landscaping + recipeFood + architecture + city + facade + vehicle + utility + lineman + insect + fauna + paint + aerospace + roadster + river + fish + tackle + hunting + terawattDC + public static let all: [MaterialSpec] = bark + foliage + stone + wood + metal + mineral + plastic + masonry + emissive + plants + industrial + ground + conifer + rock + fabric + woodExtra + water + groundBlend + groundBlends + wild + craft + office + sports + sportsGear + medical + medicalSurgical + medicalDiagnostic + medicalBedside + medicalPatient + medicalTheatre + medicalClinic + medicalHospitalStructures + medicalHospitalLobby + anatomy + woodshop + pantry + kitchen + cookware + food + landscaping + recipeFood + architecture + city + facade + vehicle + utility + lineman + insect + fauna + paint + aerospace + roadster + river + fish + tackle + hunting + terawattDC + terawattFab + terawattChip + terawattSite public static let keys: [MaterialKey] = all.map { $0.key } diff --git a/Sources/RealMaterials/Shaders/ShaderSource.swift b/Sources/RealMaterials/Shaders/ShaderSource.swift index 750bbbf0..08f3383b 100644 --- a/Sources/RealMaterials/Shaders/ShaderSource.swift +++ b/Sources/RealMaterials/Shaders/ShaderSource.swift @@ -8,7 +8,7 @@ // `evaluate()` is generated from `TextureProgram.allCases`, so there is no switch to edit. /// Program families, in compile order. Append new family sources here. -let programSources: [String] = [metalBark, metalFoliage, metalStone, metalBuilt, metalPlant, metalIndustrial, metalGround, metalConifer, metalRock, metalFabric, metalWater, metalCraft, metalOffice, metalSports, metalMedical, metalAnatomy, metalWoodshopShop, metalWoodshopTools, metalPantry, metalKitchen, metalCookware, metalFood, metalLandscaping, metalRecipe, metalInsect, metalFauna, metalAerospace, metalRiver, metalFish, metalTerawattDC] +let programSources: [String] = [metalBark, metalFoliage, metalStone, metalBuilt, metalPlant, metalIndustrial, metalGround, metalConifer, metalRock, metalFabric, metalWater, metalCraft, metalOffice, metalSports, metalMedical, metalAnatomy, metalWoodshopShop, metalWoodshopTools, metalPantry, metalKitchen, metalCookware, metalFood, metalLandscaping, metalRecipe, metalInsect, metalFauna, metalAerospace, metalRiver, metalFish, metalTerawattDC, metalSite] /// `evaluate()` dispatch, one case per `TextureProgram`. var metalDispatch: String { diff --git a/Sources/RealMaterials/Shaders/SiteShaders.swift b/Sources/RealMaterials/Shaders/SiteShaders.swift new file mode 100644 index 00000000..bfb72d6e --- /dev/null +++ b/Sources/RealMaterials/Shaders/SiteShaders.swift @@ -0,0 +1,34 @@ +// Off-world site programs: solarCell (aligned space solar cell grid with fingers, busbar and interconnect gaps). + +let metalSite = #""" +// ---------------------------------------------------------------- site +// Triple-junction space solar cells in an aligned grid on a Kapton substrate: AR-coated blue-violet cell +// with chamfered corners, fine collector fingers, one busbar, amber interconnect gap. +// colorA cell, colorB metal grid, colorC gap/substrate. f.x cells along u per tile, f.y along v, +// f.z gap width (cell fraction), f.w per-cell tone variation. +S solarCell(float2 uv, constant RFParams &P) { + S s = defaults(); uint sd = P.seed; + float2 n = float2(max(1.0, floor(P.f.x + 0.5)), max(1.0, floor(P.f.y + 0.5))); + float2 g = uv * n; + float2 gi = floor(g); float2 gf = g - gi; + int2 id = wrapc(int2(gi), int2(n)); + float2 e = min(gf, 1.0 - gf); + float gw = P.f.z; + float inCell = step(gw, e.x) * step(gw * 0.6, e.y) * step(0.16, e.x + e.y); + float tone = h01(id, sd + 3u); + float fing = 1.0 - smoothstep(0.0, 0.06, abs(fract(gf.x * 14.0) - 0.5) - 0.42 + 0.06); + float bus = 1.0 - smoothstep(0.012, 0.03, abs(gf.y - 0.5)); + float ar = fbm(uv, int2(int(n.x) * 2, int(n.y) * 2), 2, sd + 5u); + float3 cell = P.colorA.rgb * (1.0 + (tone - 0.5) * P.f.w); + cell = mix(cell, cell * float3(1.25, 0.9, 1.5), sat(0.5 + ar) * 0.35); + float3 gap = P.colorC.rgb * (0.85 + 0.3 * fbm(uv, int2(24, 24), 2, sd + 7u)); + float metalMask = sat(fing * 0.3 + bus) * inCell; + s.albedo = mix(gap, mix(cell, P.colorB.rgb, metalMask), inCell); + s.height = inCell * 0.55 + metalMask * 0.04 + ar * 0.01; + s.rough = mix(0.7, 0.16 + metalMask * 0.1, inCell); + s.ao = 1.0 - (1.0 - inCell) * 0.45; + s.metal = inCell * 0.3 + metalMask * 0.6; + s.alpha = 1.0; + return s; +} +"""# diff --git a/briefs/bond-wire-macro.json b/briefs/bond-wire-macro.json new file mode 100644 index 00000000..52c6f391 --- /dev/null +++ b/briefs/bond-wire-macro.json @@ -0,0 +1,35 @@ +{ + "budget": 10500, + "id": "bond-wire-macro", + "kind": "prop", + "materials": [ + "metal.gold", + "metal.nickel", + "laminate.abf", + "silicon.die" + ], + "name": "Bond wire macro", + "notes": "", + "parts": [ + "die edge with aluminium pads", + "substrate gold finger pads", + "gold bond wire loops", + "ball bonds", + "wedge bonds", + "wire loop arcs parametric" + ], + "references": [], + "size": [ + 0.4, + 0.1, + 0.2 + ], + "style": "Macro display model (about 1000x): row of parametric gold bond wire loops between die pads and substrate fingers, ball bonds with neck on die pads, wedge bonds with tail on fingers, polished gold.", + "summary": "Parametric gold bond wire loops between die pads and substrate fingers with ball and wedge bonds at macro scale.", + "tags": [ + "prop" + ], + "theme": "ChipLab", + "threshold": 0.85, + "tolerance": 0.08 +} \ No newline at end of file diff --git a/briefs/chip-package-final.json b/briefs/chip-package-final.json new file mode 100644 index 00000000..ef88f30e --- /dev/null +++ b/briefs/chip-package-final.json @@ -0,0 +1,41 @@ +{ + "budget": 12500, + "id": "chip-package-final", + "kind": "prop", + "materials": [ + "metal.nickel", + "silicon.die", + "silicon.die-edge", + "laminate.abf", + "metal.gold", + "plastic.underfill", + "metal.solder" + ], + "name": "Chip package final", + "notes": "Complete accelerator package with lid and exploded layers. Default state lid-off (lid hinged open 105 degrees); assembled height 6 mm.", + "parts": [ + "nickel-plated copper lid with chamfered edge and sealant bead", + "substrate with solder mask edge", + "exposed gold pads on a row of edge decoupling capacitors", + "lid removable (articulated)", + "exploded layers: lid, die set, substrate", + "central GPU die and flanking HBM stacks visible with lid off" + ], + "references": [], + "size": [ + 0.1, + 0.086, + 0.118 + ], + "style": "AI accelerator package, 100 x 100 mm, 6 mm tall, nickel lid, lightly handled.", + "summary": "AI accelerator package: nickel lid with laser mark, compute dies and HBM on glass interposer, ABF substrate, BGA balls; explodes.", + "tags": [ + "prop", + "chiplab", + "electronics", + "articulated" + ], + "theme": "ChipLab", + "threshold": 0.9, + "tolerance": 0.1 +} diff --git a/briefs/cleanroom-floor-grate.json b/briefs/cleanroom-floor-grate.json new file mode 100644 index 00000000..dfd2ed0f --- /dev/null +++ b/briefs/cleanroom-floor-grate.json @@ -0,0 +1,23 @@ +{ + "budget": 800, + "id": "cleanroom-floor-grate", + "kind": "prop", + "materials": [], + "name": "Cleanroom floor grate", + "notes": "", + "parts": [], + "references": [], + "size": [ + 0.6, + 0.05, + 0.6 + ], + "style": "TODO: era, condition, finish", + "summary": "TODO: one sentence, what it is and how it's built.", + "tags": [ + "prop" + ], + "theme": "Fab", + "threshold": 0.85, + "tolerance": 0.08 +} \ No newline at end of file diff --git a/briefs/cmp-tool.json b/briefs/cmp-tool.json new file mode 100644 index 00000000..b6c5295f --- /dev/null +++ b/briefs/cmp-tool.json @@ -0,0 +1,43 @@ +{ + "budget": 12000, + "id": "cmp-tool", + "kind": "prop", + "materials": [ + "metal.powdercoat", + "metal.stainless", + "metal.anodized", + "plastic.pad", + "glass.window-tint", + "plastic.matte", + "rubber.tubing", + "glass.lamp", + "plastic.polycarbonate-tint", + "silicon.thinfilm" + ], + "name": "CMP tool", + "notes": "Named parts platen-1..platen-3 spin.", + "parts": [ + "white enclosure with large front window", + "3 rotating platens with polishing pads", + "slurry arms over each platen", + "carrier heads on a gantry", + "post-CMP cleaner module", + "load ports with FOUPs", + "status tower", + "named spinning platens" + ], + "references": [], + "size": [ + 4, + 2.2, + 3 + ], + "style": "Generic CMP polisher, white powdercoat enclosure, tinted window, grey polishing pads with slurry stains, light wear, no branding.", + "summary": "CMP tool with 3 polishing platens, slurry arms, carrier heads, cleaner module, load ports.", + "tags": [ + "prop" + ], + "theme": "Fab", + "threshold": 0.85, + "tolerance": 0.08 +} \ No newline at end of file diff --git a/briefs/cvd-ald-cluster.json b/briefs/cvd-ald-cluster.json new file mode 100644 index 00000000..f2919034 --- /dev/null +++ b/briefs/cvd-ald-cluster.json @@ -0,0 +1,46 @@ +{ + "budget": 14000, + "id": "cvd-ald-cluster", + "kind": "prop", + "materials": [ + "metal.stainless", + "metal.anodized", + "metal.powdercoat", + "metal.satin-aluminum", + "metal.brass", + "glass.lamp", + "glass.window-tint", + "plastic.matte", + "rubber.tubing" + ], + "name": "CVD ALD cluster tool", + "notes": "Chamber lids lid-1..lid-4 articulated. Variant of ClusterTool.", + "parts": [ + "4 process chambers with hinged heated showerhead lids", + "heated lid ring with insulation jacket and heat-traced lines", + "precursor ampoule cabinets with ampoules and heated delivery lines", + "extended gas sticks with valves", + "central hexagonal vacuum transfer chamber with lid and viewport", + "2 load locks", + "EFEM front with 3 load ports and FOUPs", + "cable bundles", + "status tower", + "named chamber lids" + ], + "references": [], + "size": [ + 4, + 2.5, + 4 + ], + "style": "Generic CVD/ALD cluster tool, aluminum chambers, heated showerhead lids with insulation wrap, white EFEM, light wear, no branding.", + "summary": "Hex cluster with 4 CVD/ALD chambers, heated showerhead lids, precursor ampoule cabinets, extra gas sticks, load locks, EFEM.", + "tags": [ + "prop", + "semiconductor", + "articulated" + ], + "theme": "Fab", + "threshold": 0.85, + "tolerance": 0.08 +} \ No newline at end of file diff --git a/briefs/design-workbench-2035.json b/briefs/design-workbench-2035.json new file mode 100644 index 00000000..6b2406fc --- /dev/null +++ b/briefs/design-workbench-2035.json @@ -0,0 +1,40 @@ +{ + "budget": 10000, + "id": "design-workbench-2035", + "kind": "prop", + "materials": [ + "laminate.white", + "metal.anodized-black", + "metal.aluminum-brushed", + "glass.display", + "screen.ui", + "emissive.holo-plane", + "plastic.black", + "rubber", + "emissive.holo-grid" + ], + "name": "Design workbench 2035", + "notes": "", + "parts": [ + "laminate desk top", + "anodized frame", + "three curved glass displays", + "keyboard deck", + "holo plane", + "cable spine" + ], + "references": [], + "size": [ + 2.4, + 1.0, + 1.2 + ], + "style": "2.4 x 1.0 x 1.2 m 2035 design desk: pale laminate top with rounded edge, dark anodized frame and legs, three curved glass displays on arms, keyboard deck with keys and palm rest, floating emissive holo plane above the desk, cable spine down the rear leg.", + "summary": "2035 design workbench: laminate top, anodized frame, three curved glass displays, keyboard deck, emissive holo plane, cable spine.", + "tags": [ + "prop" + ], + "theme": "ChipLab", + "threshold": 0.85, + "tolerance": 0.08 +} \ No newline at end of file diff --git a/briefs/dicing-saw.json b/briefs/dicing-saw.json new file mode 100644 index 00000000..634ca854 --- /dev/null +++ b/briefs/dicing-saw.json @@ -0,0 +1,38 @@ +{ + "budget": 8000, + "id": "dicing-saw", + "kind": "prop", + "materials": [ + "metal.powdercoat", + "metal.stainless", + "metal.anodized", + "glass.window-tint", + "plastic.matte", + "silicon.thinfilm-patterned", + "glass.lamp" + ], + "name": "Dicing saw", + "notes": "Named part: lid", + "parts": [ + "lid with window", + "spindle with blade", + "cutting table with tape-frame wafer", + "control panel", + "status tower", + "coolant nozzles" + ], + "references": [], + "size": [ + 1.5, + 1.8, + 1.5 + ], + "style": "Generic dicing saw, white powdercoat, stainless wet area, light wear at handles, fingerprints on window, no branding.", + "summary": "Wafer dicing saw with hinged lid, window over spindle, tape-frame wafer on cutting table.", + "tags": [ + "prop" + ], + "theme": "Fab", + "threshold": 0.85, + "tolerance": 0.08 +} \ No newline at end of file diff --git a/briefs/die-blank-reticle.json b/briefs/die-blank-reticle.json new file mode 100644 index 00000000..ebd7d304 --- /dev/null +++ b/briefs/die-blank-reticle.json @@ -0,0 +1,35 @@ +{ + "budget": 2000, + "id": "die-blank-reticle", + "kind": "prop", + "materials": [ + "silicon.die", + "silicon.die-edge", + "metal.solder", + "metal.gold" + ], + "name": "Blank reticle-limit die", + "notes": "", + "parts": [ + "mirror-polished silicon backside", + "front face with faint metal-layer interference sheen", + "seal ring at edges", + "scribe-line remnant", + "C4 solder bump field", + "named block slot sockets" + ], + "references": [], + "size": [ + 0.026, + 0.0008, + 0.033 + ], + "style": "26 x 33 mm reticle-limit blank die, 0.8 mm thick, dark blue-grey mirror silicon, chamfered diced edge, faint violet/teal interference bands, seal ring, C4 bumps, 3x4 block slot sockets.", + "summary": "Blank reticle-limit die: mirror silicon backside, interference-sheen front, seal ring, scribe remnant, C4 bump field, named block slot sockets.", + "tags": [ + "prop" + ], + "theme": "ChipLab", + "threshold": 0.85, + "tolerance": 0.08 +} diff --git a/briefs/die-block-tile.json b/briefs/die-block-tile.json new file mode 100644 index 00000000..74826f4b --- /dev/null +++ b/briefs/die-block-tile.json @@ -0,0 +1,33 @@ +{ + "budget": 600, + "id": "die-block-tile", + "kind": "prop", + "materials": [ + "silicon.die", + "silicon.die-edge", + "metal.gold", + "emissive.die-tensor" + ], + "name": "Die block tile", + "notes": "", + "parts": [ + "silicon die body", + "type-specific surface pattern", + "subtle emissive tint", + "snap pin sockets" + ], + "references": [], + "size": [ + 0.0062, + 0.00046, + 0.0062 + ], + "style": "Parametric silicon IP-block die tile, 6.2 mm square default, 0.4 mm thick; tensor (MAC array), sram (bit-cell stripes), noc (routing grid), hbmPhy (edge bump rows), io (pad ring); faint emissive tint per type; four named snap pins.", + "summary": "Parametric die block tile (tensor, sram, noc, hbmPhy, io): silicon body, type-specific surface pattern, emissive tint, named snap pin sockets.", + "tags": [ + "prop" + ], + "theme": "ChipLab", + "threshold": 0.85, + "tolerance": 0.08 +} \ No newline at end of file diff --git a/briefs/diffusion-furnace.json b/briefs/diffusion-furnace.json new file mode 100644 index 00000000..8835c9aa --- /dev/null +++ b/briefs/diffusion-furnace.json @@ -0,0 +1,42 @@ +{ + "budget": 10000, + "id": "diffusion-furnace", + "kind": "prop", + "materials": [ + "metal.powdercoat", + "quartz", + "metal.stainless", + "metal.anodized", + "plastic.matte", + "glass.lamp", + "rubber.tubing" + ], + "name": "Diffusion furnace", + "notes": "Part boat-elevator slides along Y.", + "parts": [ + "powdercoat tower frame", + "vertical process tube", + "quartz wafer boat with slots on boat elevator", + "heater jacket", + "gas panel", + "exhaust duct", + "controller panel", + "named boat-elevator" + ], + "references": [], + "size": [ + 2, + 3.5, + 2 + ], + "style": "Vertical diffusion furnace stack, grey powdercoat tower, quartz boat on lift, insulated heater jacket, gas panel, exhaust duct, light wear, no branding.", + "summary": "TODO: one sentence, what it is and how it's built.", + "tags": [ + "prop", + "semiconductor", + "articulated" + ], + "theme": "Fab", + "threshold": 0.85, + "tolerance": 0.08 +} \ No newline at end of file diff --git a/briefs/duv-immersion-scanner.json b/briefs/duv-immersion-scanner.json new file mode 100644 index 00000000..2087378d --- /dev/null +++ b/briefs/duv-immersion-scanner.json @@ -0,0 +1,43 @@ +{ + "budget": 14000, + "id": "duv-immersion-scanner", + "kind": "prop", + "materials": [ + "metal.powdercoat", + "metal.anodized", + "metal.aluminum-brushed", + "metal.steel", + "metal.grate-iron", + "glass.lamp", + "plastic.matte", + "rubber.tubing", + "glass.window-tint" + ], + "name": "DUV immersion scanner", + "notes": "Generic, no branding. 6 x 3 x 3 m. cover-top and cover-side lift off.", + "parts": [ + "white modular scanner enclosure with panel seams", + "removable covers (named parts)", + "immersion hood over wafer stage", + "reticle handler and wafer handler", + "status tower", + "cable tray", + "leveling feet on cleanroom grate" + ], + "references": [], + "size": [ + 6, + 3, + 3 + ], + "style": "Generic ArF immersion lithography scanner, smaller white modular enclosure, cleanroom.", + "summary": "DUV immersion scanner: white modular enclosure, removable covers, immersion hood, handlers.", + "tags": [ + "prop", + "semiconductor", + "articulated" + ], + "theme": "Fab", + "threshold": 0.85, + "tolerance": 0.08 +} \ No newline at end of file diff --git a/briefs/earth-site-pin.json b/briefs/earth-site-pin.json new file mode 100644 index 00000000..090f7052 --- /dev/null +++ b/briefs/earth-site-pin.json @@ -0,0 +1,32 @@ +{ + "budget": 500, + "id": "earth-site-pin", + "kind": "prop", + "materials": [ + "metal.aluminum-brushed", + "metal.stainless", + "emissive.signal-orange" + ], + "name": "Earth site pin", + "notes": "", + "parts": [ + "brushed metal pin shaft", + "glowing emissive head knob", + "collar ring", + "tapered tip" + ], + "references": [], + "size": [ + 0.05, + 0.15, + 0.05 + ], + "style": "Ultra realistic tabletop map marker pin: brushed metal needle and collar, spherical head knob with glowing emissive core.", + "summary": "Tabletop map marker pin: brushed metal shaft, collar and a glowing emissive head knob.", + "tags": [ + "prop" + ], + "theme": "DataCenterSite", + "threshold": 0.85, + "tolerance": 0.08 +} \ No newline at end of file diff --git a/briefs/ebeam-metrology.json b/briefs/ebeam-metrology.json new file mode 100644 index 00000000..91c64314 --- /dev/null +++ b/briefs/ebeam-metrology.json @@ -0,0 +1,39 @@ +{ + "budget": 10000, + "id": "ebeam-metrology", + "kind": "prop", + "materials": [ + "metal.powdercoat", + "metal.stainless", + "metal.anodized", + "glass.window-tint", + "plastic.matte", + "plastic.yellow", + "glass.lamp" + ], + "name": "E-beam metrology tool", + "notes": "Named part: door", + "parts": [ + "e-beam column dome", + "vibration-isolated base with pneumatic isolators", + "chamber cabinet", + "EFEM with hinged door", + "load port", + "status tower", + "monitor arm" + ], + "references": [], + "size": [ + 3, + 2.2, + 2.5 + ], + "style": "Generic CD-SEM style metrology tool, white powdercoat, light wear at handles, fingerprints on door window, no branding.", + "summary": "Wafer-level e-beam metrology tool: vibration-isolated base, chamber cabinet, column dome, EFEM with hinged door.", + "tags": [ + "prop" + ], + "theme": "Fab", + "threshold": 0.85, + "tolerance": 0.08 +} \ No newline at end of file diff --git a/briefs/efem-robot.json b/briefs/efem-robot.json new file mode 100644 index 00000000..ec6c94ac --- /dev/null +++ b/briefs/efem-robot.json @@ -0,0 +1,35 @@ +{ + "budget": 8000, + "id": "efem-robot", + "kind": "prop", + "materials": [ + "metal.aluminum-brushed", + "plastic.matte", + "metal.anodized" + ], + "name": "EFEM wafer robot", + "notes": "", + "parts": [ + "pedestal base", + "lift column", + "two-link arm with named joints", + "end-effector fork with wafer", + "cable carrier" + ], + "references": [], + "size": [ + 1, + 1.5, + 1 + ], + "style": "Brooks/Yaskawa-class SCARA 300 mm wafer-handling robot, clean room.", + "summary": "SCARA wafer-handling robot with column, two-link arm, fork end-effector and cable carrier.", + "tags": [ + "prop", + "semiconductor", + "articulated" + ], + "theme": "Fab", + "threshold": 0.85, + "tolerance": 0.08 +} \ No newline at end of file diff --git a/briefs/euv-light-source.json b/briefs/euv-light-source.json new file mode 100644 index 00000000..d8f8b1b0 --- /dev/null +++ b/briefs/euv-light-source.json @@ -0,0 +1,45 @@ +{ + "budget": 14000, + "id": "euv-light-source", + "kind": "prop", + "materials": [ + "metal.powdercoat", + "metal.steel", + "metal.copper", + "metal.mirror-polish", + "metal.anodized", + "metal.grate-iron", + "glass.lamp", + "glass.clear", + "rubber.tubing", + "plastic.matte" + ], + "name": "EUV light source", + "notes": "Generic, no branding. 6 x 4 x 4 m. Part cutaway-cover slides off to show the collector.", + "parts": [ + "CO2 laser drive cabinet with white panels and vents", + "tin-droplet vessel with bolted flanges", + "collector mirror dish inside vessel", + "cutaway cover (named part) exposing collector", + "tin droplet generator and plasma glow", + "heavy copper and steel plumbing", + "status tower", + "leveling feet on cleanroom grate" + ], + "references": [], + "size": [ + 6, + 4, + 4 + ], + "style": "Generic EUV laser-produced-plasma source sub-fab module, white cabinet, steel vessel, copper water plumbing.", + "summary": "EUV light source: CO2 laser drive cabinet, tin vessel with collector mirror, cutaway cover, copper and steel plumbing.", + "tags": [ + "prop", + "semiconductor", + "articulated" + ], + "theme": "Fab", + "threshold": 0.85, + "tolerance": 0.08 +} \ No newline at end of file diff --git a/briefs/euv-scanner-highna.json b/briefs/euv-scanner-highna.json new file mode 100644 index 00000000..11acf735 --- /dev/null +++ b/briefs/euv-scanner-highna.json @@ -0,0 +1,28 @@ +{ + "budget": 15000, + "id": "euv-scanner-highna", + "kind": "prop", + "materials": ["metal.powdercoat", "metal.anodized", "metal.aluminum-brushed", "metal.steel", "metal.grate-iron", "glass.lamp", "plastic.matte", "rubber.tubing", "plastic.polycarbonate-tint"], + "name": "High-NA EUV scanner", + "notes": "Generic, no branding. 14 x 4 x 4 m. Covers removable and wafer stage as named parts.", + "parts": [ + "white powdercoat modular enclosure with panel seams", + "service doors and vents", + "status tower", + "reticle handler module", + "wafer handler module", + "source connection duct at rear", + "cable trays on top", + "leveling feet on cleanroom grate", + "removable covers", + "wafer stage" + ], + "references": [], + "size": [14, 4, 4], + "style": "ASML-class High-NA EUV lithography scanner, generic, white powdercoat modular frame, cleanroom.", + "summary": "High-NA EUV lithography scanner: modular white enclosure, handlers, source duct, status tower, removable covers and wafer stage.", + "tags": ["prop", "semiconductor", "articulated"], + "theme": "Fab", + "threshold": 0.85, + "tolerance": 0.08 +} diff --git a/briefs/fab-yellow-light-panel.json b/briefs/fab-yellow-light-panel.json new file mode 100644 index 00000000..18c78a64 --- /dev/null +++ b/briefs/fab-yellow-light-panel.json @@ -0,0 +1,23 @@ +{ + "budget": 400, + "id": "fab-yellow-light-panel", + "kind": "prop", + "materials": [], + "name": "Fab yellow light panel", + "notes": "", + "parts": [], + "references": [], + "size": [ + 1.2, + 0.05, + 0.6 + ], + "style": "TODO: era, condition, finish", + "summary": "TODO: one sentence, what it is and how it's built.", + "tags": [ + "prop" + ], + "theme": "Fab", + "threshold": 0.85, + "tolerance": 0.08 +} \ No newline at end of file diff --git a/briefs/ffu-ceiling-tile.json b/briefs/ffu-ceiling-tile.json new file mode 100644 index 00000000..38e4bf7c --- /dev/null +++ b/briefs/ffu-ceiling-tile.json @@ -0,0 +1,23 @@ +{ + "budget": 1200, + "id": "ffu-ceiling-tile", + "kind": "prop", + "materials": [], + "name": "FFU ceiling tile", + "notes": "", + "parts": [], + "references": [], + "size": [ + 1.2, + 0.3, + 0.6 + ], + "style": "TODO: era, condition, finish", + "summary": "TODO: one sentence, what it is and how it's built.", + "tags": [ + "prop" + ], + "theme": "Fab", + "threshold": 0.85, + "tolerance": 0.08 +} \ No newline at end of file diff --git a/briefs/foup-stocker.json b/briefs/foup-stocker.json new file mode 100644 index 00000000..1db7044f --- /dev/null +++ b/briefs/foup-stocker.json @@ -0,0 +1,37 @@ +{ + "budget": 12000, + "id": "foup-stocker", + "kind": "prop", + "materials": [ + "metal.powdercoat", + "glass.window-tint", + "plastic.polycarbonate-tint" + ], + "name": "FOUP stocker", + "notes": "", + "parts": [ + "tall white powdercoat enclosure", + "glass front window", + "shelves of FOUPs", + "internal robot arm (named part)", + "OHT load ports on top", + "I/O ports at floor", + "status tower" + ], + "references": [], + "size": [ + 6, + 4, + 2 + ], + "style": "Murata/Daifuku-class automated FOUP stocker, white enclosure, clean room.", + "summary": "Automated FOUP stocker: white enclosure, glass front showing FOUP shelves, internal stacker robot, OHT ports on top, I/O ports at floor.", + "tags": [ + "prop", + "semiconductor", + "articulated" + ], + "theme": "Fab", + "threshold": 0.85, + "tolerance": 0.08 +} \ No newline at end of file diff --git a/briefs/foup.json b/briefs/foup.json new file mode 100644 index 00000000..ac8ebca1 --- /dev/null +++ b/briefs/foup.json @@ -0,0 +1,38 @@ +{ + "budget": 5000, + "id": "foup", + "kind": "prop", + "materials": [ + "plastic.polycarbonate-tint", + "plastic.matte", + "silicon.thinfilm" + ], + "name": "FOUP front-opening unified pod", + "notes": "", + "parts": [ + "smoke polycarbonate shell with ribbed sides", + "front door with latch keyholes and gasket", + "robotic top flange (OHT grip)", + "carry handles on both sides", + "kinematic coupling plate with three V-grooves", + "25 wafer slots visible, 300 mm wafers", + "RFID tag and label" + ], + "references": [], + "size": [ + 0.4, + 0.36, + 0.34 + ], + "style": "SEMI E47.1 300 mm FOUP, Entegris/Shin-Etsu class, smoke tint, light handling wear.", + "summary": "25-slot front-opening pod for 300 mm wafers with top flange, side handles and door.", + "tags": [ + "prop", + "semiconductor", + "handheld", + "articulated" + ], + "theme": "Fab", + "threshold": 0.9, + "tolerance": 0.08 +} \ No newline at end of file diff --git a/briefs/ground-station-dish.json b/briefs/ground-station-dish.json new file mode 100644 index 00000000..b0bdcc90 --- /dev/null +++ b/briefs/ground-station-dish.json @@ -0,0 +1,37 @@ +{ + "budget": 10000, + "id": "ground-station-dish", + "kind": "prop", + "materials": [ + "metal.painted", + "metal.galvanized", + "metal.steel", + "concrete.rough", + "metal.aluminum-brushed", + "plastic.matte" + ], + "name": "Ground station dish", + "notes": "", + "parts": [ + "parabolic reflector with panel seams", + "back truss", + "feed horn on quadripod", + "az-el pedestal", + "dish slew", + "concrete foundation" + ], + "references": [], + "size": [ + 8, + 10, + 8 + ], + "style": "Ultra realistic 8 m satellite ground station antenna: segmented white aluminum reflector with panel seams and rivets, back truss, quadripod feed horn with LNA, az-el pedestal with slew drive, concrete foundation with anchor bolts.", + "summary": "Satellite ground station dish: panelled parabolic reflector, back truss, quadripod feed horn, az-el pedestal on a concrete foundation, slewing dish.", + "tags": [ + "prop" + ], + "theme": "DataCenterSite", + "threshold": 0.85, + "tolerance": 0.08 +} \ No newline at end of file diff --git a/briefs/hbm-stack.json b/briefs/hbm-stack.json new file mode 100644 index 00000000..461e3e73 --- /dev/null +++ b/briefs/hbm-stack.json @@ -0,0 +1,38 @@ +{ + "budget": 12000, + "id": "hbm-stack", + "kind": "prop", + "materials": [ + "silicon.die", + "silicon.die-edge", + "metal.copper", + "plastic.underfill" + ], + "name": "HBM5 stack", + "notes": "HBM5 16-high memory stack.", + "parts": [ + "base logic die", + "sixteen thinned DRAM dies with bond-line gaps", + "copper microbump rows between layers", + "underfill fillets", + "molded epoxy sidewalls", + "exploded layer state" + ], + "references": [], + "size": [ + 0.011, + 0.0023, + 0.01 + ], + "style": "HBM5 16-high stack on base logic die, 11 x 10 mm footprint, slightly stepped thinned dies, underfill fillets, copper microbump rows, molded sidewalls, exploded option.", + "summary": "HBM5 stack: base logic die, 16 thinned DRAM dies with edge sheen, underfill fillets, copper microbump rows and molded sidewalls; explodes.", + "tags": [ + "prop", + "chiplab", + "electronics", + "articulated" + ], + "theme": "ChipLab", + "threshold": 0.85, + "tolerance": 0.1 +} diff --git a/briefs/holo-table.json b/briefs/holo-table.json new file mode 100644 index 00000000..14b7ded0 --- /dev/null +++ b/briefs/holo-table.json @@ -0,0 +1,38 @@ +{ + "budget": 6000, + "id": "holo-table", + "kind": "prop", + "materials": [ + "glass.display", + "emissive.holo-grid", + "metal.anodized-black", + "metal.anodized", + "rubber" + ], + "name": "Holo table", + "notes": "Ultra realistic 2035 design table.", + "parts": [ + "anodized dark aluminum pedestal frame", + "glass display top", + "subtle emissive grid", + "rounded bevelled edges", + "cable port", + "leveling feet" + ], + "references": [], + "size": [ + 1.6, + 0.9, + 1.0 + ], + "style": "Ultra realistic 2035 design table, 1.6 x 0.9 x 1.0 m, dark anodized aluminum pedestal frame, black glass display top with subtle cyan emissive grid, bevelled rounded edges, cable port, leveling feet.", + "summary": "2035 holo table: anodized aluminum pedestal frame, glass top with emissive grid, cable port, leveling feet.", + "tags": [ + "prop", + "chiplab", + "furniture" + ], + "theme": "ChipLab", + "threshold": 0.85, + "tolerance": 0.08 +} \ No newline at end of file diff --git a/briefs/hybrid-bonder.json b/briefs/hybrid-bonder.json new file mode 100644 index 00000000..a26385e1 --- /dev/null +++ b/briefs/hybrid-bonder.json @@ -0,0 +1,40 @@ +{ + "budget": 10000, + "id": "hybrid-bonder", + "kind": "prop", + "materials": [ + "metal.powdercoat", + "glass.window-tint", + "metal.stainless", + "metal.anodized", + "plastic.matte", + "silicon.thinfilm-patterned", + "silicon.thinfilm-metal", + "glass.lamp" + ], + "name": "hybrid-bonder", + "notes": "Named part: door", + "parts": [ + "door with window", + "bond head", + "wafer chucks", + "stepped roof", + "side cabinet", + "status tower", + "control screen" + ], + "references": [], + "size": [ + 3, + 2.2, + 2.5 + ], + "style": "Generic wafer-to-wafer hybrid bonder, white powdercoat, stepped roof, side electronics cabinet, light wear at handles, fingerprints on window, no branding.", + "summary": "Hybrid bonder with stepped roof, side cabinet, hinged door and window onto bond head and chucks.", + "tags": [ + "prop" + ], + "theme": "Fab", + "threshold": 0.85, + "tolerance": 0.08 +} \ No newline at end of file diff --git a/briefs/interconnect-stack-cutaway.json b/briefs/interconnect-stack-cutaway.json new file mode 100644 index 00000000..0467a09e --- /dev/null +++ b/briefs/interconnect-stack-cutaway.json @@ -0,0 +1,38 @@ +{ + "budget": 8000, + "id": "interconnect-stack-cutaway", + "kind": "prop", + "materials": [ + "silicon.die-edge", + "metal.tungsten", + "metal.copper", + "oxide.lowk" + ], + "name": "Interconnect stack cutaway", + "notes": "Micro-scale model, scaled 1e6x, 0.5 m display size. Layers are named explode parts.", + "parts": [ + "silicon substrate with tungsten contacts", + "twelve copper metal layers m01-m12 with widening pitch", + "copper vias", + "low-k dielectric per layer", + "top pad layer", + "layer explode state" + ], + "references": [], + "size": [ + 0.5, + 1.0, + 0.5 + ], + "style": "BEOL interconnect stack cutaway, 12 copper metal layers alternating x and z in low-k dielectric, stair-stepped front to expose each layer, pitch and wire thickness widening upward, copper vias.", + "summary": "BEOL interconnect stack cutaway at 1e6x scale: 12 copper metal layers with vias in low-k dielectric, pitch widening upward; explodes by layer.", + "tags": [ + "prop", + "chiplab", + "micro-scale", + "articulated" + ], + "theme": "ChipLab", + "threshold": 0.85, + "tolerance": 0.08 +} \ No newline at end of file diff --git a/briefs/interposer-glass.json b/briefs/interposer-glass.json new file mode 100644 index 00000000..16a68e79 --- /dev/null +++ b/briefs/interposer-glass.json @@ -0,0 +1,36 @@ +{ + "budget": 2000, + "id": "interposer-glass", + "kind": "prop", + "materials": [ + "glass.substrate", + "metal.copper", + "metal.gold", + "plastic.underfill" + ], + "name": "Glass interposer", + "notes": "Glass core interposer for chiplet packaging.", + "parts": [ + "clear glass core slab", + "through-glass-via copper grid visible in the front and right polished edges", + "copper redistribution layer traces on top", + "copper landing pads", + "central die landing footprint microbump grid", + "two HBM landing footprints flanking the die", + "corner fiducials" + ], + "references": [], + "size": [ + 0.08, + 0.001, + 0.06 + ], + "style": "Ultra realistic glass-core interposer, 80 x 60 mm, 1 mm thick, bare, no die, pale clear glass with copper TGVs and RDL.", + "summary": "Glass-core interposer with TGV copper grid, RDL traces, die and HBM landing footprints.", + "tags": [ + "prop" + ], + "theme": "ChipLab", + "threshold": 0.85, + "tolerance": 0.08 +} \ No newline at end of file diff --git a/briefs/ion-implanter.json b/briefs/ion-implanter.json new file mode 100644 index 00000000..185690ca --- /dev/null +++ b/briefs/ion-implanter.json @@ -0,0 +1,40 @@ +{ + "budget": 14000, + "id": "ion-implanter", + "kind": "prop", + "materials": [ + "metal.powdercoat", + "metal.stainless", + "metal.anodized", + "glass.window-tint", + "plastic.matte", + "plastic.yellow", + "glass.lamp", + "rubber.tubing" + ], + "name": "Ion implanter", + "notes": "Named panels panel-1..panel-3 articulated.", + "parts": [ + "long beamline enclosure", + "ion source housing with high-voltage warning labels", + "analyzer magnet bulge", + "end station with wafer chamber", + "load ports with FOUPs", + "named articulated panels", + "status tower" + ], + "references": [], + "size": [ + 8, + 2.5, + 3 + ], + "style": "Generic medium-current ion implanter, white powdercoat panels, HV warning labels, analyzer magnet bulge, light wear, no branding.", + "summary": "Long beamline implanter with HV source, analyzer magnet, end station, load ports.", + "tags": [ + "prop" + ], + "theme": "Fab", + "threshold": 0.85, + "tolerance": 0.08 +} \ No newline at end of file diff --git a/briefs/litho-track.json b/briefs/litho-track.json new file mode 100644 index 00000000..2ecd78e8 --- /dev/null +++ b/briefs/litho-track.json @@ -0,0 +1,45 @@ +{ + "budget": 14000, + "id": "litho-track", + "kind": "prop", + "materials": [ + "metal.powdercoat", + "metal.anodized", + "metal.aluminum-brushed", + "metal.stainless", + "metal.grate-iron", + "glass.window-tint", + "glass.clear", + "glass.lamp", + "plastic.matte", + "plastic.polycarbonate-tint", + "silicon.thinfilm" + ], + "name": "Litho coater/developer track", + "notes": "Generic, no branding. 8 x 2.5 x 3 m. Panels are named parts.", + "parts": [ + "white powdercoat modules with panel seams", + "tinted service windows showing spin-coat bowls", + "EFEM with 4 FOUP load ports and docked FOUPs", + "status tower on roof", + "touchscreen on swing arm", + "leveling feet on cleanroom grate", + "named removable panels" + ], + "references": [], + "size": [ + 8, + 2.5, + 3 + ], + "style": "TEL/SCREEN-class coater/developer track, generic, white powdercoat cleanroom tool.", + "summary": "Coater/developer track: white paneled modules, tinted service windows with spin-coat bowls, 4 FOUP load ports, status tower, touchscreen.", + "tags": [ + "prop", + "semiconductor", + "articulated" + ], + "theme": "Fab", + "threshold": 0.85, + "tolerance": 0.08 +} \ No newline at end of file diff --git a/briefs/lunar-dc-habitat.json b/briefs/lunar-dc-habitat.json new file mode 100644 index 00000000..f8da2087 --- /dev/null +++ b/briefs/lunar-dc-habitat.json @@ -0,0 +1,37 @@ +{ + "budget": 15000, + "id": "lunar-dc-habitat", + "kind": "prop", + "materials": [ + "regolith.lunar", + "regolith.lunar-fine", + "fabric.canvas", + "thermal.radiator-white", + "mli.silver", + "metal.aluminum-brushed" + ], + "name": "Lunar data center habitat", + "notes": "", + "parts": [ + "pressurized modules", + "inflatable module", + "sandbag regolith berm", + "radiator fins", + "airlock", + "regolith ground skirt" + ], + "references": [], + "size": [ + 15, + 6, + 15 + ], + "style": "Ultra realistic lunar surface data center: rigid and inflatable modules half buried under a sandbag regolith berm, radiator fin bank, airlock with ramp, grey regolith ground.", + "summary": "Lunar data center habitat: modules under a sandbag regolith berm, radiator fins, airlock and grey regolith skirt.", + "tags": [ + "prop" + ], + "theme": "DataCenterSite", + "threshold": 0.85, + "tolerance": 0.08 +} \ No newline at end of file diff --git a/briefs/mars-dc-dome.json b/briefs/mars-dc-dome.json new file mode 100644 index 00000000..7646bf06 --- /dev/null +++ b/briefs/mars-dc-dome.json @@ -0,0 +1,36 @@ +{ + "budget": 15000, + "id": "mars-dc-dome", + "kind": "prop", + "materials": [ + "dust.mars", + "dust.mars-film", + "thermal.radiator-white", + "metal.aluminum-brushed", + "glass.tinted" + ], + "name": "Mars data center dome", + "notes": "", + "parts": [ + "geodesic dome panels", + "dust-coated lower panels", + "pressurized annex", + "airlock", + "radiators", + "dust ground" + ], + "references": [], + "size": [ + 20, + 10, + 20 + ], + "style": "Ultra realistic Mars surface data center: geodesic dome with red dust accumulated on the lower panels, pressurized annex with airlock, radiator bank, red dust ground with rocks.", + "summary": "Mars data center: geodesic dome with red dust on lower panels, pressurized annex and radiators.", + "tags": [ + "prop" + ], + "theme": "DataCenterSite", + "threshold": 0.85, + "tolerance": 0.08 +} \ No newline at end of file diff --git a/briefs/oht-rail.json b/briefs/oht-rail.json new file mode 100644 index 00000000..1c9577e4 --- /dev/null +++ b/briefs/oht-rail.json @@ -0,0 +1,23 @@ +{ + "budget": 800, + "id": "oht-rail", + "kind": "prop", + "materials": [], + "name": "OHT ceiling rail", + "notes": "", + "parts": [], + "references": [], + "size": [ + 3.0, + 0.2, + 0.2 + ], + "style": "TODO: era, condition, finish", + "summary": "TODO: one sentence, what it is and how it's built.", + "tags": [ + "prop" + ], + "theme": "Fab", + "threshold": 0.85, + "tolerance": 0.08 +} \ No newline at end of file diff --git a/briefs/oht-vehicle.json b/briefs/oht-vehicle.json new file mode 100644 index 00000000..e58a8533 --- /dev/null +++ b/briefs/oht-vehicle.json @@ -0,0 +1,36 @@ +{ + "budget": 6000, + "id": "oht-vehicle", + "kind": "prop", + "materials": [ + "metal.aluminum-brushed", + "plastic.matte", + "plastic.polycarbonate-tint" + ], + "name": "OHT hoist vehicle", + "notes": "", + "parts": [ + "aluminum chassis with painted covers", + "four-wheel travel carriage on rail", + "hoist with belts and gripper", + "gripper holding a FOUP flange", + "slide and rotate unit", + "status lamp and sensor" + ], + "references": [], + "size": [ + 0.6, + 0.6, + 0.9 + ], + "style": "Daifuku/Murata overhead hoist transport vehicle, white covers, clean room.", + "summary": "Overhead hoist transport vehicle with four-wheel carriage and belt hoist gripper.", + "tags": [ + "prop", + "semiconductor", + "articulated" + ], + "theme": "Fab", + "threshold": 0.9, + "tolerance": 0.08 +} \ No newline at end of file diff --git a/briefs/orbital-dc-module.json b/briefs/orbital-dc-module.json new file mode 100644 index 00000000..d1a66dd3 --- /dev/null +++ b/briefs/orbital-dc-module.json @@ -0,0 +1,41 @@ +{ + "budget": 15000, + "id": "orbital-dc-module", + "kind": "prop", + "materials": [ + "thermal.radiator-white", + "mli.blanket", + "composite.carbon", + "metal.titanium", + "metal.aluminum-brushed" + ], + "name": "Orbital DC module", + "notes": "", + "parts": [ + "pressurized hull", + "Whipple shield straps", + "gold MLI end cones", + "axial docking ports", + "radial hatches", + "square truss", + "radiator mounts", + "compute ORU boxes", + "grapple fixture", + "star tracker", + "omni antenna" + ], + "references": [], + "size": [ + 20, + 6, + 6 + ], + "style": "Ultra realistic pressurized orbital compute module 20 x 6 x 6 m: white shielded hull in a carbon square truss, gold MLI blanket cones, docking ports, radiator mounts with hoses, external compute boxes.", + "summary": "Pressurized orbital compute module: Whipple-shield hull in a square truss, gold MLI end cones, docking ports, radiator mounts and external compute boxes.", + "tags": [ + "prop" + ], + "theme": "DataCenterSite", + "threshold": 0.85, + "tolerance": 0.08 +} diff --git a/briefs/package-substrate.json b/briefs/package-substrate.json new file mode 100644 index 00000000..c5e365eb --- /dev/null +++ b/briefs/package-substrate.json @@ -0,0 +1,36 @@ +{ + "budget": 2500, + "id": "package-substrate", + "kind": "prop", + "materials": [ + "laminate.abf", + "metal.gold", + "metal.solder" + ], + "name": "Package substrate", + "notes": "Bare organic package substrate.", + "parts": [ + "ABF build-up laminate core with solder mask", + "central die land grid with gold pads", + "perimeter gold-plated pad array on the underside", + "corner fiducials and orientation chamfer", + "edge decoupling capacitors (0201) on the top" + ], + "references": [], + "size": [ + 0.1, + 0.003, + 0.1 + ], + "style": "Server AI accelerator organic substrate, 100 x 100 mm, 3 mm thick, bare, no die.", + "summary": "Bare organic package substrate: ABF laminate, gold die-land pads, perimeter pads, fiducials, edge capacitors.", + "tags": [ + "prop", + "chiplab", + "electronics", + "articulated" + ], + "theme": "ChipLab", + "threshold": 0.9, + "tolerance": 0.1 +} diff --git a/briefs/plasma-etch-cluster.json b/briefs/plasma-etch-cluster.json new file mode 100644 index 00000000..10c48bf3 --- /dev/null +++ b/briefs/plasma-etch-cluster.json @@ -0,0 +1,45 @@ +{ + "budget": 14000, + "id": "plasma-etch-cluster", + "kind": "prop", + "materials": [ + "metal.stainless", + "metal.anodized", + "metal.powdercoat", + "metal.satin-aluminum", + "glass.lamp", + "glass.window-tint", + "plastic.matte", + "rubber.tubing", + "silicon.thinfilm" + ], + "name": "Plasma etch cluster tool", + "notes": "Chamber lids lid-1..lid-4 are articulated. Helper: Props/Fab/ClusterTool.swift.", + "parts": [ + "central hexagonal vacuum transfer chamber with lid and viewport", + "4 bead-blasted aluminum process chambers with hinged lids", + "RF match boxes and gas lines on chamber tops", + "2 load locks", + "EFEM front with 3 load ports and FOUPs", + "stainless gas sticks with valves", + "cable bundles", + "status tower", + "named chamber lids" + ], + "references": [], + "size": [ + 4, + 2.5, + 4 + ], + "style": "Generic Lam/AMAT-class plasma etch cluster tool, bead-blasted aluminum chambers, white EFEM, light wear, no branding.", + "summary": "Hex transfer chamber cluster with 4 etch chambers, 2 load locks, EFEM with load ports, gas sticks, status tower.", + "tags": [ + "prop", + "semiconductor", + "articulated" + ], + "theme": "Fab", + "threshold": 0.85, + "tolerance": 0.08 +} \ No newline at end of file diff --git a/briefs/probe-station.json b/briefs/probe-station.json new file mode 100644 index 00000000..852f693d --- /dev/null +++ b/briefs/probe-station.json @@ -0,0 +1,38 @@ +{ + "budget": 9000, + "id": "probe-station", + "kind": "prop", + "materials": [ + "metal.powdercoat", + "metal.stainless", + "metal.anodized", + "glass.window-tint", + "plastic.matte", + "silicon.thinfilm", + "glass.lamp" + ], + "name": "Probe station", + "notes": "Named part: probe-card-lift", + "parts": [ + "isolation table", + "chuck with wafer", + "probe card lift", + "microscope head", + "manipulators", + "monitor" + ], + "references": [], + "size": [ + 1.5, + 1.8, + 1.5 + ], + "style": "Generic manual-semi-auto probe station, grey powdercoat, anodized platen, light wear, no branding.", + "summary": "Wafer probe station on isolation table with chuck, manipulators, microscope and lifting probe card holder.", + "tags": [ + "prop" + ], + "theme": "Fab", + "threshold": 0.85, + "tolerance": 0.08 +} \ No newline at end of file diff --git a/briefs/pvd-cluster.json b/briefs/pvd-cluster.json new file mode 100644 index 00000000..1cc3e9ed --- /dev/null +++ b/briefs/pvd-cluster.json @@ -0,0 +1,45 @@ +{ + "budget": 14000, + "id": "pvd-cluster", + "kind": "prop", + "materials": [ + "metal.stainless", + "metal.anodized", + "metal.powdercoat", + "metal.satin-aluminum", + "metal.brass", + "glass.lamp", + "glass.window-tint", + "plastic.matte", + "rubber.tubing" + ], + "name": "PVD cluster tool", + "notes": "Chamber lids lid-1..lid-4 articulated. Variant of ClusterTool.", + "parts": [ + "4 sputter chambers with hinged magnetron cathode lids", + "tall cryopump cylinders under chambers with gate valves", + "magnetron cathode housings, HV feedthroughs and water lines", + "central hexagonal vacuum transfer chamber with lid and viewport", + "2 load locks", + "EFEM front with 3 load ports and FOUPs", + "cable bundles", + "status tower", + "named chamber lids" + ], + "references": [], + "size": [ + 4, + 2.5, + 4 + ], + "style": "Generic PVD sputter cluster tool, aluminum chambers, magnetron cathode lids, tall cryopumps, white EFEM, light wear, no branding.", + "summary": "Hex cluster with 4 PVD chambers, magnetron cathode lids, cryopumps, load locks, EFEM.", + "tags": [ + "prop", + "semiconductor", + "articulated" + ], + "theme": "Fab", + "threshold": 0.85, + "tolerance": 0.08 +} \ No newline at end of file diff --git a/briefs/signoff/die-blank-reticle.json b/briefs/signoff/die-blank-reticle.json new file mode 100644 index 00000000..b5308642 --- /dev/null +++ b/briefs/signoff/die-blank-reticle.json @@ -0,0 +1,22 @@ +{ + "automated" : 0.9026721, + "date" : "2026-10-18", + "final" : 0.85949767, + "fingerprint" : "724a477d7649f65c", + "id" : "die-blank-reticle", + "iterations" : 2, + "judge" : "agent", + "scores" : { + "artifacts" : 9, + "construction" : 8, + "edges" : 8, + "grounding" : 9, + "materials" : 8, + "proportion" : 9, + "silhouette" : 9, + "wear" : 7 + }, + "seed" : 1, + "threshold" : 0.85, + "vision" : 0.83625 +} \ No newline at end of file diff --git a/briefs/signoff/hbm-stack.json b/briefs/signoff/hbm-stack.json new file mode 100644 index 00000000..98499a69 --- /dev/null +++ b/briefs/signoff/hbm-stack.json @@ -0,0 +1,22 @@ +{ + "automated" : 0.99549353, + "date" : "2026-10-18", + "final" : 0.86598516, + "fingerprint" : "b3553cbfcfd24d97", + "id" : "hbm-stack", + "iterations" : 2, + "judge" : "agent", + "scores" : { + "artifacts" : 9, + "construction" : 8, + "edges" : 8, + "grounding" : 9, + "materials" : 7, + "proportion" : 8, + "silhouette" : 9, + "wear" : 6 + }, + "seed" : 1, + "threshold" : 0.85, + "vision" : 0.79625 +} \ No newline at end of file diff --git a/briefs/signoff/holo-table.json b/briefs/signoff/holo-table.json new file mode 100644 index 00000000..1f9a7ec8 --- /dev/null +++ b/briefs/signoff/holo-table.json @@ -0,0 +1,22 @@ +{ + "automated" : 0.99992645, + "date" : "2026-10-18", + "final" : 0.86347425, + "fingerprint" : "242cec90c48356f4", + "id" : "holo-table", + "iterations" : 3, + "judge" : "agent", + "scores" : { + "artifacts" : 9, + "construction" : 8, + "edges" : 8, + "grounding" : 8, + "materials" : 8, + "proportion" : 9, + "silhouette" : 7, + "wear" : 6 + }, + "seed" : 1, + "threshold" : 0.85, + "vision" : 0.79 +} \ No newline at end of file diff --git a/briefs/solar-array-wing.json b/briefs/solar-array-wing.json new file mode 100644 index 00000000..2752270a --- /dev/null +++ b/briefs/solar-array-wing.json @@ -0,0 +1,35 @@ +{ + "budget": 4000, + "id": "solar-array-wing", + "kind": "prop", + "materials": [ + "solar.cell", + "solar.kapton", + "composite.carbon", + "metal.titanium" + ], + "name": "Solar array wing", + "notes": "", + "parts": [ + "deploy hinge panel", + "fold panels 2-10", + "cell grid", + "root spar", + "knuckles", + "tip lock bar" + ], + "references": [], + "size": [ + 30, + 0.1, + 6 + ], + "style": "Ultra realistic Z-fold rigid-panel solar array wing, ten carbon-tray panels with triple-junction cell grids, titanium knuckles, root drive spar.", + "summary": "Z-fold solar array wing: ten hinged cell-grid panels with carbon trays, edge frames, knuckles and a root drive spar.", + "tags": [ + "prop" + ], + "theme": "DataCenterSite", + "threshold": 0.85, + "tolerance": 0.08 +} diff --git a/briefs/space-radiator-panel.json b/briefs/space-radiator-panel.json new file mode 100644 index 00000000..699cfcdb --- /dev/null +++ b/briefs/space-radiator-panel.json @@ -0,0 +1,35 @@ +{ + "budget": 3000, + "id": "space-radiator-panel", + "kind": "prop", + "materials": [ + "thermal.radiator-white", + "thermal.radiator-glow", + "mli.blanket", + "metal.aluminum-brushed" + ], + "name": "Space radiator panel", + "notes": "", + "parts": [ + "deploy hinge panel", + "fold hinge panel", + "coolant tubes (glow option)", + "root bracket", + "MLI hoses and quick disconnects", + "launch lock bar" + ], + "references": [], + "size": [ + 10, + 0.1, + 4 + ], + "style": "Ultra realistic deployable spacecraft radiator, two honeycomb panels with white-coated tiles and pumped coolant tubes that glow hot, hinged root bracket.", + "summary": "Deployable two-panel spacecraft radiator: white-coated honeycomb tiles, coolant tubes that glow when hot, hinge brackets with MLI-wrapped hoses.", + "tags": [ + "prop" + ], + "theme": "DataCenterSite", + "threshold": 0.85, + "tolerance": 0.08 +} diff --git a/briefs/tool-status-tower.json b/briefs/tool-status-tower.json new file mode 100644 index 00000000..8070e658 --- /dev/null +++ b/briefs/tool-status-tower.json @@ -0,0 +1,34 @@ +{ + "budget": 600, + "id": "tool-status-tower", + "kind": "prop", + "materials": [ + "plastic.translucent", + "glass.lamp", + "metal.stainless", + "metal.anodized", + "plastic.matte" + ], + "name": "tool-status-tower", + "notes": "", + "parts": [ + "4 stacked lamps red amber green blue", + "buzzer cap", + "mounting bracket", + "lamp state knob" + ], + "references": [], + "size": [ + 0.05, + 0.4, + 0.05 + ], + "style": "Generic 4-tier signal tower, translucent lenses with ribbing, matte black bases, steel L bracket, no branding.", + "summary": "Four-lamp status tower with buzzer cap and mounting bracket.", + "tags": [ + "prop" + ], + "theme": "Fab", + "threshold": 0.85, + "tolerance": 0.08 +} \ No newline at end of file diff --git a/briefs/transistor-gaa-cutaway.json b/briefs/transistor-gaa-cutaway.json new file mode 100644 index 00000000..54e36b75 --- /dev/null +++ b/briefs/transistor-gaa-cutaway.json @@ -0,0 +1,43 @@ +{ + "budget": 8000, + "id": "transistor-gaa-cutaway", + "kind": "prop", + "materials": [ + "silicon.die", + "silicon.die-edge", + "oxide.hafnium", + "metal.tungsten", + "metal.copper", + "oxide.lowk" + ], + "name": "GAA nanosheet transistor cutaway", + "notes": "Micro-scale model, scaled 1e6x, 0.5 m display size. Layers are named explode parts.", + "parts": [ + "backside power rail", + "silicon substrate", + "three stacked silicon nanosheets", + "hafnium oxide gate dielectric", + "tungsten gate", + "inner and outer spacers", + "source and drain epi", + "contacts and M0 pads", + "layer explode state" + ], + "references": [], + "size": [ + 0.5, + 0.5, + 0.5 + ], + "style": "Gate-all-around nanosheet FET cutaway, front half of gate removed to show hafnium oxide and tungsten wrapping three silicon nanosheets, epi source/drain, spacers, contacts, backside power rail below.", + "summary": "GAA nanosheet FET cutaway at 1e6x scale: 3 nanosheets wrapped by HfO2 and tungsten gate, epi source/drain, spacers, contacts, backside power rail; explodes by layer.", + "tags": [ + "prop", + "chiplab", + "micro-scale", + "articulated" + ], + "theme": "ChipLab", + "threshold": 0.85, + "tolerance": 0.08 +} \ No newline at end of file diff --git a/briefs/wafer-300mm.json b/briefs/wafer-300mm.json new file mode 100644 index 00000000..7ac93e84 --- /dev/null +++ b/briefs/wafer-300mm.json @@ -0,0 +1,36 @@ +{ + "budget": 2000, + "id": "wafer-300mm", + "kind": "prop", + "materials": [ + "silicon.thinfilm", + "silicon.thinfilm-oxide", + "silicon.thinfilm-patterned", + "silicon.thinfilm-metal" + ], + "name": "300 mm silicon wafer", + "notes": "", + "parts": [ + "300 mm disk 775 um thick with bevelled edge", + "notch at the edge", + "laser-marked ID near notch", + "state param: bare, oxide, patterned, metal", + "die grid on the patterned state" + ], + "references": [], + "size": [ + 0.3, + 0.00078, + 0.3 + ], + "style": "Polished prime wafer, mirror finish with thin-film interference colour.", + "summary": "300 mm silicon wafer with notch, selectable process state.", + "tags": [ + "prop", + "semiconductor", + "handheld" + ], + "theme": "Fab", + "threshold": 0.9, + "tolerance": 0.08 +} \ No newline at end of file diff --git a/briefs/wafer-display-stand.json b/briefs/wafer-display-stand.json new file mode 100644 index 00000000..8c6479c9 --- /dev/null +++ b/briefs/wafer-display-stand.json @@ -0,0 +1,38 @@ +{ + "budget": 8500, + "id": "wafer-display-stand", + "kind": "prop", + "materials": [ + "metal.chrome", + "plastic.clear", + "silicon.thinfilm", + "metal.brushed", + "rubber" + ], + "name": "Wafer display stand", + "notes": "", + "parts": [ + "chrome round base", + "chrome stem", + "clear acrylic cradle with grooved rim", + "300 mm patterned wafer disc", + "iridescent die grid", + "wafer notch and flat edge", + "engraved plaque", + "rubber feet" + ], + "references": [], + "size": [ + 0.4, + 0.5, + 0.2 + ], + "style": "Desktop trophy stand 0.4 x 0.5 x 0.2 m: polished chrome weighted base, clear acrylic cradle holding a 300 mm patterned wafer disc tilted back, rainbow iridescent die grid, engraved brushed plaque on base.", + "summary": "Chrome base and clear acrylic cradle holding a 300 mm patterned wafer with iridescent die grid and an engraved plaque.", + "tags": [ + "prop" + ], + "theme": "ChipLab", + "threshold": 0.85, + "tolerance": 0.08 +} \ No newline at end of file diff --git a/briefs/wet-bench.json b/briefs/wet-bench.json new file mode 100644 index 00000000..c4fe3766 --- /dev/null +++ b/briefs/wet-bench.json @@ -0,0 +1,41 @@ +{ + "budget": 10000, + "id": "wet-bench", + "kind": "prop", + "materials": [ + "plastic.pvdf", + "glass.clear", + "metal.stainless", + "metal.powdercoat", + "plastic.matte", + "glass.lamp", + "rubber.tubing" + ], + "name": "Wet bench", + "notes": "Part sash slides along Y.", + "parts": [ + "white PVDF deck with recessed chemical baths", + "clear sash window on the front", + "exhaust plenum on top", + "DI rinse tanks", + "control panel", + "splash marks", + "named sash" + ], + "references": [], + "size": [ + 4, + 2.2, + 1.5 + ], + "style": "Semiconductor wet bench, white PVDF/polypropylene deck, recessed baths, clear sash, exhaust plenum, light chemical splash stains, no branding.", + "summary": "TODO: one sentence, what it is and how it's built.", + "tags": [ + "prop", + "semiconductor", + "articulated" + ], + "theme": "Fab", + "threshold": 0.85, + "tolerance": 0.08 +} \ No newline at end of file diff --git a/docs/assets/bond-wire-macro.png b/docs/assets/bond-wire-macro.png new file mode 100644 index 00000000..6869a8e5 Binary files /dev/null and b/docs/assets/bond-wire-macro.png differ diff --git a/docs/assets/chip-package-final.png b/docs/assets/chip-package-final.png new file mode 100644 index 00000000..ddda5ffb Binary files /dev/null and b/docs/assets/chip-package-final.png differ diff --git a/docs/assets/cleanroom-floor-grate.png b/docs/assets/cleanroom-floor-grate.png new file mode 100644 index 00000000..3168ede3 Binary files /dev/null and b/docs/assets/cleanroom-floor-grate.png differ diff --git a/docs/assets/cmp-tool.png b/docs/assets/cmp-tool.png new file mode 100644 index 00000000..aaa98a19 Binary files /dev/null and b/docs/assets/cmp-tool.png differ diff --git a/docs/assets/cvd-ald-cluster.png b/docs/assets/cvd-ald-cluster.png new file mode 100644 index 00000000..f8269673 Binary files /dev/null and b/docs/assets/cvd-ald-cluster.png differ diff --git a/docs/assets/design-workbench-2035.png b/docs/assets/design-workbench-2035.png new file mode 100644 index 00000000..8fca5caf Binary files /dev/null and b/docs/assets/design-workbench-2035.png differ diff --git a/docs/assets/dicing-saw.png b/docs/assets/dicing-saw.png new file mode 100644 index 00000000..6eb81e52 Binary files /dev/null and b/docs/assets/dicing-saw.png differ diff --git a/docs/assets/die-blank-reticle.png b/docs/assets/die-blank-reticle.png new file mode 100644 index 00000000..8b88a548 Binary files /dev/null and b/docs/assets/die-blank-reticle.png differ diff --git a/docs/assets/die-block-tile.png b/docs/assets/die-block-tile.png new file mode 100644 index 00000000..35cae67d Binary files /dev/null and b/docs/assets/die-block-tile.png differ diff --git a/docs/assets/diffusion-furnace.png b/docs/assets/diffusion-furnace.png new file mode 100644 index 00000000..eed44f8e Binary files /dev/null and b/docs/assets/diffusion-furnace.png differ diff --git a/docs/assets/duv-immersion-scanner.png b/docs/assets/duv-immersion-scanner.png new file mode 100644 index 00000000..525a5bf9 Binary files /dev/null and b/docs/assets/duv-immersion-scanner.png differ diff --git a/docs/assets/earth-site-pin.png b/docs/assets/earth-site-pin.png new file mode 100644 index 00000000..9a549ea3 Binary files /dev/null and b/docs/assets/earth-site-pin.png differ diff --git a/docs/assets/ebeam-metrology.png b/docs/assets/ebeam-metrology.png new file mode 100644 index 00000000..2dbffe7e Binary files /dev/null and b/docs/assets/ebeam-metrology.png differ diff --git a/docs/assets/efem-robot.png b/docs/assets/efem-robot.png new file mode 100644 index 00000000..2b9e615d Binary files /dev/null and b/docs/assets/efem-robot.png differ diff --git a/docs/assets/euv-light-source.png b/docs/assets/euv-light-source.png new file mode 100644 index 00000000..2ec67a55 Binary files /dev/null and b/docs/assets/euv-light-source.png differ diff --git a/docs/assets/euv-scanner-highna.png b/docs/assets/euv-scanner-highna.png new file mode 100644 index 00000000..2806e70d Binary files /dev/null and b/docs/assets/euv-scanner-highna.png differ diff --git a/docs/assets/fab-yellow-light-panel.png b/docs/assets/fab-yellow-light-panel.png new file mode 100644 index 00000000..ddf4b4f9 Binary files /dev/null and b/docs/assets/fab-yellow-light-panel.png differ diff --git a/docs/assets/ffu-ceiling-tile.png b/docs/assets/ffu-ceiling-tile.png new file mode 100644 index 00000000..1eb535b8 Binary files /dev/null and b/docs/assets/ffu-ceiling-tile.png differ diff --git a/docs/assets/foup-stocker.png b/docs/assets/foup-stocker.png new file mode 100644 index 00000000..16706965 Binary files /dev/null and b/docs/assets/foup-stocker.png differ diff --git a/docs/assets/foup.png b/docs/assets/foup.png new file mode 100644 index 00000000..018220d5 Binary files /dev/null and b/docs/assets/foup.png differ diff --git a/docs/assets/ground-station-dish.png b/docs/assets/ground-station-dish.png new file mode 100644 index 00000000..94bb465a Binary files /dev/null and b/docs/assets/ground-station-dish.png differ diff --git a/docs/assets/hbm-stack.png b/docs/assets/hbm-stack.png new file mode 100644 index 00000000..2437fbaf Binary files /dev/null and b/docs/assets/hbm-stack.png differ diff --git a/docs/assets/holo-table.png b/docs/assets/holo-table.png new file mode 100644 index 00000000..f21f2135 Binary files /dev/null and b/docs/assets/holo-table.png differ diff --git a/docs/assets/hybrid-bonder.png b/docs/assets/hybrid-bonder.png new file mode 100644 index 00000000..b244e9d4 Binary files /dev/null and b/docs/assets/hybrid-bonder.png differ diff --git a/docs/assets/interconnect-stack-cutaway.png b/docs/assets/interconnect-stack-cutaway.png new file mode 100644 index 00000000..fa0afd99 Binary files /dev/null and b/docs/assets/interconnect-stack-cutaway.png differ diff --git a/docs/assets/interposer-glass.png b/docs/assets/interposer-glass.png new file mode 100644 index 00000000..12fce92a Binary files /dev/null and b/docs/assets/interposer-glass.png differ diff --git a/docs/assets/ion-implanter.png b/docs/assets/ion-implanter.png new file mode 100644 index 00000000..c2c83810 Binary files /dev/null and b/docs/assets/ion-implanter.png differ diff --git a/docs/assets/litho-track.png b/docs/assets/litho-track.png new file mode 100644 index 00000000..d961adb8 Binary files /dev/null and b/docs/assets/litho-track.png differ diff --git a/docs/assets/lunar-dc-habitat.png b/docs/assets/lunar-dc-habitat.png new file mode 100644 index 00000000..f5b63baa Binary files /dev/null and b/docs/assets/lunar-dc-habitat.png differ diff --git a/docs/assets/mars-dc-dome.png b/docs/assets/mars-dc-dome.png new file mode 100644 index 00000000..babe34aa Binary files /dev/null and b/docs/assets/mars-dc-dome.png differ diff --git a/docs/assets/oht-rail.png b/docs/assets/oht-rail.png new file mode 100644 index 00000000..65dcad59 Binary files /dev/null and b/docs/assets/oht-rail.png differ diff --git a/docs/assets/oht-vehicle.png b/docs/assets/oht-vehicle.png new file mode 100644 index 00000000..4423003a Binary files /dev/null and b/docs/assets/oht-vehicle.png differ diff --git a/docs/assets/orbital-dc-module.png b/docs/assets/orbital-dc-module.png new file mode 100644 index 00000000..bd1175e6 Binary files /dev/null and b/docs/assets/orbital-dc-module.png differ diff --git a/docs/assets/package-substrate.png b/docs/assets/package-substrate.png new file mode 100644 index 00000000..2cc6f4dd Binary files /dev/null and b/docs/assets/package-substrate.png differ diff --git a/docs/assets/plasma-etch-cluster.png b/docs/assets/plasma-etch-cluster.png new file mode 100644 index 00000000..cebda36d Binary files /dev/null and b/docs/assets/plasma-etch-cluster.png differ diff --git a/docs/assets/probe-station.png b/docs/assets/probe-station.png new file mode 100644 index 00000000..76971c21 Binary files /dev/null and b/docs/assets/probe-station.png differ diff --git a/docs/assets/pvd-cluster.png b/docs/assets/pvd-cluster.png new file mode 100644 index 00000000..25830ef3 Binary files /dev/null and b/docs/assets/pvd-cluster.png differ diff --git a/docs/assets/solar-array-wing.png b/docs/assets/solar-array-wing.png new file mode 100644 index 00000000..e4ec7d59 Binary files /dev/null and b/docs/assets/solar-array-wing.png differ diff --git a/docs/assets/space-radiator-panel.png b/docs/assets/space-radiator-panel.png new file mode 100644 index 00000000..63d6d6b8 Binary files /dev/null and b/docs/assets/space-radiator-panel.png differ diff --git a/docs/assets/tool-status-tower.png b/docs/assets/tool-status-tower.png new file mode 100644 index 00000000..1ce1f7cc Binary files /dev/null and b/docs/assets/tool-status-tower.png differ diff --git a/docs/assets/transistor-gaa-cutaway.png b/docs/assets/transistor-gaa-cutaway.png new file mode 100644 index 00000000..1a606916 Binary files /dev/null and b/docs/assets/transistor-gaa-cutaway.png differ diff --git a/docs/assets/wafer-300mm.png b/docs/assets/wafer-300mm.png new file mode 100644 index 00000000..7cb0d5dc Binary files /dev/null and b/docs/assets/wafer-300mm.png differ diff --git a/docs/assets/wafer-display-stand.png b/docs/assets/wafer-display-stand.png new file mode 100644 index 00000000..dd74e527 Binary files /dev/null and b/docs/assets/wafer-display-stand.png differ diff --git a/docs/assets/wet-bench.png b/docs/assets/wet-bench.png new file mode 100644 index 00000000..586f17f4 Binary files /dev/null and b/docs/assets/wet-bench.png differ