1.2K tris3.64 × 4.53 × 4.00 m
Use it in code
Remix in the editorstation-module.js · 405 lines
Self-contained ES module — only depends on three. Call the factory and add the group to your scene.
/**
* Station Module — Codels 3D model library
* License: CC0-1.0 · Units: meters · Y-up · Front: +Z · Origin: center of the base (y = 0)
* Triangles: 1,240 · Size (W × H × D): 3.64 × 4.53 × 4.00 m
*
* Usage:
* import { createStationModule } from './station-module.js';
*
* const model = createStationModule();
* model.position.set(0, 0, 0);
* scene.add(model);
*/
import * as THREE from 'three';
const deg = THREE.MathUtils.degToRad;
// Extrude a 2D outline along Z (centered). bevel = [thickness, size, segments].
function extrude(outline, depth, holes = [], bevel = null) {
const shape = new THREE.Shape(outline.map(([x, y]) => new THREE.Vector2(x, y)));
for (const hole of holes) shape.holes.push(new THREE.Path(hole.map(([x, y]) => new THREE.Vector2(x, y))));
const options = bevel
? { depth, bevelEnabled: true, bevelThickness: bevel[0], bevelSize: bevel[1], bevelSegments: bevel[2] }
: { depth, bevelEnabled: false };
return new THREE.ExtrudeGeometry(shape, options).translate(0, 0, -depth / 2);
}
// Tube along a smooth curve through the given points.
function tube(path, radius, tubularSegments, radialSegments, closed = false) {
const curve = new THREE.CatmullRomCurve3(path.map(([x, y, z]) => new THREE.Vector3(x, y, z)), closed);
return new THREE.TubeGeometry(curve, tubularSegments, radius, radialSegments, closed);
}
export function createStationModule() {
const root = new THREE.Group();
root.name = 'StationModule';
// Materials
const hullMat = new THREE.MeshStandardMaterial({ name: 'hull', color: 0xd4dae1, roughness: 0.45, metalness: 0.3, flatShading: true });
const trimMat = new THREE.MeshStandardMaterial({ name: 'trim', color: 0x3a4049, roughness: 0.55, metalness: 0.45, flatShading: true });
const floorMat = new THREE.MeshStandardMaterial({ name: 'floor', color: 0x4a515c, roughness: 0.7, metalness: 0.4, flatShading: true });
const metalMat = new THREE.MeshStandardMaterial({ name: 'metal', color: 0x8a929c, roughness: 0.4, metalness: 0.6, flatShading: true });
const glassMat = new THREE.MeshStandardMaterial({ name: 'glass', color: 0x9fd6ef, roughness: 0.08, metalness: 0.2, transparent: true, opacity: 0.45 });
const glowMat = new THREE.MeshStandardMaterial({ name: 'glow', color: 0x3ad6ff, roughness: 0.4, emissive: 0x3ad6ff, emissiveIntensity: 1.3, flatShading: true });
const hazardMat = new THREE.MeshStandardMaterial({ name: 'hazard', color: 0xf2b630, roughness: 0.55, metalness: 0.2, flatShading: true });
const redMat = new THREE.MeshStandardMaterial({ name: 'red', color: 0xff5a4a, roughness: 0.4, emissive: 0xff3b2f, emissiveIntensity: 1.2, flatShading: true });
// Shared geometries
const boxGeo = new THREE.BoxGeometry(2.22, 0.12, 3.72);
const boxGeo2 = new THREE.BoxGeometry(0.9368, 0.12, 3.72);
const boxGeo3 = new THREE.BoxGeometry(0.12, 0.42, 3.72);
const boxGeo4 = new THREE.BoxGeometry(0.04, 1, 3.72);
const boxGeo5 = new THREE.BoxGeometry(0.16, 1, 0.16);
const boxGeo6 = new THREE.BoxGeometry(0.18, 0.08, 3.72);
const boxGeo7 = new THREE.BoxGeometry(0.1, 0.05, 0.3);
const boxGeo8 = new THREE.BoxGeometry(2.16, 0.012, 0.05);
const boxGeo9 = new THREE.BoxGeometry(0.05, 0.03, 3.52);
const extrudeGeo = extrude([[-1.1297, 0.18], [1.1297, 0.18], [1.82, 0.8703], [1.82, 2.7297], [1.1297, 3.42], [-1.1297, 3.42], [-1.82, 2.7297], [-1.82, 0.8703]], 0.14, [[[-1.64, 0.9448], [-1.64, 2.6552], [-1.0552, 3.24], [1.0552, 3.24], [1.64, 2.6552], [1.64, 0.9448], [1.0552, 0.36], [-1.0552, 0.36]]]);
const extrudeGeo2 = extrude([[-1.08, 0.3], [1.08, 0.3], [1.7, 0.92], [1.7, 2.68], [1.08, 3.3], [-1.08, 3.3], [-1.7, 2.68], [-1.7, 0.92]], 0.2, [[[-0.72, 2.43], [-0.42, 2.73], [0.42, 2.73], [0.72, 2.43], [0.72, 0.46], [-0.72, 0.46]]]);
const boxGeo10 = new THREE.BoxGeometry(0.12, 2.05, 0.06);
const boxGeo11 = new THREE.BoxGeometry(1.2, 0.1, 0.06);
const boxGeo12 = new THREE.BoxGeometry(0.14, 0.24, 0.05);
const boxGeo13 = new THREE.BoxGeometry(0.08, 0.08, 0.03);
const boxGeo14 = new THREE.BoxGeometry(0.72, 2.21, 0.08);
const boxGeo15 = new THREE.BoxGeometry(0.03, 1.95, 0.02);
const boxGeo16 = new THREE.BoxGeometry(0.09, 0.4, 0.02);
const boxGeo17 = new THREE.BoxGeometry(0.3, 0.3, 3.5);
const boxGeo18 = new THREE.BoxGeometry(0.5, 0.08, 0.5);
// Parts
const hull = new THREE.Group();
hull.name = 'hull';
root.add(hull);
const panel1 = new THREE.Mesh(boxGeo, hullMat);
panel1.name = 'panel1';
panel1.position.set(0, 0.36, 0);
hull.add(panel1);
const panel2 = new THREE.Mesh(boxGeo2, hullMat);
panel2.name = 'panel2';
panel2.position.set(1.3476, 0.6524, 0);
panel2.rotation.set(0, 0, deg(45));
hull.add(panel2);
const wallLowR = new THREE.Mesh(boxGeo3, hullMat);
wallLowR.name = 'wallLowR';
wallLowR.position.set(1.64, 1.11, 0);
hull.add(wallLowR);
const wallHighR = new THREE.Mesh(boxGeo3, hullMat);
wallHighR.name = 'wallHighR';
wallHighR.position.set(1.64, 2.49, 0);
hull.add(wallHighR);
const windowR = new THREE.Mesh(boxGeo4, glassMat);
windowR.name = 'windowR';
windowR.position.set(1.64, 1.8, 0);
hull.add(windowR);
const box7 = new THREE.Mesh(boxGeo5, trimMat);
box7.position.set(1.64, 1.8, -1.78);
hull.add(box7);
const box8 = new THREE.Mesh(boxGeo5, trimMat);
box8.position.set(1.64, 1.8, 0);
hull.add(box8);
const box9 = new THREE.Mesh(boxGeo5, trimMat);
box9.position.set(1.64, 1.8, 1.78);
hull.add(box9);
const box10 = new THREE.Mesh(boxGeo6, trimMat);
box10.position.set(1.64, 1.28, 0);
hull.add(box10);
const box11 = new THREE.Mesh(boxGeo6, trimMat);
box11.position.set(1.64, 2.32, 0);
hull.add(box11);
const panel4 = new THREE.Mesh(boxGeo2, hullMat);
panel4.name = 'panel4';
panel4.position.set(1.3476, 2.9476, 0);
panel4.rotation.set(0, 0, deg(135));
hull.add(panel4);
const panel5 = new THREE.Mesh(boxGeo, hullMat);
panel5.name = 'panel5';
panel5.position.set(0, 3.24, 0);
panel5.rotation.set(0, 0, deg(180));
hull.add(panel5);
const panel6 = new THREE.Mesh(boxGeo2, hullMat);
panel6.name = 'panel6';
panel6.position.set(-1.3476, 2.9476, 0);
panel6.rotation.set(0, 0, deg(-135));
hull.add(panel6);
const wallLowL = new THREE.Mesh(boxGeo3, hullMat);
wallLowL.name = 'wallLowL';
wallLowL.position.set(-1.64, 1.11, 0);
hull.add(wallLowL);
const wallHighL = new THREE.Mesh(boxGeo3, hullMat);
wallHighL.name = 'wallHighL';
wallHighL.position.set(-1.64, 2.49, 0);
hull.add(wallHighL);
const windowL = new THREE.Mesh(boxGeo4, glassMat);
windowL.name = 'windowL';
windowL.position.set(-1.64, 1.8, 0);
hull.add(windowL);
const box18 = new THREE.Mesh(boxGeo5, trimMat);
box18.position.set(-1.64, 1.8, -1.78);
hull.add(box18);
const box19 = new THREE.Mesh(boxGeo5, trimMat);
box19.position.set(-1.64, 1.8, 0);
hull.add(box19);
const box20 = new THREE.Mesh(boxGeo5, trimMat);
box20.position.set(-1.64, 1.8, 1.78);
hull.add(box20);
const box21 = new THREE.Mesh(boxGeo6, trimMat);
box21.position.set(-1.64, 1.28, 0);
hull.add(box21);
const box22 = new THREE.Mesh(boxGeo6, trimMat);
box22.position.set(-1.64, 2.32, 0);
hull.add(box22);
const panel8 = new THREE.Mesh(boxGeo2, hullMat);
panel8.name = 'panel8';
panel8.position.set(-1.3476, 0.6524, 0);
panel8.rotation.set(0, 0, deg(-45));
hull.add(panel8);
const midRib = new THREE.Mesh(extrude([[-1.1214, 0.2], [1.1214, 0.2], [1.8, 0.8786], [1.8, 2.7214], [1.1214, 3.4], [-1.1214, 3.4], [-1.8, 2.7214], [-1.8, 0.8786]], 0.3, [[[-1.68, 0.9283], [-1.68, 2.6717], [-1.0717, 3.28], [1.0717, 3.28], [1.68, 2.6717], [1.68, 0.9283], [1.0717, 0.32], [-1.0717, 0.32]]]), trimMat);
midRib.name = 'midRib';
root.add(midRib);
const conduitL = new THREE.Mesh(tube([[-1.445, 3.045, -1.86], [-1.445, 3.045, 1.86]], 0.07, 1, 6), metalMat);
conduitL.name = 'conduitL';
root.add(conduitL);
const markerL = new THREE.Mesh(boxGeo7, redMat);
markerL.name = 'markerL';
markerL.position.set(-1.73, 2.58, 0);
root.add(markerL);
const conduitR = new THREE.Mesh(tube([[1.445, 3.045, -1.86], [1.445, 3.045, 1.86]], 0.07, 1, 6), metalMat);
conduitR.name = 'conduitR';
root.add(conduitR);
const markerR = new THREE.Mesh(boxGeo7, redMat);
markerR.name = 'markerR';
markerR.position.set(1.73, 2.58, 0);
root.add(markerR);
const roofVent = new THREE.Mesh(new THREE.BoxGeometry(1, 0.25, 1.2), trimMat);
roofVent.name = 'roofVent';
roofVent.position.set(-0.25, 3.425, -0.9);
root.add(roofVent);
const box30 = new THREE.Mesh(new THREE.BoxGeometry(0.7, 0.04, 0.9), metalMat);
box30.position.set(-0.25, 3.57, -0.9);
root.add(box30);
const roofBox = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.35, 0.5), hullMat);
roofBox.name = 'roofBox';
roofBox.position.set(0.45, 3.475, 0.95);
root.add(roofBox);
const roofAntenna = new THREE.Mesh(new THREE.CylinderGeometry(0.03, 0.03, 0.8, 5), metalMat);
roofAntenna.name = 'roofAntenna';
roofAntenna.position.set(0.55, 4.05, 1.05);
root.add(roofAntenna);
const roofBeacon = new THREE.Mesh(new THREE.SphereGeometry(0.05, 6, 4), redMat);
roofBeacon.name = 'roofBeacon';
roofBeacon.position.set(0.55, 4.48, 1.05);
root.add(roofBeacon);
const floor = new THREE.Mesh(new THREE.BoxGeometry(2.26, 0.06, 3.72), floorMat);
floor.name = 'floor';
floor.position.set(0, 0.45, 0);
root.add(floor);
const box35 = new THREE.Mesh(boxGeo8, trimMat);
box35.position.set(0, 0.484, -1.51);
root.add(box35);
const box36 = new THREE.Mesh(boxGeo8, trimMat);
box36.position.set(0, 0.484, -0.69);
root.add(box36);
const box37 = new THREE.Mesh(boxGeo8, trimMat);
box37.position.set(0, 0.484, 0.13);
root.add(box37);
const box38 = new THREE.Mesh(boxGeo8, trimMat);
box38.position.set(0, 0.484, 0.95);
root.add(box38);
const box39 = new THREE.Mesh(boxGeo8, trimMat);
box39.position.set(0, 0.484, 1.77);
root.add(box39);
const guideL = new THREE.Mesh(boxGeo9, glowMat);
guideL.name = 'guideL';
guideL.position.set(-1.03, 0.49, 0);
root.add(guideL);
const pipeL = new THREE.Mesh(tube([[-1.53, 2.5, -1.86], [-1.53, 2.5, 1.86]], 0.05, 1, 5), metalMat);
pipeL.name = 'pipeL';
root.add(pipeL);
const cableL = new THREE.Mesh(tube([[-1.54, 1.08, -1.86], [-1.54, 1.08, 1.86]], 0.04, 1, 5), redMat);
cableL.name = 'cableL';
root.add(cableL);
const guideR = new THREE.Mesh(boxGeo9, glowMat);
guideR.name = 'guideR';
guideR.position.set(1.03, 0.49, 0);
root.add(guideR);
const pipeR = new THREE.Mesh(tube([[1.53, 2.5, -1.86], [1.53, 2.5, 1.86]], 0.05, 1, 5), metalMat);
pipeR.name = 'pipeR';
root.add(pipeR);
const cableR = new THREE.Mesh(tube([[1.54, 1.08, -1.86], [1.54, 1.08, 1.86]], 0.04, 1, 5), redMat);
cableR.name = 'cableR';
root.add(cableR);
const ceilingLight = new THREE.Mesh(new THREE.BoxGeometry(0.45, 0.04, 3.42), glowMat);
ceilingLight.name = 'ceilingLight';
ceilingLight.position.set(0, 3.16, 0);
root.add(ceilingLight);
const frontEnd = new THREE.Group();
frontEnd.name = 'frontEnd';
root.add(frontEnd);
const flange = new THREE.Mesh(extrudeGeo, trimMat);
flange.name = 'flange';
flange.position.set(0, 0, 1.93);
frontEnd.add(flange);
const bulkhead = new THREE.Mesh(extrudeGeo2, hullMat);
bulkhead.name = 'bulkhead';
bulkhead.position.set(0, 0, 1.82);
frontEnd.add(bulkhead);
const box50 = new THREE.Mesh(boxGeo10, trimMat);
box50.position.set(-0.78, 1.505, 1.94);
frontEnd.add(box50);
const box51 = new THREE.Mesh(boxGeo10, trimMat);
box51.position.set(0.78, 1.505, 1.94);
frontEnd.add(box51);
const doorLight = new THREE.Mesh(boxGeo11, glowMat);
doorLight.name = 'doorLight';
doorLight.position.set(0, 2.91, 1.94);
frontEnd.add(doorLight);
const doorPanel = new THREE.Mesh(boxGeo12, trimMat);
doorPanel.name = 'doorPanel';
doorPanel.position.set(1.04, 1.45, 1.94);
frontEnd.add(doorPanel);
const doorButton = new THREE.Mesh(boxGeo13, glowMat);
doorButton.name = 'doorButton';
doorButton.position.set(1.04, 1.5, 1.97);
frontEnd.add(doorButton);
const doorLeafL = new THREE.Group();
doorLeafL.name = 'doorLeafL';
doorLeafL.position.set(0, 0, 1.82);
frontEnd.add(doorLeafL);
const leaf = new THREE.Mesh(boxGeo14, metalMat);
leaf.name = 'leaf';
leaf.position.set(-0.36, 1.585, 0);
doorLeafL.add(leaf);
const seam = new THREE.Mesh(boxGeo15, glowMat);
seam.name = 'seam';
seam.position.set(-0.03, 1.555, 0.045);
doorLeafL.add(seam);
const box58 = new THREE.Mesh(boxGeo16, hazardMat);
box58.position.set(-0.18, 1.03, 0.045);
box58.rotation.set(0, 0, deg(35));
doorLeafL.add(box58);
const box59 = new THREE.Mesh(boxGeo16, hazardMat);
box59.position.set(-0.38, 1.03, 0.045);
box59.rotation.set(0, 0, deg(35));
doorLeafL.add(box59);
const doorLeafR = new THREE.Group();
doorLeafR.name = 'doorLeafR';
doorLeafR.position.set(0, 0, 1.82);
frontEnd.add(doorLeafR);
const leaf2 = new THREE.Mesh(boxGeo14, metalMat);
leaf2.name = 'leaf';
leaf2.position.set(0.36, 1.585, 0);
doorLeafR.add(leaf2);
const seam2 = new THREE.Mesh(boxGeo15, glowMat);
seam2.name = 'seam';
seam2.position.set(0.03, 1.555, 0.045);
doorLeafR.add(seam2);
const box63 = new THREE.Mesh(boxGeo16, hazardMat);
box63.position.set(0.18, 1.03, 0.045);
box63.rotation.set(0, 0, deg(-35));
doorLeafR.add(box63);
const box64 = new THREE.Mesh(boxGeo16, hazardMat);
box64.position.set(0.38, 1.03, 0.045);
box64.rotation.set(0, 0, deg(-35));
doorLeafR.add(box64);
const backEnd = new THREE.Group();
backEnd.name = 'backEnd';
backEnd.rotation.set(0, deg(180), 0);
root.add(backEnd);
const flange2 = new THREE.Mesh(extrudeGeo, trimMat);
flange2.name = 'flange';
flange2.position.set(0, 0, 1.93);
backEnd.add(flange2);
const bulkhead2 = new THREE.Mesh(extrudeGeo2, hullMat);
bulkhead2.name = 'bulkhead';
bulkhead2.position.set(0, 0, 1.82);
backEnd.add(bulkhead2);
const box68 = new THREE.Mesh(boxGeo10, trimMat);
box68.position.set(-0.78, 1.505, 1.94);
backEnd.add(box68);
const box69 = new THREE.Mesh(boxGeo10, trimMat);
box69.position.set(0.78, 1.505, 1.94);
backEnd.add(box69);
const doorLight2 = new THREE.Mesh(boxGeo11, glowMat);
doorLight2.name = 'doorLight';
doorLight2.position.set(0, 2.91, 1.94);
backEnd.add(doorLight2);
const doorPanel2 = new THREE.Mesh(boxGeo12, trimMat);
doorPanel2.name = 'doorPanel';
doorPanel2.position.set(1.04, 1.45, 1.94);
backEnd.add(doorPanel2);
const doorButton2 = new THREE.Mesh(boxGeo13, glowMat);
doorButton2.name = 'doorButton';
doorButton2.position.set(1.04, 1.5, 1.97);
backEnd.add(doorButton2);
const doorLeafL2 = new THREE.Group();
doorLeafL2.name = 'doorLeafL';
doorLeafL2.position.set(0, 0, 1.82);
backEnd.add(doorLeafL2);
const leaf3 = new THREE.Mesh(boxGeo14, metalMat);
leaf3.name = 'leaf';
leaf3.position.set(-0.36, 1.585, 0);
doorLeafL2.add(leaf3);
const seam3 = new THREE.Mesh(boxGeo15, glowMat);
seam3.name = 'seam';
seam3.position.set(-0.03, 1.555, 0.045);
doorLeafL2.add(seam3);
const box76 = new THREE.Mesh(boxGeo16, hazardMat);
box76.position.set(-0.18, 1.03, 0.045);
box76.rotation.set(0, 0, deg(35));
doorLeafL2.add(box76);
const box77 = new THREE.Mesh(boxGeo16, hazardMat);
box77.position.set(-0.38, 1.03, 0.045);
box77.rotation.set(0, 0, deg(35));
doorLeafL2.add(box77);
const doorLeafR2 = new THREE.Group();
doorLeafR2.name = 'doorLeafR';
doorLeafR2.position.set(0, 0, 1.82);
backEnd.add(doorLeafR2);
const leaf4 = new THREE.Mesh(boxGeo14, metalMat);
leaf4.name = 'leaf';
leaf4.position.set(0.36, 1.585, 0);
doorLeafR2.add(leaf4);
const seam4 = new THREE.Mesh(boxGeo15, glowMat);
seam4.name = 'seam';
seam4.position.set(0.03, 1.555, 0.045);
doorLeafR2.add(seam4);
const box81 = new THREE.Mesh(boxGeo16, hazardMat);
box81.position.set(0.18, 1.03, 0.045);
box81.rotation.set(0, 0, deg(-35));
doorLeafR2.add(box81);
const box82 = new THREE.Mesh(boxGeo16, hazardMat);
box82.position.set(0.38, 1.03, 0.045);
box82.rotation.set(0, 0, deg(-35));
doorLeafR2.add(box82);
const skidL = new THREE.Mesh(boxGeo17, trimMat);
skidL.name = 'skidL';
skidL.position.set(-0.85, 0.15, 0);
root.add(skidL);
const box84 = new THREE.Mesh(boxGeo18, metalMat);
box84.position.set(-0.85, 0.04, -1.2);
root.add(box84);
const box85 = new THREE.Mesh(boxGeo18, metalMat);
box85.position.set(-0.85, 0.04, 1.2);
root.add(box85);
const skidR = new THREE.Mesh(boxGeo17, trimMat);
skidR.name = 'skidR';
skidR.position.set(0.85, 0.15, 0);
root.add(skidR);
const box87 = new THREE.Mesh(boxGeo18, metalMat);
box87.position.set(0.85, 0.04, -1.2);
root.add(box87);
const box88 = new THREE.Mesh(boxGeo18, metalMat);
box88.position.set(0.85, 0.04, 1.2);
root.add(box88);
root.traverse((o) => {
if (o.isMesh) {
o.castShadow = true;
o.receiveShadow = true;
}
});
return root;
}
export default createStationModule;
Ratings & comments
0 comments
No comments yet. Share how you used this model or what could be better.
Related models
Same category or similar tags









