Download variants.js from FormaLau/microduck-reachy-simulator: direct link, hf CLI and curl.
- Browser
- Download file 8.78 kB
-
https://huggingface.co/spaces/FormaLau/microduck-reachy-simulator/resolve/c8f39b4b30bfdc51319d824b82d4c0a41742a7fa/variants.js
- Command line
-
hf download hf://spaces/FormaLau/microduck-reachy-simulator@c8f39b4b30bfdc51319d824b82d4c0a41742a7fa/variants.js
-
curl -L -o variants.js https://huggingface.co/spaces/FormaLau/microduck-reachy-simulator/resolve/c8f39b4b30bfdc51319d824b82d4c0a41742a7fa/variants.js
8.78 kB
| // Colour variants of the real Microduck robots (see the reference photo of | |
| // the four printed units). Each variant fills the same semantic slots; the | |
| // mesh-filename -> slot mapping lives in meshMaterialsFor(). All colours are | |
| // linear-space RGB and slightly oversaturated: the renderer tone-maps with | |
| // ACESFilmicToneMapping under warm key/rim lights, which desaturates and | |
| // shifts hues toward gold, so the base colours lean past the target to land | |
| // right on screen. | |
| import * as THREE from "three"; | |
| // ββ Shared slot specs (identical on all four robots) βββββββββββββββββββ | |
| // sRGB targets from the reference photo, converted to linear with a | |
| // slight saturation boost (x1.12) so ACES lands on the clean tint. | |
| const AMBER_YELLOW = { color: [1.0, 0.413, 0.007], roughness: 0.4, metalness: 0.0 }; // #ffb52e | |
| const BRIGHT_ORANGE = { color: [1.0, 0.144, 0.008], roughness: 0.45, metalness: 0.0 }; // #ff7a2f | |
| const AMBER = { color: [0.847, 0.339, 0.022], roughness: 0.45, metalness: 0.0 }; // #eda63e | |
| const CLEAN_YELLOW = { color: [1.0, 0.608, 0.021], roughness: 0.4, metalness: 0.0 }; // #ffd23f | |
| const CREAM = { color: [0.888, 0.86, 0.798], roughness: 0.35, metalness: 0.0 }; // #f2efe8 | |
| const DARK = { color: [0.012, 0.012, 0.014], roughness: 0.55, metalness: 0.3 }; // #1d1d1f | |
| const GRAY = { color: [0.256, 0.256, 0.279], roughness: 0.5, metalness: 0.35 }; // #8b8b90 | |
| // Camera-lens eye: very dark blue-black, glossy like coated glass. | |
| const LENS = { color: [0.01, 0.012, 0.02], roughness: 0.05, metalness: 0.0 }; | |
| // Per-variant colours. | |
| const WARM_GRAY = { color: [0.328, 0.312, 0.283], roughness: 0.35, metalness: 0.0 }; // #9b9892 | |
| const AMBER_ORANGE = { color: [0.815, 0.321, 0.019], roughness: 0.5, metalness: 0.0 }; // #e9a23b | |
| const CHARCOAL = { color: [0.028, 0.028, 0.033], roughness: 0.5, metalness: 0.0 }; // #2f2f33 | |
| const CHARCOAL_FACE = { color: [0.044, 0.044, 0.051], roughness: 0.5, metalness: 0.0 }; // #3c3c40 | |
| const CHARCOAL_BODY = { color: [0.017, 0.017, 0.019], roughness: 0.5, metalness: 0.0 }; // #232326 | |
| const LIGHT_WARM_GRAY = { color: [0.485, 0.459, 0.416], roughness: 0.35, metalness: 0.0 };// #b9b5ae | |
| const PURPLE_RING = { color: [0.114, 0.058, 0.578], roughness: 0.4, metalness: 0.0 }; // #6a52c8 | |
| const PURPLE_FEET = { color: [0.164, 0.085, 0.584], roughness: 0.45, metalness: 0.0 }; // #7a5fc9 | |
| const PALE_BLUE = { color: [0.441, 0.613, 0.723], roughness: 0.35, metalness: 0.0 }; // #b6cfdd | |
| const MEDIUM_BLUE = { color: [0.13, 0.321, 0.552], roughness: 0.35, metalness: 0.0 }; // #6f9ec4 | |
| const BLUE_GRAY = { color: [0.183, 0.379, 0.565], roughness: 0.35, metalness: 0.0 }; // #7fa9c6 | |
| const FEET_YELLOW = { color: [0.784, 0.455, 0.023], roughness: 0.5, metalness: 0.0 }; // #e5b93e | |
| // Slots (validated against the four-robot reference photo): headDome | |
| // (top shell), facePlate (around the eye), trim (band under the shell + | |
| // upper beak base), beakUpper (mouth-roof plate), beakLower (jaw plates), | |
| // eyeRing, lens, bodyShell (trunk shells), legShells (upper-leg shells), | |
| // feet (foot blocks + ankle brackets), soles, mechDark, mechGray. | |
| export const VARIANTS = { | |
| // Front-center robot: warm gray head, cream body, amber eye, orange | |
| // trim/upper beak over a lighter amber lower beak, amber-orange feet. | |
| classic: { | |
| headDome: WARM_GRAY, | |
| facePlate: WARM_GRAY, | |
| trim: BRIGHT_ORANGE, | |
| beakUpper: BRIGHT_ORANGE, | |
| beakLower: AMBER, | |
| eyeRing: AMBER_YELLOW, | |
| lens: LENS, | |
| bodyShell: CREAM, | |
| legShells: CREAM, | |
| feet: AMBER_ORANGE, | |
| soles: AMBER_ORANGE, | |
| mechDark: DARK, | |
| mechGray: GRAY, | |
| }, | |
| // Front-left robot: charcoal shells, slightly lighter face plate, | |
| // amber eye, orange/amber beak, yellow soles peeking under black feet. | |
| charcoal: { | |
| headDome: CHARCOAL, | |
| facePlate: CHARCOAL_FACE, | |
| trim: BRIGHT_ORANGE, | |
| beakUpper: BRIGHT_ORANGE, | |
| beakLower: AMBER, | |
| eyeRing: AMBER_YELLOW, | |
| lens: LENS, | |
| bodyShell: CHARCOAL_BODY, | |
| legShells: CHARCOAL_BODY, | |
| feet: CHARCOAL_BODY, | |
| soles: CLEAN_YELLOW, | |
| mechDark: DARK, | |
| mechGray: GRAY, | |
| }, | |
| // Back-center robot: light warm gray head, purple eye, YELLOW trim and | |
| // beak (both plates), cream body, purple feet. | |
| purple: { | |
| headDome: LIGHT_WARM_GRAY, | |
| facePlate: LIGHT_WARM_GRAY, | |
| trim: CLEAN_YELLOW, | |
| beakUpper: CLEAN_YELLOW, | |
| beakLower: CLEAN_YELLOW, | |
| eyeRing: PURPLE_RING, | |
| lens: LENS, | |
| bodyShell: CREAM, | |
| legShells: CREAM, | |
| feet: PURPLE_FEET, | |
| soles: PURPLE_FEET, | |
| mechDark: DARK, | |
| mechGray: GRAY, | |
| }, | |
| // Right robot: pale blue dome over a medium blue face plate (two | |
| // different blues), amber eye, orange/amber beak, blue-gray body, | |
| // clean yellow feet. | |
| blue: { | |
| headDome: PALE_BLUE, | |
| facePlate: MEDIUM_BLUE, | |
| trim: BRIGHT_ORANGE, | |
| beakUpper: BRIGHT_ORANGE, | |
| beakLower: AMBER, | |
| eyeRing: AMBER_YELLOW, | |
| lens: LENS, | |
| bodyShell: BLUE_GRAY, | |
| legShells: BLUE_GRAY, | |
| feet: FEET_YELLOW, | |
| soles: FEET_YELLOW, | |
| mechDark: DARK, | |
| mechGray: GRAY, | |
| }, | |
| }; | |
| export const VARIANT_NAMES = Object.keys(VARIANTS); | |
| export const DEFAULT_VARIANT = "classic"; | |
| export const randomVariantName = () => | |
| VARIANT_NAMES[Math.floor(Math.random() * VARIANT_NAMES.length)]; | |
| // Mesh filename -> material spec for one variant (mjlab model meshes). | |
| // Anything not listed is small mechanical hardware and falls back to | |
| // mechGray. Verified visually against the four-robot reference photo. | |
| export function meshMaterialsFor(v) { | |
| return { | |
| // Head: dome, band under it, face plate around the eye. | |
| "top_head_shell.stl": v.headDome, | |
| "bottom_head_shell.stl": v.trim, | |
| "face_part.stl": v.facePlate, | |
| // The eye: printed ring + camera lens behind it. | |
| "noenoeil.stl": v.eyeRing, | |
| "lens.stl": v.lens, | |
| "m12_lens_holder.stl": v.mechDark, | |
| // Beak: mouth-roof plate on top, rigid jaw + soft pad below. | |
| "soft_mouth_top.stl": v.beakUpper, | |
| "jaw.stl": v.beakLower, | |
| "jaw_soft.stl": v.beakLower, | |
| // Body shells | |
| "trunk_base.stl": v.bodyShell, | |
| "left_shell.stl": v.bodyShell, | |
| "right_shell.stl": v.bodyShell, | |
| "upper_leg_left.stl": v.legShells, | |
| "upper_leg_right.stl": v.legShells, | |
| "hip_l.stl": v.trim, | |
| // Feet: foot block + ankle bracket, soft sole pad below. | |
| "foot_left.stl": v.feet, | |
| "foot_right.stl": v.feet, | |
| "ankle_left.stl": v.feet, | |
| "ankle_right.stl": v.feet, | |
| "sole_left.stl": v.soles, | |
| "sole_right.stl": v.soles, | |
| // Dark mechanics | |
| "xl330.stl": v.mechDark, | |
| "neck.stl": v.mechDark, | |
| "np_f970.stl": v.mechDark, | |
| "pcb__raspberry_pi_zero_2_w.stl": v.mechDark, | |
| "elec_rpi_robot_hat_pcb.stl": v.mechDark, | |
| "banana_pcb_locker.stl": v.mechDark, | |
| "speaker.stl": v.mechDark, | |
| // Gray mechanics | |
| "upper_leg_rigidity_plate.stl": v.mechGray, | |
| "leg.stl": v.mechGray, | |
| "yaw2roll.stl": v.mechGray, | |
| "yaw_roll_motion.stl": v.mechGray, | |
| "neck_pitch.stl": v.mechGray, | |
| "bearing_roll.stl": v.mechGray, | |
| "motor_support.stl": v.mechGray, | |
| "power_support.stl": v.mechGray, | |
| "seeed_bearing__configuration__22x16x4.stl": v.mechGray, | |
| "seeed_bearing__configuration_default.stl": v.mechGray, | |
| }; | |
| } | |
| // buildRig materialForMesh hook for one variant. | |
| export const materialHookFor = (v) => { | |
| const map = meshMaterialsFor(v); | |
| return (mesh) => map[mesh] ?? v.mechGray; | |
| }; | |
| // ββ Live re-skin ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // Swap materials on an already-built rig without reloading any STL. | |
| // Meshes are identified via userData.meshName (set by duck.js buildRig and | |
| // preserved by cloneRig). Materials are cached per resolved spec so clones | |
| // and repeated switches share GPU material instances. | |
| const matCache = new Map(); | |
| function matFor(spec) { | |
| const key = `${spec.color.join(",")}|${spec.roughness ?? 0.5}|${spec.metalness ?? 0}`; | |
| let m = matCache.get(key); | |
| if (!m) { | |
| m = new THREE.MeshStandardMaterial({ | |
| color: new THREE.Color(...spec.color), | |
| roughness: spec.roughness ?? 0.5, | |
| metalness: spec.metalness ?? 0.0, | |
| }); | |
| matCache.set(key, m); | |
| } | |
| return m; | |
| } | |
| export function applyVariant(rig, variant) { | |
| const v = typeof variant === "string" ? VARIANTS[variant] : variant; | |
| const map = meshMaterialsFor(v); | |
| rig.root.traverse((o) => { | |
| if (!o.isMesh || !o.userData.meshName) return; | |
| o.material = matFor(map[o.userData.meshName] ?? v.mechGray); | |
| }); | |
| } | |
| // Linear-space spec colour -> sRGB CSS hex, for swatch UI elements. | |
| export function specToHex(spec) { | |
| return `#${new THREE.Color(...spec.color).getHexString()}`; | |
| } | |