← Commit history

Migrate asset from Helium wiki

Kyle Bergstedt ·c5c6783ff3 ·4mo ago ·parent 8bdf4cf
1 file changed +534
viewer-sot-23-6.htmladded+534
@@ -0,0 +1,534 @@+<!DOCTYPE html>+<html>+<head><script>(function(){var n=0;function show(p,m){var el=document.createElement('div');el.style.cssText='position:fixed;left:0;right:0;background:#e04040;color:#fff;padding:6px 12px;z-index:'+(99999-n)+';font:13px/1.4 monospace;white-space:pre-wrap;border-bottom:1px solid #b02020;top:'+n*30+'px';el.textContent=p+m;document.body.appendChild(el);n++}window.onerror=function(msg,src,line){show('ERROR '+(src||'').split('/').pop()+':'+line+' \u2014 ',msg)};window.addEventListener('unhandledrejection',function(e){show('REJECT \u2014 ',e.reason&&e.reason.message||e.reason||'unknown')})})()</script>+<meta charset="utf-8">+<title>Basic3dView — SOT-23-6</title>+<style>+* { margin: 0; padding: 0; box-sizing: border-box; }+body {+  overflow: hidden;+  height: 100vh;+  display: flex;+  flex-direction: column;+  background: #1a1a1a;+  color: #ccc;+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;+}+#viewer-wrap {+  flex: 1;+  position: relative;+  min-height: 0;+}+.viewer-container { position: relative; width: 100%; height: 100%; overflow: hidden; }+.viewer-container canvas { display: block; width: 100%; height: 100%; outline: none; }++#toolbar {+  position: absolute;+  top: 8px;+  left: 8px;+  z-index: 20;+  display: flex;+  gap: 1px;+  background: #1a1a1a;+  border: 1px solid #333;+  border-radius: 6px;+  padding: 2px;+}+#toolbar button {+  display: flex;+  align-items: center;+  justify-content: center;+  width: 32px;+  height: 28px;+  background: transparent;+  border: none;+  border-radius: 4px;+  cursor: pointer;+  transition: background 0.12s;+  position: relative;+}+#toolbar button svg {+  width: 16px;+  height: 16px;+  fill: none;+  stroke: #888;+  stroke-width: 1.8;+  stroke-linecap: round;+  stroke-linejoin: round;+}+#toolbar button:hover { background: #2a2a2a; }+#toolbar button:hover svg { stroke: #ccc; }+#toolbar button.active svg { stroke: #00b8b1; }+#toolbar button:active { background: #333; }+#toolbar button::after {+  content: attr(data-tip);+  position: absolute;+  top: calc(100% + 6px);+  left: 50%;+  transform: translateX(-50%);+  background: #1a1a1a;+  color: #ccc;+  font-size: 11px;+  padding: 4px 8px;+  border-radius: 4px;+  border: 1px solid #333;+  white-space: nowrap;+  pointer-events: none;+  opacity: 0;+  transition: opacity 0.15s;+  z-index: 30;+}+#toolbar button:hover::after { opacity: 1; }++#caption-bar {+  display: none;+  padding: 8px 14px;+  background: rgba(13, 17, 23, 0.85);+  border-top: 1px solid #222;+  font-size: 13px;+  color: #b0b8c4;+  line-height: 1.4;+}+#caption-bar.visible { display: block; }+#info-bar {+  display: flex;+  align-items: center;+  gap: 12px;+  padding: 6px 12px;+  background: #111;+  border-top: 1px solid #333;+  font-size: 12px;+  color: #888;+  min-height: 32px;+}+#info-bar .label { color: #00b8b1; font-weight: 600; }+#info-bar .viewer-name { margin-left: auto; color: #00b8b1; font-weight: 600; }++#loading {+  position: absolute;+  top: 50%;+  left: 50%;+  transform: translate(-50%, -50%);+  color: #888;+  font-size: 14px;+  z-index: 10;+}+++/* Drag-drop overlay */+#drop-overlay {+  display: none;+  position: absolute;+  top: 0; left: 0; right: 0; bottom: 0;+  background: rgba(0, 184, 177, 0.15);+  border: 3px dashed #00b8b1;+  z-index: 100;+  align-items: center;+  justify-content: center;+  font-size: 18px;+  color: #00b8b1;+  font-weight: 600;+}+#drop-overlay.active { display: flex; }++/* Mesh list panel */+#mesh-panel {+  display: none;+  position: absolute;+  top: 8px;+  right: 8px;+  z-index: 20;+  background: #1a1a1a;+  border: 1px solid #333;+  border-radius: 6px;+  padding: 8px;+  max-height: 60vh;+  overflow-y: auto;+  min-width: 200px;+  max-width: 300px;+  font-size: 11px;+}+#mesh-panel.open { display: block; }+#mesh-panel h3 {+  font-size: 12px;+  color: #00b8b1;+  margin-bottom: 6px;+  font-weight: 600;+}+#mesh-panel .mesh-item {+  padding: 3px 6px;+  border-radius: 3px;+  cursor: pointer;+  display: flex;+  justify-content: space-between;+  gap: 8px;+}+#mesh-panel .mesh-item:hover { background: #2a2a2a; }+#mesh-panel .mesh-item .name { color: #ccc; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }+#mesh-panel .mesh-item .verts { color: #666; flex-shrink: 0; }+</style>+</head>+<body>+<div id="viewer-wrap">+  <div id="loading">Loading 3D engine...</div>+  <div id="drop-overlay">Drop GLB file here</div>+  <div id="toolbar" style="display:none">+    <button id="btn-home" data-tip="Reset camera">+      <svg viewBox="0 0 24 24"><path d="M3 12l9-9 9 9"/><path d="M9 21V12h6v9"/></svg>+    </button>+    <button id="btn-frame" data-tip="Frame model">+      <svg viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="12" cy="12" r="3"/></svg>+    </button>+    <button id="btn-ground" data-tip="Toggle ground" class="active">+      <svg viewBox="0 0 24 24"><path d="M2 20L12 4l10 16H2z"/></svg>+    </button>+    <button id="btn-wireframe" data-tip="Toggle wireframe">+      <svg viewBox="0 0 24 24"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>+    </button>+    <button id="btn-axes" data-tip="Toggle axes">+      <svg viewBox="0 0 24 24"><path d="M12 2v20M2 12h20"/><path d="M12 2l-2 4M12 2l2 4"/><path d="M22 12l-4-2M22 12l-4 2"/></svg>+    </button>+    <button id="btn-meshes" data-tip="Mesh list">+      <svg viewBox="0 0 24 24"><path d="M4 6h16M4 12h16M4 18h16"/></svg>+    </button>+  </div>+  <div id="mesh-panel"><h3>Meshes</h3><div id="mesh-list"></div></div>+</div>+<div id="caption-bar"></div>+<div id="info-bar">+  <span id="info-text">Drag & drop a .glb file or load via MCP tool</span>+  <span class="viewer-name">Basic3dView</span>+</div>++<script>+var viewer = null;+var loadingEl = document.getElementById('loading');+var toolbarEl = document.getElementById('toolbar');+var groundVisible = true;+var wireframeMode = false;+var axesVisible = false;+var axesMeshes = [];+var meshPanelOpen = false;++// ── Drag & Drop ────────────────────────────────────────────────+var dropOverlay = document.getElementById('drop-overlay');+var dragCounter = 0;++document.addEventListener('dragenter', function(e) {+  e.preventDefault();+  dragCounter++;+  dropOverlay.classList.add('active');+});+document.addEventListener('dragleave', function(e) {+  e.preventDefault();+  dragCounter--;+  if (dragCounter <= 0) {+    dragCounter = 0;+    dropOverlay.classList.remove('active');+  }+});+document.addEventListener('dragover', function(e) {+  e.preventDefault();+});+document.addEventListener('drop', function(e) {+  e.preventDefault();+  dragCounter = 0;+  dropOverlay.classList.remove('active');++  var files = e.dataTransfer.files;+  for (var i = 0; i < files.length; i++) {+    var file = files[i];+    if (file.name.toLowerCase().endsWith('.glb') || file.name.toLowerCase().endsWith('.gltf')) {+      loadFromFile(file);+      return;+    }+  }+});++function loadFromFile(file) {+  if (!viewer) return;+  loadingEl.style.display = '';+  loadingEl.textContent = 'Loading ' + file.name + '...';++  var url = URL.createObjectURL(file);+  viewer.clearScene();++  viewer.loadModel(url).then(function() {+    URL.revokeObjectURL(url);+    loadingEl.style.display = 'none';+    viewer.frameModel();+    fixNearClip();+    updateInfo(file.name);+    populateMeshList();+    parent.postMessage(JSON.stringify({ type: 'basic_3d_loaded' }), '*');+  }).catch(function(err) {+    URL.revokeObjectURL(url);+    loadingEl.textContent = 'Load failed: ' + (err.message || err);+    loadingEl.style.color = '#e04040';+    parent.postMessage(JSON.stringify({ type: 'basic_3d_loaded', error: true }), '*');+  });+}++// ── Load from URL ──────────────────────────────────────────────+function loadFromUrl(modelUrl, options) {+  if (!viewer) {+    window._pendingModel = { modelUrl: modelUrl, options: options || {} };+    return;+  }+  options = options || {};++  loadingEl.style.display = '';+  loadingEl.textContent = 'Loading 3D model...';++  var fullUrl = resolveModelUrl(modelUrl);+  viewer.clearScene();++  viewer.loadModel(fullUrl).then(function() {+    loadingEl.style.display = 'none';+    viewer.frameModel();+    fixNearClip();+    updateInfo(options.title || options.fileName || modelUrl.split('/').pop());+    setCaption(options.caption || '');+    populateMeshList();+    parent.postMessage(JSON.stringify({ type: 'basic_3d_loaded' }), '*');+  }).catch(function(err) {+    loadingEl.textContent = 'Load failed: ' + (err.message || err);+    loadingEl.style.color = '#e04040';+    parent.postMessage(JSON.stringify({ type: 'basic_3d_loaded', error: true }), '*');+  });+}++function resolveModelUrl(modelUrl) {+  if (modelUrl.startsWith('http://') || modelUrl.startsWith('https://') || modelUrl.startsWith('blob:')) {+    return modelUrl;+  }+  var base = location.pathname.replace(/\/[^/]*$/, '');+  return base + (modelUrl.startsWith('/') ? '' : '/') + modelUrl;+}++function fixNearClip() {+  var cam = viewer.getCamera();+  if (cam && cam.radius < 0.1) {+    cam.minZ = cam.radius / 1000;+  }+}++function setCaption(text) {+  var el = document.getElementById('caption-bar');+  if (text) {+    el.textContent = text;+    el.classList.add('visible');+  } else {+    el.textContent = '';+    el.classList.remove('visible');+  }+}++function updateInfo(name) {+  var scene = viewer.getScene();+  var meshCount = 0;+  var vertCount = 0;+  scene.meshes.forEach(function(m) {+    if (m.getTotalVertices && m.getTotalVertices() > 0) {+      meshCount++;+      vertCount += m.getTotalVertices();+    }+  });+  var info = name;+  info += ' \u2014 ' + meshCount + ' meshes, ' + vertCount.toLocaleString() + ' verts';+  document.getElementById('info-text').textContent = info;+}++function populateMeshList() {+  var scene = viewer.getScene();+  var listEl = document.getElementById('mesh-list');+  listEl.innerHTML = '';+  scene.meshes.forEach(function(m) {+    if (!m.getTotalVertices || m.getTotalVertices() === 0) return;+    var div = document.createElement('div');+    div.className = 'mesh-item';+    var nameSpan = document.createElement('span');+    nameSpan.className = 'name';+    nameSpan.textContent = m.name || '(unnamed)';+    nameSpan.title = m.name || '(unnamed)';+    var vertSpan = document.createElement('span');+    vertSpan.className = 'verts';+    vertSpan.textContent = m.getTotalVertices().toLocaleString() + 'v';+    div.appendChild(nameSpan);+    div.appendChild(vertSpan);+    div.onclick = function() {+      // Highlight mesh briefly+      var origColor = null;+      var mat = m.material;+      if (mat) {+        origColor = mat.emissiveColor ? mat.emissiveColor.clone() : null;+        mat.emissiveColor = new window.Adom3DViewer.BABYLON.Color3(0, 0.72, 0.69);+        setTimeout(function() {+          if (origColor) mat.emissiveColor = origColor;+          else mat.emissiveColor = new window.Adom3DViewer.BABYLON.Color3(0, 0, 0);+        }, 1500);+      }+    };+    listEl.appendChild(div);+  });+}++// ── postMessage API ────────────────────────────────────────────+window.addEventListener('message', function(e) {+  var msg;+  try { msg = JSON.parse(e.data); } catch(ex) { return; }+  if (msg.type === 'load_model') {+    loadFromUrl(msg.modelUrl, msg.options);+  } else if (msg.type === 'basic3d_toggle') {+    var btn = document.getElementById('btn-' + msg.name);+    if (btn) btn.click();+  } else if (msg.type === 'basic3d_set_camera') {+    var cam = viewer && viewer.getCamera();+    if (cam) {+      if (msg.alpha !== undefined) cam.alpha = msg.alpha;+      if (msg.beta !== undefined) cam.beta = msg.beta;+      if (msg.radius !== undefined) cam.radius = msg.radius;+    }+  } else if (msg.type === 'basic3d_set_caption') {+    setCaption(msg.text || '');+  } else if (msg.type === 'basic3d_fill') {+    // Fill entire iframe with a solid color (for screenshot calibration)+    var overlay = document.getElementById('calibration-overlay');+    if (msg.color) {+      if (!overlay) {+        overlay = document.createElement('div');+        overlay.id = 'calibration-overlay';+        overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;z-index:9999;';+        document.body.appendChild(overlay);+      }+      overlay.style.background = msg.color;+      overlay.style.display = 'block';+    } else if (overlay) {+      overlay.style.display = 'none';+    }+  }+});++// ── Init Babylon.js ────────────────────────────────────────────+var base = location.pathname.replace(/\/[^/]*$/, '');+var script = document.createElement('script');+script.src = base + '/adom-3d-viewer.min.js?v=' + Date.now();+script.onload = function() {+  loadingEl.textContent = 'Initializing viewer...';+  try {+    var wrap = document.getElementById('viewer-wrap');+    loadingEl.style.display = 'none';++    var envUrl = base + '/environmentSpecular.env';+    viewer = window.Adom3DViewer.init(wrap, {+      zUp: true,+      showViewCube: true,+      showGround: true,+      environmentUrl: envUrl,+    });++    toolbarEl.style.display = '';+    initToolbar();++    parent.postMessage(JSON.stringify({ type: 'basic_3d_ready' }), '*');++      // Auto-load model for wiki demo+      (function() {+        var glbUrl = 'sot-23-6.glb';+        loadFromUrl(glbUrl, { title: 'SOT-23-6', caption: 'SOT-23-6 floating in space — ground plane toggled off' });+        // Toggle features after model loads+        var poll = setInterval(function() {+          if (document.getElementById('loading').style.display === 'none' && viewer) {+            clearInterval(poll);+            document.getElementById('btn-ground').click();+          }+        }, 200);+      })();++    if (window._pendingModel) {+      var pending = window._pendingModel;+      window._pendingModel = null;+      loadFromUrl(pending.modelUrl, pending.options);+    }+  } catch (err) {+    loadingEl.style.display = '';+    loadingEl.textContent = 'Failed to init viewer: ' + err.message;+    loadingEl.style.color = '#e04040';+  }+};+script.onerror = function() {+  loadingEl.textContent = 'Failed to load 3D viewer bundle';+  loadingEl.style.color = '#e04040';+};+document.head.appendChild(script);++// ── Toolbar ────────────────────────────────────────────────────+function initToolbar() {+  document.getElementById('btn-home').onclick = function() {+    if (viewer) viewer.goHome();+  };+  document.getElementById('btn-frame').onclick = function() {+    if (viewer) viewer.frameModel();+  };+  document.getElementById('btn-ground').onclick = function() {+    groundVisible = !groundVisible;+    if (viewer) viewer.setGroundVisible(groundVisible);+    this.classList.toggle('active', groundVisible);+  };+  document.getElementById('btn-wireframe').onclick = function() {+    wireframeMode = !wireframeMode;+    var scene = viewer.getScene();+    scene.meshes.forEach(function(m) {+      if (m.material) m.material.wireframe = wireframeMode;+    });+    this.classList.toggle('active', wireframeMode);+  };+  document.getElementById('btn-axes').onclick = function() {+    axesVisible = !axesVisible;+    toggleAxes(axesVisible);+    this.classList.toggle('active', axesVisible);+  };+  document.getElementById('btn-meshes').onclick = function() {+    meshPanelOpen = !meshPanelOpen;+    document.getElementById('mesh-panel').classList.toggle('open', meshPanelOpen);+    this.classList.toggle('active', meshPanelOpen);+  };+}++function toggleAxes(show) {+  if (!viewer) return;+  var scene = viewer.getScene();+  var BABYLON = window.Adom3DViewer.BABYLON;++  // Remove existing+  axesMeshes.forEach(function(m) { m.dispose(); });+  axesMeshes = [];++  if (!show) return;++  var len = 5;+  var colors = [+    new BABYLON.Color3(1, 0.2, 0.2),   // X = red+    new BABYLON.Color3(0.2, 1, 0.2),   // Y = green+    new BABYLON.Color3(0.3, 0.5, 1),   // Z = blue+  ];+  var dirs = [+    new BABYLON.Vector3(len, 0, 0),+    new BABYLON.Vector3(0, len, 0),+    new BABYLON.Vector3(0, 0, len),+  ];+  var labels = ['X', 'Y', 'Z'];++  for (var i = 0; i < 3; i++) {+    var points = [BABYLON.Vector3.Zero(), dirs[i]];+    var lines = BABYLON.MeshBuilder.CreateLines('axis_' + labels[i], {+      points: points,+    }, scene);+    lines.color = colors[i];+    lines.isPickable = false;+    axesMeshes.push(lines);+  }+}+</script>+</body>+</html>