{"type":"typescript","version":"1.5.0-rc.0","entry":"main.ts","files":{"main.ts":"import { fitPropeller, sizes } from \"./sizes\";\r\nimport { setStage } from \"./stage\";\r\nimport { draw, makeLooks } from \"./materials\";\r\nimport { framePlate } from \"./parts/frame\";\r\nimport { standoffs } from \"./parts/standoffs\";\r\nimport { armBody } from \"./parts/arm/body\";\r\nimport { motor } from \"./parts/arm/motor\";\r\nimport { propeller } from \"./parts/arm/propeller\";\r\nimport { armBolts, light } from \"./parts/arm/fittings\";\r\nimport { around } from \"./parts/around\";\r\nimport { alternateSpin } from \"./parts/spin\";\r\nimport { bySide } from \"./parts/lights\";\r\nimport { canopy, honeycomb } from \"./parts/canopy\";\r\nimport { hatchWithLogo } from \"./parts/hatch\";\r\nimport { antennas, gps } from \"./parts/electronics\";\r\nimport { damper, foot, leg, legBolts, spring } from \"./parts/landing-gear\";\r\nimport { battery, camera, gimbal, straps } from \"./parts/payload\";\r\n\r\nconst diameter = fitPropeller(sizes.arms, sizes.armLength, sizes.propeller);\r\nconsole.log(\"Propeller diameter:\", diameter);\r\n\r\nconst looks = makeLooks();\r\nsetStage();\r\n\r\nconst plate = await framePlate(sizes.legs);\r\nconst topPlate = await bitbybit.occt.transforms.translate({ shape: plate, translation: [0, 13, 0] });\r\nawait draw(looks.carbon, [plate, topPlate]);\r\n\r\nconst posts = await standoffs(sizes.arms);\r\nawait draw(looks.accent, posts.posts);\r\nawait draw(looks.steel, posts.bolts);\r\n\r\nconst arm = await armBody(sizes.armLength);\r\nawait draw(looks.carbon, await around(arm, sizes.arms));\r\n\r\nconst motorParts = await motor(sizes.armLength);\r\nawait draw(looks.aluminium, await around(motorParts.motor, sizes.arms));\r\nawait draw(looks.copper, await around(motorParts.stator, sizes.arms));\r\n\r\nconst blades = await propeller(sizes.armLength, diameter);\r\nawait draw(looks.accent, await alternateSpin(await around(blades, sizes.arms)));\r\n\r\nconst bolts = await armBolts(sizes.armLength);\r\nawait draw(looks.steel, await around(bolts, sizes.arms));\r\nconst lamp = await light(sizes.armLength);\r\nconst lights = await bySide(await around(lamp, sizes.arms));\r\nawait draw(looks.green, lights.front);\r\nawait draw(looks.red, lights.back);\r\n\r\nconst shell = await canopy();\r\nawait draw(looks.paint, await bitbybit.occt.transforms.translate({ shape: shell, translation: [0, 16, 0] }));\r\n\r\nconst cells = await honeycomb(shell);\r\nawait draw(looks.honeycomb, await bitbybit.occt.transforms.translate({ shape: cells, translation: [0, 16, 0] }));\r\n\r\nconst lid = await hatchWithLogo();\r\nawait draw(looks.paint, lid.hatch);\r\nawait draw(looks.emblem, lid.emblem);\r\n\r\nawait draw(looks.polymer, await gps());\r\nawait draw(looks.rubber, await antennas());\r\n\r\nawait draw(looks.carbon, await around(await leg(), sizes.legs));\r\n\r\nawait draw(looks.aluminium, await around(await damper(), sizes.legs));\r\nawait draw(looks.steel, await around(await spring(), sizes.legs));\r\n\r\nawait draw(looks.rubber, await around(await foot(), sizes.legs));\r\nawait draw(looks.steel, await around(await legBolts(), sizes.legs));\r\n\r\nconst view = await camera();\r\nawait draw(looks.aluminium, await gimbal());\r\nawait draw(looks.polymer, [view.camera, await battery()]);\r\nawait draw(looks.glass, view.lens);\r\nawait draw(looks.accent, await straps());","materials.ts":"type Shape = Bit.Inputs.OCCT.TopoDSShapePointer;\n\ninterface Finish {\n    readonly colour: string;\n    readonly metallic: number;\n    readonly roughness: number;\n    readonly edgeWidth?: number;\n    readonly emissive?: string;\n}\n\nconst FINISHES = {\n    carbon: { colour: \"#23262b\", metallic: 0.35, roughness: 0.45, edgeWidth: 6 },\n    paint: { colour: \"#1f2b4d\", metallic: 0.1, roughness: 0.25, edgeWidth: 6 },\n    honeycomb: { colour: \"#1f2b4d\", metallic: 0.1, roughness: 0.25, edgeWidth: 8 },\n    accent: { colour: \"#f0cebb\", metallic: 0.85, roughness: 0.35, edgeWidth: 4 },\n    aluminium: { colour: \"#b9bec6\", metallic: 1, roughness: 0.32, edgeWidth: 4 },\n    copper: { colour: \"#b87333\", metallic: 1, roughness: 0.5, edgeWidth: 4 },\n    steel: { colour: \"#1c1c1e\", metallic: 0.9, roughness: 0.42, edgeWidth: 4 },\n    polymer: { colour: \"#2a2d31\", metallic: 0.05, roughness: 0.4, edgeWidth: 6 },\n    rubber: { colour: \"#111214\", metallic: 0, roughness: 0.92, edgeWidth: 4 },\n    glass: { colour: \"#0c1522\", metallic: 0.1, roughness: 0.05, edgeWidth: 4 },\n    emblem: { colour: \"#f4f4f4\", metallic: 0.2, roughness: 0.5, edgeWidth: 4 },\n    green: { colour: \"#22ff66\", metallic: 0, roughness: 0.2, emissive: \"#22ff66\" },\n    red: { colour: \"#ff2a2a\", metallic: 0, roughness: 0.2, emissive: \"#ff2a2a\" },\n} satisfies Record<string, Finish>;\n\ntype Look = keyof typeof FINISHES;\n\nfunction isDark(hex: string): boolean {\n    const value = parseInt(hex.slice(1), 16);\n    const channel = (shift: number) => ((value >> shift) & 255) / 255;\n    return 0.2126 * channel(16) + 0.7152 * channel(8) + 0.0722 * channel(0) < 0.3;\n}\n\nexport function makeLooks() {\n    const looks = {} as Record<Look, Bit.Inputs.Draw.DrawOcctShapeOptions>;\n    for (const [name, finish] of Object.entries(FINISHES) as [Look, Finish][]) {\n        const material = bitbybit.babylon.material.pbrMetallicRoughness.create({\n            name,\n            baseColor: finish.colour,\n            emissiveColor: finish.emissive ?? \"#000000\",\n            metallic: finish.metallic,\n            roughness: finish.roughness,\n            alpha: 1,\n            backFaceCulling: false,\n            zOffset: 2,\n        });\n        const edges = finish.edgeWidth === undefined\n            ? { drawEdges: false }\n            : { edgeColour: isDark(finish.colour) ? \"#f2f2f2\" : \"#0d0e10\", edgeWidth: finish.edgeWidth };\n        looks[name] = bitbybit.draw.optionsOcctShape({ faceMaterial: material, precision: 0.05, drawTwoSided: false, ...edges });\n    }\n    return looks;\n}\n\nexport function draw(look: Bit.Inputs.Draw.DrawOcctShapeOptions, shapes: Shape | Shape[]) {\n    return bitbybit.draw.drawAnyAsync({ entity: shapes, options: look });\n}","parts/arm/body.ts":"export async function armBody(length: number) {\n    const tip = length - 12;\n    const outline = await bitbybit.occt.shapes.wire.createPolygonWire({ points: [[28, 0, -13], [tip, 0, -8], [tip, 0, 8], [28, 0, 13]] });\n    const rounded = await bitbybit.occt.fillets.fillet2d({ shape: outline, radius: 4 });\n    const face = await bitbybit.occt.shapes.face.createFaceFromWire({ shape: rounded, planar: true });\n    const slab = await bitbybit.occt.operations.extrude({ shape: face, direction: [0, 10, 0] });\n    const onPlate = await bitbybit.occt.transforms.translate({ shape: slab, translation: [0, 3, 0] });\n    const pad = await bitbybit.occt.shapes.solid.createCylinder({ radius: 15, height: 10, center: [length, 3, 0], direction: [0, 1, 0] });\n    const arm = await bitbybit.occt.booleans.union({ shapes: [onPlate, pad], keepEdges: false });\n    return bitbybit.occt.booleans.difference({ shape: arm, shapes: await cutouts(length), keepEdges: false });\n}\n\nasync function cutouts(length: number) {\n    const tip = length - 12;\n    const holes: Bit.Inputs.OCCT.TopoDSShapePointer[] = [];\n    for (let x = 62; x < length - 34; x += 24) {\n        const half = 13 + (8 - 13) * (x - 28) / (tip - 28);\n        const hexagon = await bitbybit.occt.shapes.wire.createNGonWire({ nrCorners: 6, radius: Math.max(half - 3.5, 2.5), center: [x, 3, 0], direction: [0, 1, 0] });\n        const rounded = await bitbybit.occt.fillets.fillet2d({ shape: hexagon, radius: 1.2 });\n        const face = await bitbybit.occt.shapes.face.createFaceFromWire({ shape: rounded, planar: true });\n        holes.push(await bitbybit.occt.operations.extrude({ shape: face, direction: [0, 10, 0] }));\n    }\n    return holes;\n}","parts/arm/fittings.ts":"import { m3Bolt } from \"../bolt\";\n\nexport async function armBolts(length: number) {\n    const clamp = await bitbybit.occt.transforms.rotate({ shape: await m3Bolt(16), axis: [1, 0, 0], angle: 180 });\n    const motorBolt = await m3Bolt(12);\n    const placed: Bit.Inputs.OCCT.TopoDSShapePointer[] = [];\n    for (const x of [34, 44]) {\n        placed.push(await bitbybit.occt.transforms.translate({ shape: clamp, translation: [x, 16, 0] }));\n    }\n    for (const angle of [45, 135, 225, 315]) {\n        const a = angle * Math.PI / 180;\n        placed.push(await bitbybit.occt.transforms.translate({ shape: motorBolt, translation: [length + 8 * Math.cos(a), 3, 8 * Math.sin(a)] }));\n    }\n    return bitbybit.occt.shapes.compound.makeCompound({ shapes: placed });\n}\n\nexport async function light(length: number) {\n    const dome = await bitbybit.occt.shapes.solid.createSphere({ radius: 3, center: [length - 24, 3, 0] });\n    const above = await bitbybit.occt.shapes.solid.createBox({ width: 8, length: 8, height: 4, center: [length - 24, 5, 0] });\n    return bitbybit.occt.booleans.difference({ shape: dome, shapes: [above], keepEdges: false });\n}","parts/arm/motor.ts":"async function revolved(points: Bit.Inputs.Base.Point3[]) {\n    const outline = await bitbybit.occt.shapes.wire.createPolygonWire({ points });\n    const face = await bitbybit.occt.shapes.face.createFaceFromWire({ shape: outline, planar: true });\n    return bitbybit.occt.operations.revolve({ shape: face, angle: 360, direction: [0, 1, 0], copy: false });\n}\n\nexport async function motor(length: number) {\n    const body = await revolved([[0, 0, 0], [13, 0, 0], [13, 3, 0], [8, 3, 0], [8, 6, 0], [14, 6, 0], [14, 19, 0], [12.8, 20, 0], [6, 20, 0], [6, 22, 0], [2.5, 22, 0], [2.5, 28, 0], [0, 28, 0]]);\n    const cavity = await bitbybit.occt.shapes.solid.createCylinder({ radius: 12.4, height: 12, center: [0, 6, 0], direction: [0, 1, 0] });\n    const bearing = await bitbybit.occt.shapes.solid.createCylinder({ radius: 4, height: 14, center: [0, 5, 0], direction: [0, 1, 0] });\n    const cuts = [await bitbybit.occt.booleans.difference({ shape: cavity, shapes: [bearing], keepEdges: false })];\n    for (let i = 0; i < 8; i++) {\n        const slot = await bitbybit.occt.shapes.solid.createBox({ width: 2.2, length: 8, height: 9, center: [0, 13, 12] });\n        cuts.push(await bitbybit.occt.transforms.rotate({ shape: slot, axis: [0, 1, 0], angle: i * 45 }));\n    }\n    const housing = await bitbybit.occt.booleans.difference({ shape: body, shapes: cuts, keepEdges: false });\n    const hexagon = await bitbybit.occt.shapes.wire.createNGonWire({ nrCorners: 6, radius: 3.6, center: [0, 34, 0], direction: [0, 1, 0] });\n    const hexFace = await bitbybit.occt.shapes.face.createFaceFromWire({ shape: hexagon, planar: true });\n    const hex = await bitbybit.occt.operations.extrude({ shape: hexFace, direction: [0, 3.5, 0] });\n    const chamfer = await revolved([[0, 34, 0], [2.9, 34, 0], [3.7, 34.7, 0], [3.7, 36.8, 0], [2.9, 37.5, 0], [0, 37.5, 0]]);\n    const nut = await bitbybit.occt.booleans.intersection({ shapes: [hex, chamfer], keepEdges: false });\n    const whole = await bitbybit.occt.shapes.compound.makeCompound({ shapes: [housing, nut] });\n    const stator = await bitbybit.occt.shapes.solid.createCylinder({ radius: 11.8, height: 11, center: [0, 6.5, 0], direction: [0, 1, 0] });\n    return {\n        motor: await bitbybit.occt.transforms.translate({ shape: whole, translation: [length, 13, 0] }),\n        stator: await bitbybit.occt.transforms.translate({ shape: stator, translation: [length, 13, 0] }),\n    };\n}","parts/arm/propeller.ts":"async function blade(diameter: number) {\n    const stations = [[0, 12, 3.2, 38, 0], [0.25, 17, 2.4, 24, 0.6], [0.5, 15.5, 1.8, 16, 1.8], [0.78, 11, 1.4, 11, 3.6], [1, 4.5, 1, 8, 6]];\n    const root = 6;\n    const span = diameter / 2 - root;\n    const sections: Bit.Inputs.OCCT.TopoDSShapePointer[] = [];\n    for (const [t, chord, thickness, twist, sweep] of stations) {\n        const r = root + span * t;\n        const ellipse = await bitbybit.occt.shapes.wire.createEllipseWire({ center: [r, 0, sweep], direction: [1, 0, 0], radiusMinor: thickness / 2, radiusMajor: chord / 2 });\n        sections.push(await bitbybit.occt.transforms.rotateAroundCenter({ shape: ellipse, angle: twist, center: [r, 0, sweep], axis: [1, 0, 0] }));\n    }\n    return bitbybit.occt.operations.loft({ shapes: sections, makeSolid: true });\n}\n\nexport async function propeller(length: number, diameter: number) {\n    const first = await blade(diameter);\n    const second = await bitbybit.occt.transforms.rotate({ shape: first, axis: [0, 1, 0], angle: 180 });\n    const outline = await bitbybit.occt.shapes.wire.createPolygonWire({ points: [[0, -5, 0], [8, -5, 0], [8, 4.5, 0], [6.5, 6.2, 0], [4.5, 7, 0], [0, 7, 0]] });\n    const face = await bitbybit.occt.shapes.face.createFaceFromWire({ shape: outline, planar: true });\n    const hub = await bitbybit.occt.operations.revolve({ shape: face, angle: 360, direction: [0, 1, 0], copy: false });\n    const whole = await bitbybit.occt.booleans.union({ shapes: [hub, first, second], keepEdges: false });\n    return bitbybit.occt.transforms.translate({ shape: whole, translation: [length, 40, 0] });\n}","parts/around.ts":"export function around(shape: Bit.Inputs.OCCT.TopoDSShapePointer, count: number) {\n    const angles = Array.from({ length: count }, (_, i) => (i + 0.5) * 360 / count);\n    return bitbybit.occt.transforms.rotateShapes({\n        shapes: angles.map(() => shape),\n        axes: angles.map((): Bit.Inputs.Base.Vector3 => [0, 1, 0]),\n        angles,\n    });\n}","parts/bolt.ts":"export async function m3Bolt(length: number) {\n    const profile: Bit.Inputs.Base.Point3[] = [\n        [0, -3, 0], [2.35, -3, 0], [2.75, -2.6, 0], [2.75, 0, 0],\n        [1.5, 0, 0], [1.5, length - 0.4, 0], [1.1, length, 0], [0, length, 0],\n    ];\n    const outline = await bitbybit.occt.shapes.wire.createPolygonWire({ points: profile });\n    const face = await bitbybit.occt.shapes.face.createFaceFromWire({ shape: outline, planar: true });\n    const body = await bitbybit.occt.operations.revolve({ shape: face, angle: 360, direction: [0, 1, 0], copy: false });\n    const hexagon = await bitbybit.occt.shapes.wire.createNGonWire({ nrCorners: 6, radius: 1.45, center: [0, -3.5, 0], direction: [0, 1, 0] });\n    const socketFace = await bitbybit.occt.shapes.face.createFaceFromWire({ shape: hexagon, planar: true });\n    const socket = await bitbybit.occt.operations.extrude({ shape: socketFace, direction: [0, 2.5, 0] });\n    return bitbybit.occt.booleans.difference({ shape: body, shapes: [socket], keepEdges: false });\n}","parts/canopy.ts":"export async function canopy() {\n    const sections = [[0, 140, 90, 26, 4], [12, 132, 84, 24, 6], [22, 110, 70, 19, 9], [29, 76, 48, 13, 10], [32, 52, 36, 10, 10]];\n    const wires: Bit.Inputs.OCCT.TopoDSShapePointer[] = [];\n    for (const [y, length, width, radius, forward] of sections) {\n        const rectangle = await bitbybit.occt.shapes.wire.createRectangleWire({ width, length, center: [0, y, forward], direction: [0, 1, 0] });\n        wires.push(await bitbybit.occt.fillets.fillet2d({ shape: rectangle, radius }));\n    }\n    return bitbybit.occt.operations.loft({ shapes: wires, makeSolid: true });\n}\n\nexport async function honeycomb(shell: Bit.Inputs.OCCT.TopoDSShapePointer) {\n    const centre = (await bitbybit.occt.analysis.measure.tightBoundingBox({ shape: shell })).center;\n    const grown = await bitbybit.occt.transforms.scale3d({ shape: shell, scale: [1.02, 1.02, 1.02], center: [centre[0], 0, centre[2]] });\n    const faces = await bitbybit.occt.shapes.face.getFaces({ shape: grown });\n    const areas = await bitbybit.occt.shapes.face.getFacesAreas({ shapes: faces });\n    const largestSide = Math.max(...areas.slice(0, -2));\n    const cells: Bit.Inputs.OCCT.TopoDSShapePointer[] = [];\n    for (const [i, face] of faces.entries()) {\n        const normal = await bitbybit.occt.shapes.face.normalOnUV({ shape: face, paramU: 0.5, paramV: 0.5 });\n        if (Math.abs(normal[1]) > 0.9) {\n            continue;\n        }\n        const across = areas[i] > largestSide * 0.7 ? 8 : 5;\n        cells.push(...await bitbybit.occt.shapes.face.subdivideToHexagonWires({\n            shape: face,\n            nrHexagonsU: across,\n            nrHexagonsV: 3,\n            flatU: false,\n            scalePatternU: [0.8],\n            scalePatternV: [0.8],\n            filletPattern: [0.35],\n            offsetFromBorderU: 0.05,\n            offsetFromBorderV: 0.08,\n        }));\n    }\n    return bitbybit.occt.shapes.compound.makeCompound({ shapes: cells });\n}","parts/electronics.ts":"export async function gps() {\n    const mast = await bitbybit.occt.shapes.solid.createCylinder({ radius: 2.5, height: 34, center: [0, 16, -70], direction: [0, 1, 0] });\n    const puck = await bitbybit.occt.shapes.solid.createCylinder({ radius: 11, height: 6, center: [0, 50, -70], direction: [0, 1, 0] });\n    const rounded = await bitbybit.occt.fillets.filletEdges({ shape: puck, radius: 1.5 });\n    return bitbybit.occt.booleans.union({ shapes: [mast, rounded], keepEdges: false });\n}\n\nexport async function antennas() {\n    const rod = await bitbybit.occt.shapes.solid.createCylinder({ radius: 1.5, height: 42, center: [0, 0, 0], direction: [0, 1, 0] });\n    const tip = await bitbybit.occt.shapes.solid.createSphere({ radius: 1.5, center: [0, 42, 0] });\n    const antenna = await bitbybit.occt.booleans.union({ shapes: [rod, tip], keepEdges: false });\n    const tilted = await bitbybit.occt.transforms.rotate({ shape: antenna, axis: [1, 0, 0], angle: 225 });\n    const pair: Bit.Inputs.OCCT.TopoDSShapePointer[] = [];\n    for (const x of [-14, 14]) {\n        pair.push(await bitbybit.occt.transforms.translate({ shape: tilted, translation: [x, 0, -67] }));\n    }\n    return bitbybit.occt.shapes.compound.makeCompound({ shapes: pair });\n}","parts/frame.ts":"import { around } from \"./around\";\n\nexport async function framePlate(legs: number) {\n    const rectangle = await bitbybit.occt.shapes.wire.createRectangleWire({ width: 96, length: 150 });\n    const outline = await bitbybit.occt.fillets.fillet2d({ shape: rectangle, radius: 26 });\n    const face = await bitbybit.occt.shapes.face.createFaceFromWire({ shape: outline, planar: true });\n    const slab = await bitbybit.occt.operations.extrude({ shape: face, direction: [0, 3, 0] });\n    const cuts = [...await vents(), await legOpenings(legs)];\n    return bitbybit.occt.booleans.difference({ shape: slab, shapes: cuts, keepEdges: false });\n}\n\nasync function vents() {\n    const hexagons = await bitbybit.occt.shapes.wire.hexagonsInGrid({\n        width: 56,\n        height: 20,\n        nrHexagonsInWidth: 5,\n        nrHexagonsInHeight: 2,\n        flatTop: true,\n        scalePatternWidth: [0.8],\n        scalePatternHeight: [0.8],\n    });\n    const faces = await bitbybit.occt.shapes.face.createFacesFromWires({ shapes: hexagons, planar: true });\n    const prisms = await bitbybit.occt.operations.extrudeShapes({ shapes: faces, direction: [0, 3, 0] });\n    const set = await bitbybit.occt.shapes.compound.makeCompound({ shapes: prisms });\n    const front = await bitbybit.occt.transforms.translate({ shape: set, translation: [0, 0, 60] });\n    const back = await bitbybit.occt.transforms.translate({ shape: set, translation: [0, 0, -60] });\n    return [front, back];\n}\n\nasync function legOpenings(legs: number) {\n    const opening = await bitbybit.occt.shapes.solid.createCylinder({ radius: 6, height: 3, center: [52, 0, 0], direction: [0, 1, 0] });\n    return bitbybit.occt.shapes.compound.makeCompound({ shapes: await around(opening, legs) });\n}","parts/hatch.ts":"const LOGO = \"https://git-cdn.bitbybit.dev/latest/assets/logo/logo-faces.stp\";\n\nexport async function hatchWithLogo() {\n    const rectangle = await bitbybit.occt.shapes.wire.createRectangleWire({ width: 34, length: 50, center: [0, 48, 10], direction: [0, 1, 0] });\n    const outline = await bitbybit.occt.fillets.fillet2d({ shape: rectangle, radius: 9 });\n    const face = await bitbybit.occt.shapes.face.createFaceFromWire({ shape: outline, planar: true });\n    const lid = await bitbybit.occt.operations.extrude({ shape: face, direction: [0, 1.4, 0] });\n    const logo = await logoOnHatch();\n    const cut = await bitbybit.occt.operations.extrude({ shape: logo, direction: [0, 1.7, 0] });\n    const inset = await bitbybit.occt.transforms.scale3d({ shape: logo, scale: [0.96, 1, 0.96], center: [0, 47.9, 10] });\n    return {\n        hatch: await bitbybit.occt.booleans.difference({ shape: lid, shapes: [cut], keepEdges: false }),\n        emblem: await bitbybit.occt.operations.extrude({ shape: inset, direction: [0, 1.1, 0] }),\n    };\n}\n\nasync function logoOnHatch() {\n    const text = await bitbybit.asset.fetchText({ url: LOGO });\n    const logo = await bitbybit.occt.io.loadSTEPorIGESFromText({ text, fileType: Bit.Inputs.OCCT.fileTypeEnum.step, adjustZtoY: true });\n    const mirrored = await bitbybit.occt.transforms.mirrorAlongNormal({ shape: logo, origin: [0, 0, 0], normal: [1, 0, 0] });\n    const size = (await bitbybit.occt.analysis.measure.tightBoundingBox({ shape: mirrored })).size;\n    const scaled = await bitbybit.occt.transforms.scale({ shape: mirrored, factor: 26 / Math.max(size[0], size[2]) });\n    const box = await bitbybit.occt.analysis.measure.tightBoundingBox({ shape: scaled });\n    return bitbybit.occt.transforms.translate({ shape: scaled, translation: [-box.center[0], 47.9 - box.min[1], 10 - box.center[2]] });\n}","parts/landing-gear.ts":"import { m3Bolt } from \"./bolt\";\n\nexport async function leg() {\n    const path = await bitbybit.occt.shapes.wire.createPolylineWire({ points: [[52, 0, 0], [58, -18, 0], [78, -34, 0], [78, -48, 0]] });\n    const bent = await bitbybit.occt.fillets.fillet3DWire({ shape: path, radiusList: [12, 6], indexes: [0, 1], direction: [0, 0, 1] });\n    const tube = await bitbybit.occt.operations.pipeWireCylindrical({ shape: bent, radius: 4, makeSolid: true, trihedronEnum: Bit.Inputs.OCCT.geomFillTrihedronEnum.isConstantNormal, forceApproxC1: false });\n    const flange = await bitbybit.occt.shapes.solid.createBox({ width: 20, length: 12, height: 3, center: [52, -1.5, 0] });\n    const boss = await bitbybit.occt.shapes.solid.createCylinder({ radius: 5.5, height: 6.4, center: [52, -3, 0], direction: [0, 1, 0] });\n    return bitbybit.occt.booleans.union({ shapes: [tube, flange, boss], keepEdges: false });\n}\n\nexport async function damper() {\n    const profile: Bit.Inputs.Base.Point3[] = [\n        [0, 0, 0], [6.5, 0, 0], [6.5, -4, 0], [5.5, -4.5, 0], [5.5, -14, 0], [6.2, -14, 0],\n        [6.2, -16.5, 0], [4, -16.5, 0], [4, -18, 0], [2.2, -18, 0], [2.2, -30, 0], [0, -30, 0],\n    ];\n    const outline = await bitbybit.occt.shapes.wire.createPolygonWire({ points: profile });\n    const face = await bitbybit.occt.shapes.face.createFaceFromWire({ shape: outline, planar: true });\n    const body = await bitbybit.occt.operations.revolve({ shape: face, angle: 360, direction: [0, 1, 0], copy: false });\n    return bitbybit.occt.transforms.translate({ shape: body, translation: [78, -46, 0] });\n}\n\nexport async function spring() {\n    const helix = await bitbybit.occt.shapes.wire.createHelixWire({ radius: 4.2, pitch: 2.8, height: 12.5, center: [78, -75.8, 0], direction: [0, 1, 0], clockwise: false, tolerance: 0.0001 });\n    return bitbybit.occt.operations.pipeWireCylindrical({ shape: helix, radius: 0.9, makeSolid: true, trihedronEnum: Bit.Inputs.OCCT.geomFillTrihedronEnum.isCorrectedFrenet, forceApproxC1: false });\n}\n\nexport async function foot() {\n    const rings: Bit.Inputs.OCCT.TopoDSShapePointer[] = [];\n    for (const [y, radius] of [[0, 3.2], [-2, 7], [-5, 9.5], [-8, 9], [-10, 7]]) {\n        rings.push(await bitbybit.occt.shapes.wire.createCircleWire({ radius, center: [78, -76 + y, 0], direction: [0, 1, 0] }));\n    }\n    return bitbybit.occt.operations.loft({ shapes: rings, makeSolid: true });\n}\n\nexport async function legBolts() {\n    const bolt = await m3Bolt(8);\n    const inner = await bitbybit.occt.transforms.translate({ shape: bolt, translation: [44.5, -3, 0] });\n    const outer = await bitbybit.occt.transforms.translate({ shape: bolt, translation: [59.5, -3, 0] });\n    return bitbybit.occt.shapes.compound.makeCompound({ shapes: [inner, outer] });\n}","parts/lights.ts":"export async function bySide(lights: Bit.Inputs.OCCT.TopoDSShapePointer[]) {\n    const front: Bit.Inputs.OCCT.TopoDSShapePointer[] = [];\n    const back: Bit.Inputs.OCCT.TopoDSShapePointer[] = [];\n    for (const lamp of lights) {\n        const centre = (await bitbybit.occt.analysis.measure.tightBoundingBox({ shape: lamp })).center;\n        (centre[2] > 0 ? front : back).push(lamp);\n    }\n    return { front, back };\n}","parts/payload.ts":"export async function gimbal() {\n    const outline = await bitbybit.occt.shapes.wire.createPolygonWire({ points: [[-4, 19, -4], [19.5, 19, -4], [19.5, -7, -4], [14.5, -7, -4], [14.5, 15, -4], [-4, 15, -4]] });\n    const rounded = await bitbybit.occt.fillets.fillet2d({ shape: outline, radius: 1.5 });\n    const face = await bitbybit.occt.shapes.face.createFaceFromWire({ shape: rounded, planar: true });\n    const bracket = await bitbybit.occt.operations.extrude({ shape: face, direction: [0, 0, 8] });\n    const yaw = await bitbybit.occt.shapes.solid.createCylinder({ radius: 9, height: 8, center: [0, 18, 0], direction: [0, 1, 0] });\n    const roll = await bitbybit.occt.shapes.solid.createCylinder({ radius: 6, height: 5, center: [14.5, 0, 0], direction: [-1, 0, 0] });\n    const whole = await bitbybit.occt.booleans.union({ shapes: [yaw, bracket, roll], keepEdges: false });\n    return bitbybit.occt.transforms.translate({ shape: whole, translation: [0, -26, 55] });\n}\n\nexport async function camera() {\n    const box = await bitbybit.occt.shapes.solid.createBox({ width: 24, length: 20, height: 18, center: [0, -26, 55] });\n    const body = await bitbybit.occt.fillets.filletEdges({ shape: box, radius: 3 });\n    const barrel = await bitbybit.occt.shapes.solid.createCylinder({ radius: 6.5, height: 9, center: [0, -26, 63], direction: [0, 0, 1] });\n    return {\n        camera: await bitbybit.occt.booleans.union({ shapes: [body, barrel], keepEdges: false }),\n        lens: await bitbybit.occt.shapes.solid.createCylinder({ radius: 5.2, height: 1.5, center: [0, -26, 71.5], direction: [0, 0, 1] }),\n    };\n}\n\nexport async function battery() {\n    const box = await bitbybit.occt.shapes.solid.createBox({ width: 36, length: 72, height: 26, center: [0, -16, -20] });\n    return bitbybit.occt.fillets.filletEdges({ shape: box, radius: 3 });\n}\n\nexport async function straps() {\n    const outer = await bitbybit.occt.shapes.solid.createBox({ width: 40, length: 10, height: 30, center: [0, 0, 0] });\n    const inner = await bitbybit.occt.shapes.solid.createBox({ width: 36.5, length: 12, height: 26.5, center: [0, 0, 0] });\n    const strap = await bitbybit.occt.booleans.difference({ shape: outer, shapes: [inner], keepEdges: false });\n    const pair: Bit.Inputs.OCCT.TopoDSShapePointer[] = [];\n    for (const z of [-42, 2]) {\n        pair.push(await bitbybit.occt.transforms.translate({ shape: strap, translation: [0, -16, z] }));\n    }\n    return bitbybit.occt.shapes.compound.makeCompound({ shapes: pair });\n}","parts/spin.ts":"export async function alternateSpin(propellers: Bit.Inputs.OCCT.TopoDSShapePointer[]) {\n    const spun: Bit.Inputs.OCCT.TopoDSShapePointer[] = [];\n    for (const [i, propeller] of propellers.entries()) {\n        const angle = (i + 0.5) * 360 / propellers.length * Math.PI / 180;\n        const centre = (await bitbybit.occt.analysis.measure.tightBoundingBox({ shape: propeller })).center;\n        const turned = await bitbybit.occt.transforms.rotateAroundCenter({ shape: propeller, angle: i * 37, center: [centre[0], 0, centre[2]], axis: [0, 1, 0] });\n        spun.push(i % 2 === 0\n            ? turned\n            : await bitbybit.occt.transforms.mirrorAlongNormal({ shape: turned, origin: [0, 0, 0], normal: [Math.sin(angle), 0, Math.cos(angle)] }));\n    }\n    return spun;\n}","parts/standoffs.ts":"import { around } from \"./around\";\nimport { m3Bolt } from \"./bolt\";\n\nexport async function standoffs(arms: number) {\n    const post = await bitbybit.occt.shapes.solid.createCylinder({ radius: 3.2, height: 10, center: [40, 3, 0], direction: [0, 1, 0] });\n    const bolt = await m3Bolt(8);\n    const lower = await bitbybit.occt.transforms.translate({ shape: bolt, translation: [40, 0, 0] });\n    const flipped = await bitbybit.occt.transforms.rotate({ shape: bolt, axis: [1, 0, 0], angle: 180 });\n    const upper = await bitbybit.occt.transforms.translate({ shape: flipped, translation: [40, 16, 0] });\n    const pair = await bitbybit.occt.shapes.compound.makeCompound({ shapes: [lower, upper] });\n    const halfBack = -180 / arms;\n    const postBetween = await bitbybit.occt.transforms.rotate({ shape: post, axis: [0, 1, 0], angle: halfBack });\n    const pairBetween = await bitbybit.occt.transforms.rotate({ shape: pair, axis: [0, 1, 0], angle: halfBack });\n    return { posts: await around(postBetween, arms), bolts: await around(pairBetween, arms) };\n}","sizes.ts":"export const sizes = {\n    arms: 4,\n    armLength: 185,\n    propeller: 230,\n    legs: 4,\n};\n\nexport function fitPropeller(arms: number, armLength: number, diameter: number): number {\n    const neighbours = 2 * armLength * Math.sin(Math.PI / arms) - 10;\n    const canopy = 2 * (armLength - 68);\n    const largest = Math.floor(Math.min(neighbours, canopy) / 5) * 5;\n    return Math.min(diameter, largest);\n}","stage.ts":"export function setStage() {\n    bitbybit.babylon.scene.backgroundColour({ colour: \"#15171a\" });\n    bitbybit.babylon.scene.enableSkybox({ skybox: Bit.Inputs.Base.skyboxEnum.city, environmentIntensity: 0.4, hideSkybox: true });\n    bitbybit.babylon.scene.drawDirectionalLight({\n        direction: [-0.45, -1, -0.3],\n        intensity: 2,\n        shadowGeneratorMapSize: 4096,\n        shadowBias: 0.0003,\n        shadowNormalBias: 1,\n        shadowMinZ: -1000,\n        shadowMaxZ: 1000,\n    });\n    bitbybit.babylon.scene.adjustActiveArcRotateCamera({ position: [620, 380, 760], lookAt: [0, -20, 0], maxZ: 20000, panningSensibility: 2 });\n}"}}