Spaces:
Running on Zero
Running on Zero
File size: 6,791 Bytes
77e37fc af54811 77e37fc af54811 77e37fc | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 | from __future__ import annotations
import json
from pathlib import Path
from typing import Iterable
import numpy as np
import trimesh
def _sample_points(points: np.ndarray, max_points: int = 3500) -> np.ndarray:
if len(points) <= max_points:
return points.astype(float)
idx = np.linspace(0, len(points) - 1, max_points).astype(int)
return points[idx].astype(float)
def load_points_from_cloud_file(path: str | Path, max_points: int = 3500) -> np.ndarray:
cloud = trimesh.load(path, force="mesh")
if isinstance(cloud, trimesh.points.PointCloud):
points = np.asarray(cloud.vertices)
elif isinstance(cloud, trimesh.Trimesh):
if len(cloud.faces) > 0:
count = min(max_points * 2, max(1200, len(cloud.faces) * 3))
points = cloud.sample(count)
else:
points = np.asarray(cloud.vertices)
else:
points = np.asarray(getattr(cloud, "vertices", []))
return _sample_points(np.asarray(points, dtype=float), max_points=max_points)
def load_points_from_mesh_file(path: str | Path, max_points: int = 3500) -> np.ndarray:
mesh = trimesh.load(path, force="mesh")
if isinstance(mesh, trimesh.Scene):
mesh = trimesh.util.concatenate([g for g in mesh.geometry.values() if isinstance(g, trimesh.Trimesh)])
if isinstance(mesh, trimesh.Trimesh):
if len(mesh.faces) > 0:
count = min(max_points * 2, max(1600, len(mesh.faces) * 2))
points = mesh.sample(count)
else:
points = np.asarray(mesh.vertices)
else:
points = np.asarray(getattr(mesh, "vertices", []))
return _sample_points(np.asarray(points, dtype=float), max_points=max_points)
def empty_viewer_html(message: str = "Generate a blueprint to preview it here.") -> str:
return f"""
<div style='height:520px;border-radius:20px;border:1px solid rgba(255,255,255,.08);display:flex;align-items:center;justify-content:center;background:linear-gradient(180deg,#06070a,#0b1020);color:#cfd6ff;font-family:Inter,system-ui,sans-serif;'>
<div style='text-align:center;padding:18px 24px;max-width:420px;'>
<div style='font-size:1.12rem;font-weight:700;margin-bottom:6px;'>Blueprint Viewer</div>
<div style='opacity:.84;line-height:1.45'>{message}</div>
</div>
</div>
"""
def point_cloud_viewer_html(points: np.ndarray, status: str = "Blueprint") -> str:
points = np.asarray(points, dtype=float)
if points.size == 0:
return empty_viewer_html("No points to display yet.")
points = _sample_points(points)
mins = points.min(axis=0)
maxs = points.max(axis=0)
center = (mins + maxs) / 2.0
span = float(np.max(maxs - mins)) or 1.0
normalized = (points - center) / span
color_src = normalized - normalized.min(axis=0, keepdims=True)
denom = color_src.ptp(axis=0, keepdims=True)
denom[denom == 0] = 1.0
colors = np.clip(color_src / denom, 0.0, 1.0)
points_payload = np.round(normalized, 5).tolist()
colors_payload = np.round(colors, 5).tolist()
return f"""
<div style="height:560px;border-radius:20px;overflow:hidden;border:1px solid rgba(255,255,255,.08);background:radial-gradient(circle at 50% 20%, #0f1630, #05070d 75%);position:relative;">
<div id="pb3d-label" style="position:absolute;top:12px;left:12px;z-index:2;background:rgba(16,21,40,.75);border:1px solid rgba(255,255,255,.08);backdrop-filter:blur(8px);padding:10px 12px;border-radius:14px;color:#eef2ff;font:600 14px/1.3 Inter,system-ui,sans-serif;">{status}<div style="opacity:.75;font-weight:500;margin-top:4px">One finger orbit • two fingers pan/zoom</div></div>
<canvas id="pb3d-canvas" style="width:100%;height:100%;display:block;touch-action:none"></canvas>
</div>
<script src="https://unpkg.com/three@0.160.0/build/three.min.js"></script>
<script src="https://unpkg.com/three@0.160.0/examples/js/controls/OrbitControls.js"></script>
<script>
(() => {{
const root = document.currentScript.previousElementSibling.previousElementSibling.previousElementSibling;
const canvas = root.querySelector('canvas');
const holder = root;
if (!window.THREE || !window.THREE.OrbitControls) {{
holder.innerHTML = `<div style='height:100%;display:flex;align-items:center;justify-content:center;color:#eef2ff;font-family:Inter,system-ui,sans-serif;'>Viewer failed to load.</div>`;
return;
}}
const pts = {json.dumps(points_payload)};
const cols = {json.dumps(colors_payload)};
const THREE = window.THREE;
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(50, holder.clientWidth / holder.clientHeight, 0.01, 100);
camera.position.set(1.8, 1.4, 2.2);
const renderer = new THREE.WebGLRenderer({{canvas, antialias:true, alpha:true}});
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
renderer.setSize(holder.clientWidth, holder.clientHeight, false);
renderer.outputColorSpace = THREE.SRGBColorSpace;
const controls = new THREE.OrbitControls(camera, canvas);
controls.enableDamping = true;
controls.enablePan = true;
controls.minDistance = 0.4;
controls.maxDistance = 8;
controls.target.set(0,0,0);
const positions = new Float32Array(pts.length * 3);
const colors = new Float32Array(cols.length * 3);
for (let i = 0; i < pts.length; i++) {{
positions[i*3] = pts[i][0];
positions[i*3+1] = pts[i][2];
positions[i*3+2] = pts[i][1];
colors[i*3] = cols[i][0] * 0.85 + 0.15;
colors[i*3+1] = cols[i][1] * 0.85 + 0.15;
colors[i*3+2] = cols[i][2] * 0.85 + 0.15;
}}
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const material = new THREE.PointsMaterial({{size: 0.025, sizeAttenuation: true, vertexColors:true}});
const cloud = new THREE.Points(geometry, material);
scene.add(cloud);
const grid = new THREE.GridHelper(2.4, 12, 0x4c5cff, 0x1d2645);
grid.position.y = -0.72;
scene.add(grid);
const axes = new THREE.AxesHelper(0.7);
scene.add(axes);
const lightA = new THREE.DirectionalLight(0xffffff, 1.8);
lightA.position.set(2, 3, 2);
scene.add(lightA);
const lightB = new THREE.DirectionalLight(0x7795ff, 0.9);
lightB.position.set(-2, -1, -1.5);
scene.add(lightB);
scene.add(new THREE.AmbientLight(0xb8c8ff, 0.8));
function resize() {{
const w = holder.clientWidth;
const h = holder.clientHeight;
camera.aspect = w / h;
camera.updateProjectionMatrix();
renderer.setSize(w, h, false);
}}
window.addEventListener('resize', resize);
function tick() {{
controls.update();
renderer.render(scene, camera);
requestAnimationFrame(tick);
}}
tick();
}})();
</script>
"""
|