PageSourceSearch

https://toolingmetrics.com/assets/controllers/device_model_controller-9e3aa12c.js

js toolingmetrics.com collected 2026-10-06 08:52:14 UTC 16,284 bytes, 557 lines download raw bytes

1import { CSS2DObject } from "CSS2DRenderer";
2import { Controller } from "@hotwired/stimulus";
3import {
4  capturePicture,
5  cleanup,
6  disposeObject,
7  rotateTo,
8  setupLighting,
9  setupViewCube,
10  setupViewer,
11  toggleTransparent,
12  toggleWireframe,
13} from "cad-viewer";
14import CameraControls from "camera-controls";
15import * as THREE from "three";
16
17let scene, renderer;
18let labelRenderer;
19let currentCamera;
20let cameraControls, cameraDirLight;
21let boundedLength;
22let mainObject;
23let loaded = false;
24let wireframed = true;
25let transparent = true;
26const solid = true;
27let mixer;
28let highlightMaterial;
29let material;
30let lineMaterial;
31const clock = new THREE.Clock();
32const materialColorValue = [0.5803921568627451, 0.6392156862745098, 0.7215686274509804];
33const highlightColorValue = [0.9803921568627451, 0.8, 0.08235294117647059];
34const itemConditionMaterialColorTokens = {
35  unspecified: "--color-zinc-300",
36  brand_new: "--color-green-600",
37  refurbished: "--color-green-400",
38  unused: "--color-teal-500",
39  used: "--color-main-500",
40  for_service: "--color-yellow-400",
41  for_scrap: "--color-red-400",
42};
43let highlightClip;
44let arrowRenderer, arrowScene, arrowCamera;
45let viewerElement, coordinateElement, spinnerElement;
46let minDim, wcs;
47let coordinateVisible = false;
48let assemblyMetadata = null;
49let loadedMetadata = null;
50let animationFrame = null;
51let highlightedMesh = null;
52let activeController = null;
53
54const DEG90 = 90 * THREE.MathUtils.DEG2RAD;
55const MAX_PIXEL_RATIO = 1.5;
56
57// Connects to data-controller="device-model"
58export default class extends Controller {
59  static targets = ["viewer", "coordinate", "spinner", "metadata"];
60
61  static values = {
62    address: Array,
63    itemCondition: String,
64  };
65
66  connect() {
67    activeController = this;
68    viewerElement = this.viewerTarget;
69    coordinateElement = this.coordinateTarget;
70    spinnerElement = this.spinnerTarget;
71    configureHighlightAnimation(this.itemConditionValue);
72    const metadata = this.metadataTarget.dataset.metadata;
73
74    if (loaded) {
75      attachViewer();
76      resizeViewer();
77      if (metadata !== loadedMetadata) {
78        replaceModel(metadata);
79      } else {
80        zoomToFit();
81      }
82      spinnerElement.classList.add("hidden");
83      if (this.addressValue.length === 3) {
84        this.showLocation(this.addressValue);
85      }
86      return;
87    }
88
89    destroyViewer();
90    loadedMetadata = metadata;
91    loaded = false;
92    boundedLength = 250;
93    coordinateVisible = false;
94    wcs = null;
95    highlightMaterial = new THREE.MeshBasicMaterial({ color: 0xfacc15 });
96    material = new THREE.MeshBasicMaterial({ color: 0xa1a1aa });
97    lineMaterial = new THREE.LineBasicMaterial({ color: 0x52525b });
98    CameraControls.install({ THREE: THREE });
99    try {
100      assemblyMetadata = JSON.parse(metadata);
101    } catch {
102      assemblyMetadata = {};
103    }
104    this.loadModels();
105  }
106
107  disconnect() {
108    if (activeController !== this) return;
109
110    activeController = null;
111    cancelAnimationFrame(animationFrame);
112    animationFrame = null;
113    stopMixer();
114    detachViewer();
115    viewerElement = null;
116    coordinateElement = null;
117    spinnerElement = null;
118  }
119
120  loadModels() {
121    if (!loaded) {
122      initModelViewer();
123      mainObject.updateMatrix();
124      rotateTo("isometric", cameraControls, "Z");
125      zoomToFit();
126      loaded = true;
127      spinnerElement.classList.add("hidden");
128      if (this.addressValue.length === 3) {
129        this.showLocation(this.addressValue);
130      }
131      startAnimation();
132    }
133  }
134
135  reloadModels() {
136    spinnerElement.classList.remove("hidden");
137    replaceModel(this.metadataTarget.dataset.metadata);
138    spinnerElement.classList.add("hidden");
139  }
140
141  highlightModule(event) {
142    mainObject.children.forEach((mesh) => {
143      if (mesh.type === "Mesh" && mesh.name.split(",").pop() === event.params.index.toString()) {
144        // flashing the mesh location
145        highlightLocation(mesh);
146      }
147    });
148    renderer.render(scene, currentCamera);
149  }
150
151  resetHighlightModule(event) {
152    mainObject.children.forEach((mesh) => {
153      if (mesh.type === "Mesh" && mesh.name.split(",").pop() === event.params.index.toString()) {
154        // stop flashing the mesh location
155        resetHighlightLocation(mesh);
156      }
157    });
158    renderer.render(scene, currentCamera);
159  }
160
161  highlightLocation(event) {
162    mainObject.children.forEach((mesh) => {
163      if (mesh.type === "Mesh" && mesh.name === event.params.index.toString()) {
164        // flashing the mesh location
165        highlightLocation(mesh);
166      }
167    });
168    renderer.render(scene, currentCamera);
169  }
170
171  resetHighlightLocation(event) {
172    mainObject.children.forEach((mesh) => {
173      if (mesh.type === "Mesh" && mesh.name === event.params.index.toString()) {
174        // stop flashing the mesh location
175        resetHighlightLocation(mesh);
176      }
177    });
178    renderer.render(scene, currentCamera);
179  }
180
181  showLocation(address) {
182    stopMixer();
183    if (highlightedMesh?.material === highlightMaterial) {
184      highlightedMesh.material = material;
185    }
186    highlightedMesh = null;
187    mainObject.children.forEach((mesh) => {
188      if (mesh.type === "Mesh" && mesh.name === address.toString()) {
189        highlightMaterial.opacity = 1.0;
190        highlightMaterial.transparent = false;
191        mesh.material = highlightMaterial;
192        highlightMaterial.needsUpdate = true;
193        mixer = new THREE.AnimationMixer(mesh);
194        const clipAction = mixer.clipAction(highlightClip);
195        clipAction.setLoop(THREE.LoopRepeat, 10);
196        clipAction.clampWhenFinished = true;
197        clipAction.play();
198        mixer.addEventListener("finished", stopMixer);
199        highlightedMesh = mesh;
200        return;
201      }
202    });
203    renderer.render(scene, currentCamera);
204    if (mixer) startAnimation();
205  }
206
207  visibleToggle() {
208    toggleCoordinate();
209  }
210
211  toggleViewWireframe() {
212    wireframed = !wireframed;
213    toggleWireframe(mainObject, wireframed);
214    renderer.render(scene, currentCamera);
215  }
216
217  toggleViewTransparent() {
218    if (solid === false) return;
219
220    transparent = !transparent;
221    toggleTransparent(mainObject, transparent);
222    renderer.render(scene, currentCamera);
223  }
224
225  rotateToView(event) {
226    const type = event.params.name;
227    rotateTo(type, cameraControls, "Z");
228    zoomToFit();
229  }
230
231  fitToView() {
232    zoomToFit();
233  }
234
235  capturePicture() {
236    renderer.render(scene, currentCamera);
237    capturePicture(renderer.domElement);
238  }
239}
240
241function initModelViewer() {
242  const canvasElement = viewerElement;
243  const arrowCanvas = coordinateElement;
244  const width = canvasElement.offsetWidth;
245  const height = canvasElement.offsetHeight;
246
247  ({ renderer, labelRenderer, currentCamera } = setupViewer(
248    width,
249    height,
250    boundedLength,
251    "Z",
252    MAX_PIXEL_RATIO,
253  ));
254  canvasElement.appendChild(renderer.domElement);
255  canvasElement.appendChild(labelRenderer.domElement);
256
257  scene = new THREE.Scene();
258
259  cameraDirLight = setupLighting(scene, currentCamera, boundedLength);
260
261  mainObject = new THREE.Group();
262  mainObject.matrixAutoUpdate = false;
263  LoadDevice(mainObject, assemblyMetadata.metadata);
264  setUpDirection(mainObject, assemblyMetadata.up_direction);
265  scene.add(mainObject);
266
267  cameraControls = new CameraControls(currentCamera, renderer.domElement);
268  cameraControls.addEventListener("control", startAnimation);
269  cameraControls.addEventListener("transitionstart", startAnimation);
270  cameraControls.update();
271
272  ({ arrowRenderer, arrowScene, arrowCamera } = setupViewCube(currentCamera));
273  arrowCanvas.appendChild(arrowRenderer.domElement);
274}
275
276function attachViewer() {
277  viewerElement.appendChild(renderer.domElement);
278  viewerElement.appendChild(labelRenderer.domElement);
279  coordinateElement.appendChild(arrowRenderer.domElement);
280}
281
282function detachViewer() {
283  renderer?.domElement.remove();
284  labelRenderer?.domElement.remove();
285  arrowRenderer?.domElement.remove();
286}
287
288function resizeViewer() {
289  const width = viewerElement.offsetWidth;
290  const height = viewerElement.offsetHeight;
291  const currentSize = renderer.getSize(new THREE.Vector2());
292  if (currentSize.x !== width || currentSize.y !== height) {
293    const aspect = width / height;
294    currentCamera.left = (-boundedLength / 2) * aspect;
295    currentCamera.right = (boundedLength / 2) * aspect;
296    currentCamera.top = boundedLength / 2;
297    currentCamera.bottom = -boundedLength / 2;
298    currentCamera.updateProjectionMatrix();
299    renderer.setSize(width, height);
300    labelRenderer.setSize(width, height);
301  }
302  renderer.render(scene, currentCamera);
303  labelRenderer.render(scene, currentCamera);
304}
305
306function replaceModel(metadata) {
307  stopMixer();
308  highlightedMesh = null;
309  while (labelRenderer.domElement.lastElementChild) {
310    labelRenderer.domElement.lastElementChild.remove();
311  }
312  if (wcs) {
313    scene.remove(wcs);
314    disposeObject(wcs);
315    wcs = null;
316  }
317  if (mainObject) {
318    scene.remove(mainObject);
319    disposeObject(mainObject);
320  }
321  highlightMaterial?.dispose();
322  material?.dispose();
323  lineMaterial?.dispose();
324  highlightMaterial = new THREE.MeshBasicMaterial({ color: 0xfacc15 });
325  material = new THREE.MeshBasicMaterial({ color: 0xa1a1aa });
326  lineMaterial = new THREE.LineBasicMaterial({ color: 0x52525b });
327  loadedMetadata = metadata;
328  assemblyMetadata = JSON.parse(metadata);
329  mainObject = new THREE.Group();
330  mainObject.matrixAutoUpdate = false;
331  LoadDevice(mainObject, assemblyMetadata.metadata);
332  setUpDirection(mainObject, assemblyMetadata.up_direction);
333  scene.add(mainObject);
334  mainObject.updateMatrix();
335  rotateTo("isometric", cameraControls, "Z");
336  zoomToFit();
337}
338
339function destroyViewer() {
340  cancelAnimationFrame(animationFrame);
341  animationFrame = null;
342  stopMixer();
343  cameraControls?.dispose();
344  cleanup(renderer, scene, currentCamera);
345  cleanup(arrowRenderer, arrowScene, arrowCamera);
346  mainObject = null;
347  assemblyMetadata = null;
348  loadedMetadata = null;
349  renderer = null;
350  scene = null;
351  labelRenderer = null;
352  currentCamera = null;
353  cameraControls = null;
354  arrowRenderer = null;
355  arrowScene = null;
356  arrowCamera = null;
357  highlightMaterial = null;
358  material = null;
359  lineMaterial = null;
360  highlightedMesh = null;
361  loaded = false;
362}
363
364function LoadDevice(targetObject, metadata) {
365  if (transparent === true) {
366    material.opacity = 0.4;
367    material.transparent = true;
368  } else {
369    material.opacity = 1.0;
370    material.transparent = false;
371  }
372  const geometryCache = new Map();
373  for (const index in metadata) {
374    if (Object.hasOwn(metadata, index)) {
375      const data = metadata[index];
376      const geometryKey = data.box.join(",");
377      let geometries = geometryCache.get(geometryKey);
378      if (!geometries) {
379        const geometry = new THREE.BoxGeometry(data.box[0], data.box[1], data.box[2]);
380        geometries = { geometry, edges: new THREE.EdgesGeometry(geometry) };
381        geometryCache.set(geometryKey, geometries);
382      }
383
384      const { geometry, edges } = geometries;
385      const mesh = new THREE.Mesh(geometry, material);
386      targetObject.add(mesh);
387      const xo = data.box[0] / 2.0 + data.coordinate[0];
388      const yo = data.box[1] / 2.0 + data.coordinate[1];
389      const zo = data.box[2] / 2.0 + data.coordinate[2];
390      mesh.position.set(xo, yo, zo);
391      const name = index.slice(1, index.length - 1).replaceAll(" ", "");
392      mesh.name = name;
393      const wireframe = new THREE.LineSegments(edges, lineMaterial);
394      wireframe.name = name;
395      wireframe.visible = wireframed;
396      targetObject.add(wireframe);
397      wireframe.position.set(xo, yo, zo);
398    }
399  }
400}
401
402function animate() {
403  if (!renderer || !cameraControls) {
404    animationFrame = null;
405    return;
406  }
407
408  const delta = clock.getDelta();
409  const updated = cameraControls.update(delta);
410
411  if (updated || mixer) {
412    if (mixer) {
413      mixer.update(delta);
414    }
415    renderer.render(scene, currentCamera);
416  }
417
418  if (updated) {
419    labelRenderer.render(scene, currentCamera);
420    arrowRenderer.render(arrowScene, arrowCamera);
421    arrowCamera.position.copy(currentCamera.position);
422    arrowCamera.position.sub(cameraControls.getTarget());
423    arrowCamera.position.setLength(300);
424    arrowCamera.lookAt(arrowScene.position);
425    cameraDirLight.position.set(
426      currentCamera.position.x,
427      currentCamera.position.y,
428      currentCamera.position.z,
429    );
430  }
431
432  animationFrame = updated || mixer ? requestAnimationFrame(animate) : null;
433}
434
435function startAnimation() {
436  if (animationFrame === null) {
437    clock.start();
438    animationFrame = requestAnimationFrame(animate);
439  }
440}
441
442function stopMixer() {
443  if (mixer) {
444    const root = mixer.getRoot();
445    mixer.removeEventListener("finished", stopMixer);
446    mixer.stopAllAction();
447    mixer.uncacheRoot(root);
448    mixer = null;
449  }
450}
451
452function configureHighlightAnimation(itemCondition) {
453  const conditionColorValue = itemConditionMaterialColorTokens[itemCondition]
454    ? colorValueFromToken(itemConditionMaterialColorTokens[itemCondition])
455    : materialColorValue;
456  const highlightColorTrack = new THREE.ColorKeyframeTrack(
457    ".material.color",
458    [0, 2, 3],
459    [highlightColorValue, conditionColorValue, highlightColorValue].flat(),
460    THREE.InterpolateDiscrete,
461  );
462  highlightClip = new THREE.AnimationClip("Action", 3, [highlightColorTrack]);
463  highlightMaterial?.color.set(0xfacc15);
464}
465
466function colorValueFromToken(colorToken) {
467  const probe = document.createElement("span");
468  probe.style.color = `var(${colorToken})`;
469  document.body.appendChild(probe);
470  const renderedColor = getComputedStyle(probe).color;
471  probe.remove();
472
473  const canvas = document.createElement("canvas");
474  const context = canvas.getContext("2d");
475  context.fillStyle = renderedColor;
476  context.fillRect(0, 0, 1, 1);
477  return Array.from(context.getImageData(0, 0, 1, 1).data.slice(0, 3), (channel) =>
478    channel / 255,
479  );
480}
481
482function highlightLocation(locationMesh) {
483  if (locationMesh !== undefined) {
484    highlightMaterial.opacity = 1.0;
485    highlightMaterial.transparent = false;
486    locationMesh.material = highlightMaterial;
487    highlightMaterial.needsUpdate = true;
488  }
489}
490
491function resetHighlightLocation(locationMesh) {
492  if (locationMesh !== undefined) {
493    locationMesh.material = material;
494    material.needsUpdate = true;
495  }
496}
497
498function toggleCoordinate() {
499  coordinateVisible = !coordinateVisible;
500  if (!wcs) {
501    const box = new THREE.Box3().setFromObject(mainObject);
502    minDim = Math.min(box.max.x - box.min.x, box.max.y - box.min.y, box.max.z - box.min.z);
503    wcs = new THREE.AxesHelper(minDim / 5);
504    wcs.setColors(0xdc2626, 0x16a34a, 0x2563eb);
505    wcs.name = "WCS";
506    const text = document.createElement("div");
507    const text_hidden = coordinateVisible ? "" : "hidden";
508    text.className = `coordinateLabel ${text_hidden}`;
509    text.style.fontSize = "14px";
510    text.style.fontFamily = "monospace";
511    text.style.color = "#2563eb";
512    text.textContent = wcs.name;
513    const wcsLabel = new CSS2DObject(text);
514    wcsLabel.position.set(minDim / 8, minDim / 8, minDim / 8);
515    wcs.add(wcsLabel);
516    wcs.visible = coordinateVisible;
517    scene.add(wcs);
518  }
519  scene.traverse((object) => {
520    if (object.type === "AxesHelper") {
521      object.visible = coordinateVisible;
522    }
523  });
524  for (const label of labelRenderer.domElement.getElementsByClassName("coordinateLabel")) {
525    if (coordinateVisible) {
526      label.classList.remove("hidden");
527    } else {
528      label.classList.add("hidden");
529    }
530  }
531  renderer.render(scene, currentCamera);
532  labelRenderer.render(scene, currentCamera);
533}
534
535function zoomToFit() {
536  const box = new THREE.Box3().setFromObject(mainObject);
537  const sphere = new THREE.Sphere();
538  box.getBoundingSphere(sphere);
539  cameraControls.fitToSphere(sphere, false);
540  renderer.render(scene, currentCamera);
541}
542
543function setUpDirection(targetObject, direction) {
544  const box = new THREE.Box3().setFromObject(targetObject);
545  switch (direction) {
546    case "x":
547      targetObject.rotation.set(0, -DEG90, 0);
548      targetObject.position.setX(box.getSize(new THREE.Vector3()).z);
549      break;
550    case "y":
551      targetObject.rotation.set(DEG90, 0, 0);
552      targetObject.position.setY(box.getSize(new THREE.Vector3()).z);
553      break;
554    default:
555      break;
556  }
557}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.