diff --git a/.gitignore b/.gitignore index 82b4e8f..c1eca77 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,15 @@ *.stl !data/*.stl __pycache__ +out/ + +node_modules +a.* + +openscad.wasm +openscad.wasm.js +worker.js + +# Added by cargo + +/target diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..17ba462 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "webapp/openscad-wasm"] + path = webapp/openscad-wasm + url = git@github.com:openscad/openscad-wasm.git diff --git a/flake.lock b/flake.lock index 786f446..dc9c41a 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1774106199, - "narHash": "sha256-US5Tda2sKmjrg2lNHQL3jRQ6p96cgfWh3J1QBliQ8Ws=", + "lastModified": 1783522502, + "narHash": "sha256-iffAls3iaNTyJC2faYcUXSI+Gp02cDjYl+MygxKl2GI=", "owner": "nixos", "repo": "nixpkgs", - "rev": "6c9a78c09ff4d6c21d0319114873508a6ec01655", + "rev": "0bb7ec54c8483066ec9d7720e780a5caa71f8612", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 9a89640..10147b9 100644 --- a/flake.nix +++ b/flake.nix @@ -19,6 +19,13 @@ pkgs.cairo pkgs.openscad pkgs.python3 + pkgs.nodejs + + # openscad-wasm build dependencies + pkgs.gnumake + pkgs.git + pkgs.wget + pkgs.deno ]; nativeBuildInputs = with pkgs; [ diff --git a/webapp/app.ts b/webapp/app.ts new file mode 100644 index 0000000..48201a0 --- /dev/null +++ b/webapp/app.ts @@ -0,0 +1,1126 @@ +import * as THREE from "three"; +import { STLLoader } from "three/addons/loaders/STLLoader.js"; +import { OBJLoader } from "three/addons/loaders/OBJLoader.js"; +import { OrbitControls } from "three/addons/controls/OrbitControls.js"; +import { mergeGeometries } from "three/addons/utils/BufferGeometryUtils.js"; +import { VertexPrintOutputs, VertexPrintParams, vertexPrint } from "./core" +import Matrix from "ml-matrix"; +import JSZip from "jszip"; + +const DATA_DIR = "data/"; +const DEFAULT_MODEL = "DisdyakisTriacontahedron.obj"; + +const V_FG = cssVar("--color-v-fg"); +const V_BG = cssVar("--color-v-bg"); +const V_DARK = cssVar("--color-v-dark"); +const V_BORDER = cssVar("--color-v-border"); +const V_BLUE = cssVar("--color-v-blue"); + +const PARAMS = new VertexPrintParams(); + +// utils --- +function clamp(v: number, min: number, max: number): number { + return Math.min(max, Math.max(min, v)); +} + +function cssVar(name: string): number { + const hex = getComputedStyle(document.documentElement) + .getPropertyValue(name) + .trim(); + return parseInt(hex.replace("#", ""), 16); +} + +function makeOrientationCube(renderer: THREE.WebGLRenderer): + [THREE.Mesh, THREE.Scene, THREE.OrthographicCamera] { + const scene = new THREE.Scene(); + const cam = new THREE.OrthographicCamera(); + cam.position.set(0, 0, 1); + + const FACE_COLOR = '#' + V_BORDER.toString(16).padStart(6, '0'); + const TEXT_COLOR = '#' + V_FG.toString(16).padStart(6, '0'); + const TEXTURE_SIZE = 256; + + const maxAnisotropy = renderer.capabilities.getMaxAnisotropy(); + function makeFaceLabel(text: string): THREE.CanvasTexture { + const canvas = document.createElement("canvas"); + canvas.width = canvas.height = TEXTURE_SIZE; + const context = canvas.getContext("2d")!; + context.fillStyle = FACE_COLOR; + context.fillRect(0, 0, TEXTURE_SIZE, TEXTURE_SIZE); + context.fillStyle = TEXT_COLOR; + context.font = `48px monospace`; + context.textAlign = "center"; + context.textBaseline = "middle"; + context.fillText(text, TEXTURE_SIZE / 2, TEXTURE_SIZE / 2); + const texture = new THREE.CanvasTexture(canvas); + texture.colorSpace = THREE.SRGBColorSpace; + texture.anisotropy = maxAnisotropy; + return texture; + } + + const faceLabels: string[] = [ + "Right", // +x + "Left", // -x + "Top", // +y + "Bottom", // -y + "Front", // +z + "Back" // -z + ]; + + const cubeMaterials = faceLabels.map(label => { + const texture = makeFaceLabel(label); + return new THREE.MeshPhongMaterial({ + map: texture, + }); + }); + + const cube = new THREE.Mesh( + new THREE.BoxGeometry(0.75, 0.75, 0.75), + cubeMaterials + ); + scene.add(cube); + scene.add(new THREE.AmbientLight(0xffffff, 1)); + + const directionalLight = new THREE.DirectionalLight(V_FG, 2); + directionalLight.position.set(1, 1, 1); + scene.add(directionalLight); + + return [cube, scene, cam] +} + +// Canvas logic. Placeholder. --- + +class Canvas { + scene: THREE.Scene; + camera: THREE.OrthographicCamera; + controls: OrbitControls; + root: THREE.Group; + viewHalfHeight: number; + + currentMesh: THREE.Mesh | null; + currentName: string; + currentData: ArrayBuffer; + + currentMeshes: THREE.Mesh[]; + vertexCount: number; + vertexMaterial: THREE.MeshStandardMaterial; + edgeMaterial: THREE.MeshStandardMaterial; + meshMaterial: THREE.MeshStandardMaterial; + highlightMaterial: THREE.MeshStandardMaterial; + highlighted: number[]; + + constructor() { + this.currentMesh = null; + this.currentMeshes = []; + this.vertexMaterial = new THREE.MeshStandardMaterial({ + color: V_BLUE, + flatShading: false, + }); + this.edgeMaterial = new THREE.MeshStandardMaterial({ + color: 0xffffff, + flatShading: false, + }); + this.meshMaterial = new THREE.MeshStandardMaterial({ + color: V_BLUE, + flatShading: false, + }); + this.highlightMaterial = new THREE.MeshStandardMaterial({ + color: 0xff0000, + flatShading: false, + }); + this.currentName = DEFAULT_MODEL; + this.currentData = new ArrayBuffer(0); + this.highlighted = []; + this.vertexCount = 0; + + this.initCanvas(); + } + + async initCanvas() { + this.scene = new THREE.Scene(); + this.scene.background = new THREE.Color(V_BG); + + this.root = new THREE.Group(); + this.root.rotation.x = -Math.PI / 2; + this.scene.add(this.root); + + this.camera = new THREE.OrthographicCamera(); + this.camera.position.set(20, 15, 20); + this.camera.near = 0.1; + this.camera.far = 1000; + this.viewHalfHeight = 15; + this.updateFrustum(); + + const renderer = new THREE.WebGLRenderer({ antialias: true }); + renderer.setSize(window.innerWidth, window.innerHeight); + renderer.domElement.style.position = "fixed"; + renderer.domElement.style.top = "0"; + renderer.domElement.style.left = "0"; + renderer.setPixelRatio(window.devicePixelRatio); + document.body.prepend(renderer.domElement); + + this.controls = new OrbitControls(this.camera, renderer.domElement); + this.controls.update(); + + this.scene.add(new THREE.HemisphereLight(V_FG, V_BG, 1)); + + const keyLight = new THREE.DirectionalLight(V_FG, 2); + this.camera.add(keyLight); + this.camera.add(keyLight.target); + keyLight.position.set(0, 0, 0); + keyLight.target.position.set(0, 0, -1); + + const fillLight = new THREE.DirectionalLight(V_FG, 0.8); + this.camera.add(fillLight); + this.camera.add(fillLight.target); + fillLight.position.set(1, -1, 0); + fillLight.target.position.set(0, 0, -1); + + this.scene.add(this.camera); + + const mesh = new THREE.Mesh(new THREE.BufferGeometry(), this.meshMaterial); + this.root.add(mesh); + this.currentMesh = mesh; + + const [cube, cubeScene, cubeCamera] = makeOrientationCube(renderer); + + const ORENT_CUBE_SIZE = 240; + renderer.autoClear = false; + renderer.setScissorTest(true); + + const animate = () => { + requestAnimationFrame(animate); + this.controls.update(); + + const W = window.innerWidth; + const H = window.innerHeight; + + // Full-screen main scene + renderer.setViewport(0, 0, W, H); + renderer.setScissor(0, 0, W, H); + renderer.clear(); + renderer.render(this.scene, this.camera); + + // Counter-rotate the orientation cube so its faces track the camera's + // orientation (ie the cube shows where the camera looks). + cube.quaternion.copy(this.camera.quaternion).invert(); + + const gx = W - ORENT_CUBE_SIZE; // right edge + const gy = 0; // bottom edge (GL y is measured from bottom) + renderer.setViewport(gx, gy, ORENT_CUBE_SIZE, ORENT_CUBE_SIZE); + renderer.setScissor(gx, gy, ORENT_CUBE_SIZE, ORENT_CUBE_SIZE); + renderer.clearDepth(); + renderer.render(cubeScene, cubeCamera); + }; + animate(); + + window.addEventListener("resize", () => { + this.updateFrustum(); + renderer.setSize(window.innerWidth, window.innerHeight); + }); + } + + // Recompute the orthographic view frustum from the current half-height + // and window aspect ratio. + updateFrustum() { + const aspect = window.innerWidth / window.innerHeight; + const h = this.viewHalfHeight; + const w = h * aspect; + this.camera.left = -w; + this.camera.right = w; + this.camera.top = h; + this.camera.bottom = -h; + this.camera.updateProjectionMatrix(); + } + + // Reposition camera so every mesh currently in the scene is in frame, + // preserving camera orientation. + fitView() { + const box = new THREE.Box3().setFromObject(this.root); + if (box.isEmpty()) return; + const sphere = new THREE.Sphere(); + box.getBoundingSphere(sphere); + const radius = sphere.radius || 1; + const center = sphere.center; + + const dist = radius * 3; + + const dir = new THREE.Vector3() + .copy(this.camera.position) + .sub(this.controls.target) + .normalize(); + if (dir.lengthSq() === 0) dir.set(1, 0.75, 1).normalize(); + this.camera.position.copy(center).add(dir.multiplyScalar(dist)); + this.controls.target.copy(center); + this.controls.update(); + + const aspect = window.innerWidth / window.innerHeight; + this.viewHalfHeight = (radius / Math.min(1, aspect)) * 1.25; + + this.camera.near = Math.max(0.1, radius / 100); + this.camera.far = dist + radius * 10; + this.updateFrustum(); + } + + // Load and display geometry. The file extension determines how `data` is + // parsed. + loadGeometry(name: string, data: ArrayBuffer) { + if (!this.currentMesh) return; + this.clear(); + this.currentName = name; + this.currentData = data; + let geometry: THREE.BufferGeometry; + try { + const isObj = name.toLowerCase().endsWith(".obj"); + geometry = isObj + ? geometryFromObj(new TextDecoder().decode(data)) + : new STLLoader().parse(data); + } catch (e) { + // TODO: show user an error message + console.error("Failed to load", name, e); + return; + } + + const count = geometry.attributes.position?.count ?? 0; + if (count === 0) { + console.error("Failed to load", name, "malformed file: no geometry parsed"); + return; + } + + geometry.center(); + geometry.computeVertexNormals(); + this.currentMesh.geometry = geometry; + + disableDownloadButton(); + resetInspector(); + this.fitView(); + } + + loadVertexPrintOutputs(outputs: VertexPrintOutputs) { + this.clear() + const polyhedron = outputs.polyhedron; + this.vertexCount = polyhedron.vertexFigures.length; + for (let i = 0; i < polyhedron.vertexFigures.length; ++i) { + const geometry = new STLLoader().parse(outputs.stls[i]); + geometry.computeVertexNormals(); + const mesh = new THREE.Mesh(geometry, this.vertexMaterial); + const position = polyhedron.vertices.getRow(i); + const rotation = polyhedron.vertexFigures[i].euler; + const x = PARAMS.scale * position[0]; + const y = PARAMS.scale * position[1]; + const z = PARAMS.scale * position[2]; + mesh.position.set(x, y, z); + mesh.rotation.set(rotation[0], rotation[1], rotation[2], "ZYX"); + this.root.add(mesh); + this.currentMeshes.push(mesh) + } + + this.loadEdges(outputs) + + this.fitView(); + } + + // Draw edges. Identical to solids.scad/hedron_edges + loadEdges(outputs: VertexPrintOutputs) { + const polyhedron = outputs.polyhedron; + const radius = polyhedron.options.edgeDiameter / 2; + for (const [key, value] of polyhedron.edges) { + const [v1, v2] = key.split(",").map(Number); + const a = polyhedron.vertices.getRowVector(v1); + const b = polyhedron.vertices.getRowVector(v2); + const [o1, o2] = value.offsets; + + // v = b - a, as in the scad prototype + const v = Matrix.sub(b, a); + const dist = v.norm(); + const length = PARAMS.scale * dist - o1 - o2; + + if (length <= 0) { + continue; + } + + const vx = v.get(0, 0); + const vy = v.get(0, 1); + const vz = v.get(0, 2); + + const phi = Math.atan2(vy, vx); + const theta = Math.acos(vz / dist); + + const geometry = new THREE.CylinderGeometry(radius, radius, length, 16); + geometry.translate(0, length / 2, 0); + geometry.rotateX(Math.PI / 2); + + const mesh = new THREE.Mesh(geometry, this.edgeMaterial); + + // Position at the scaled vertex a, inset by o1 along the edge + const ux = vx / dist; + const uy = vy / dist; + const uz = vz / dist; + + mesh.position.set( + PARAMS.scale * a.get(0, 0) + o1 * ux, + PARAMS.scale * a.get(0, 1) + o1 * uy, + PARAMS.scale * a.get(0, 2) + o1 * uz, + ); + + mesh.rotation.set(0, theta, phi, "ZYX"); + + this.root.add(mesh); + this.currentMeshes.push(mesh); + } + } + + // Clear scene of all meshes + clear() { + for (const mesh of this.currentMeshes) { + mesh.geometry.dispose(); + this.root.remove(mesh); + } + this.currentMeshes = []; + if (this.currentMesh) { + this.currentMesh.geometry.dispose(); + this.currentMesh.geometry = new THREE.BufferGeometry(); + } + this.highlighted = []; + this.vertexCount = 0; + } + + highlightVertex(index: number) { + this.currentMeshes[index].material = this.highlightMaterial; + this.highlighted.push(index) + } + + highlightEdge(index: number) { + const meshIndex = this.vertexCount + index; + this.currentMeshes[meshIndex].material = this.highlightMaterial; + this.highlighted.push(meshIndex) + } + + clearHighlights() { + for (const v of this.highlighted) { + const mat = v < this.vertexCount ? this.vertexMaterial : this.edgeMaterial; + this.currentMeshes[v].material = mat; + } + this.highlighted = []; + } +} + +// Merge every mesh geometry contained in an OBJLoader-produced group. +function geometryFromObj(text: string): THREE.BufferGeometry { + const group = new OBJLoader().parse(text); + const geometries: THREE.BufferGeometry[] = []; + group.traverse((o: THREE.Object3D) => { + const mesh = o as THREE.Mesh; + if (mesh.isMesh) geometries.push(mesh.geometry); + }); + if (geometries.length === 0) return new THREE.BufferGeometry(); + if (geometries.length === 1) return geometries[0]; + return mergeGeometries(geometries, false) ?? geometries[0]; +} + +// Runtime page construction --- + +type NumberKeys = { + [K in keyof VertexPrintParams]: VertexPrintParams[K] extends number ? K : never +}[keyof VertexPrintParams]; +type StringKeys = { + [K in keyof VertexPrintParams]: VertexPrintParams[K] extends string ? K : never +}[keyof VertexPrintParams]; + +type SliderParam = { + kind: 'slider'; + name: NumberKeys; + label: string; + desc: string; + min: number; + max: number; + step: number; + value: number; + unit: string; +}; + +type SelectParam = { + kind: 'select'; + name: StringKeys; + label: string; + desc: string; + value: string; + options: { value: string; label: string }[]; + reveal?: (v: string) => string | null; +}; +type Param = SliderParam | SelectParam; + +const OPTIONS: Param[] = [ + { + kind: 'slider', + name: 'edgeDiameter', + label: 'Rod diameter', + desc: 'Diameter of your dowel rods.', + min: 0, + max: 100, + step: 0.05, + value: 3.0, + unit: 'mm', + }, + { + kind: 'slider', + name: 'diameterTolerance', + label: 'Diameter tolerance', + desc: 'Additional tolerance added to the diameter.\nAdd about 12% of your diameter for wood dowel rods, and 5% of your diameter for metal dowel rods', + min: 0, + max: 1, + step: 0.01, + value: 0.35, + unit: 'mm', + }, + { + kind: 'slider', + name: 'diameterTaper', + label: 'Diameter taper', + desc: 'The rod holder diameter decreases by this amount. A small taper is helpful to account for unevenness in the diameters of your dowel rods, particularly for wood dowel rods', + min: 0, + max: 1, + step: 0.01, + value: 0.1, + unit: 'mm', + }, + { + kind: 'slider', + name: 'wallThickness', + label: 'Wall thickness', + desc: 'Thickness of the tube walls.', + min: 0, + max: 40, + step: 0.01, + value: 1.2, + unit: 'mm', + }, + { + kind: 'slider', + name: 'scale', + label: 'Scale', + desc: 'Scale factor. Vertexprinted objects are typically larger than than the original object. Objects that are too small risk having their vertex pieces collide.', + min: 0, + max: 100, + step: 0.01, + value: 1.00, + unit: 'x', + }, + { + kind: 'slider', + name: 'rodInset', + label: 'Tube length', + desc: 'Length of tube enveloping dowel rods.', + min: 0, + max: 100, + step: 0.1, + value: 6, + unit: 'mm', + }, + { + kind: 'slider', + name: 'minPrinterOverhangAngle', + label: 'Overhang angle', + desc: 'The angle by which your vertex pieces can overhang. At 0°, any overhang angle is permitted. At 90°, vertexprint minimizes overhangs.', + min: 0, + max: 90, + step: 5, + value: 45, + unit: '°', + }, + { + kind: 'select', + name: 'offsetType', + label: 'Offset type', + desc: 'Dowel rods are offset from the center of each vertex to avoid them from colliding with each other within a vertex piece.\n\ + "Auto (per-edge)" allows a different offset for each edge at a given vertex. It is the most space-efficient.\n\ + "Auto (per-vertex)" forces the same offset at each vertex, but allows different vertices to have different offsets.\n\ + "Auto (global)" forces the entire solid to share an offset value.\n\ + "Manual" allows you to select a global offset value.\n', + value: 'auto_per_edge', + options: [ + { value: 'fixed', label: 'Manual' }, + { value: 'auto_global', label: 'Auto (global)' }, + { value: 'auto_per_vertex', label: 'Auto (per-vertex)' }, + { value: 'auto_per_edge', label: 'Auto (per-edge)' }, + ], + reveal: (v) => v === 'fixed' ? 'manualOffset' : null, + }, + { + kind: 'slider', + name: 'manualOffset', + label: 'Offset', + desc: 'Manually set a global offset value.', // see comment above + min: 0, + max: 100, + step: 0.01, + value: 0, + unit: 'mm', + }, + { + kind: 'select', + name: 'renderQuality', + label: 'Render quality', + desc: 'Quality of part. "Preview" renderes with fewer triangles. Always render with "Final" before printing.', + value: 'preview', + options: [ + { value: 'preview', label: 'Preview' }, + { value: 'final', label: 'Final' }, + ], + }, +]; + +// Build and position a tooltip element for `host`, returning it. +function makeTooltip(host: HTMLElement, text: string): HTMLDivElement { + const tip = document.createElement('div'); + tip.className = 'dh-tooltip'; + const lines = text.split('\n').map(l => l.trim()).filter(l => l.length > 0); + tip.innerHTML = lines.map(l => `

${l}

`).join(''); + document.body.appendChild(tip); + + const r = host.getBoundingClientRect(); + const tw = tip.offsetWidth; + const th = tip.offsetHeight; + const gap = 6; + + // Place tooltip below the host element. Place it above if it overflows the + // viewport. + let top = r.bottom + gap; + if (top + th > window.innerHeight) top = r.top - th - gap; + if (top < 0) top = gap; + + // Left-align the tooltip with the host. Clamp to viewport if this is not possible. + let left = r.left; + if (left + tw > window.innerWidth - gap) left = window.innerWidth - tw - gap; + if (left < gap) left = gap; + tip.style.top = `${top}px`; + tip.style.left = `${left}px`; + return tip; +} + +// Bind a tooltip to a html element +function bindTooltip(element: HTMLElement, text: string) { + let tip: HTMLDivElement | null = null; + let timer: number | null = null; + const title = element.getAttribute('title'); + const show = () => { + if (tip || timer) return; + // strip title while tooltip shows, keep title otherwise for screen + // readers + if (title !== null) element.removeAttribute('title'); + // 1s delay before tooltip shows + timer = window.setTimeout(() => { + timer = null; + tip = makeTooltip(element, text); + }, 600); + }; + const hide = () => { + if (timer !== null) { + window.clearTimeout(timer); timer = null; + } + if (title !== null) element.setAttribute('title', title); + if (tip) { + tip.remove(); tip = null; + } + }; + element.addEventListener('mouseenter', show); + element.addEventListener('focus', show); + element.addEventListener('mouseleave', hide); + element.addEventListener('blur', hide); + element.addEventListener('keydown', (e) => { if (e.key === 'Escape') hide(); }); +} + +const TW_CLASS = { + row: 'flex flex-col gap-0.5 px-1.5 py-1', + top: 'flex items-center justify-between gap-1.5', + label: 'opt-label font-mono text-[11px] text-v-fg truncate', + num: 'w-16 min-w-16 font-mono text-[11px] leading-none text-v-fg bg-black border border-v-border py-0.5 pl-1 pr-5 text-left appearance-none focus:outline-none focus:border-v-blue', + unit: 'pointer-events-none absolute right-1 inset-y-0 flex items-center translate-y-px font-mono text-[11px] leading-none text-v-fg/70', + slider: 'dh-slider w-full h-3.5 cursor-pointer appearance-none', + select: 'w-full font-mono text-[11px] text-v-fg bg-black border border-v-border px-1 py-0.5 focus:outline-none focus:border-v-blue', +}; + + +type SliderRow = { + row: HTMLElement; + slider: HTMLInputElement; + num: HTMLInputElement; + param: SliderParam +}; + +type SelectRow = { + row: HTMLElement; + sel: HTMLSelectElement; + param: SelectParam +}; + +class Sidebar { + rows: Map; + + constructor() { + this.rows = new Map(); + this.initSidebar(); + } + + makeSlider(param: SliderParam): HTMLElement { + const row = document.createElement('div'); + row.className = TW_CLASS.row; + row.dataset.param = param.name; + row.innerHTML = + `
${param.label}` + + `
` + + `${param.unit}
` + + ``; + bindTooltip(row.querySelector('.opt-label')!, param.desc); + return row + } + + makeSelect(param: SelectParam): HTMLElement { + const row = document.createElement('div'); + row.className = TW_CLASS.row; + row.dataset.param = param.name; + const paramsHTML = param.options.map(o => + ``).join(''); + row.innerHTML = + `
${param.label}
` + + ``; + bindTooltip(row.querySelector('.opt-label')!, param.desc); + return row + } + + enforceSelects() { + for (const param of OPTIONS) { + if (param.kind === 'select' && param.reveal) { + const currentVal = String(PARAMS[param.name]); + const shown = param.reveal(currentVal); + if (shown) + this.rows.get(shown)!.style.display = ''; + for (const o of param.options) { + const r = param.reveal(o.value); + if (r && o.value !== currentVal) + this.rows.get(r)!.style.display = 'none'; + } + } + } + } + + // Programmatically set a param value and update the corresponding UI + // element. + setParam(name: NumberKeys | StringKeys, value: number | string) { + const row = this.rows.get(name as string); + if (!row) return; + const param = OPTIONS.find(p => p.name === name); + if (!param) return; + if (param.kind === 'slider') { + const slider = row.querySelector('.dh-slider') as HTMLInputElement; + const num = row.querySelector('input[type="number"]') as HTMLInputElement; + const v = clamp(value as number, param.min, param.max); + slider.value = String(v); + num.value = String(v); + PARAMS[param.name] = v; + const pct = (v - param.min) / (param.max - param.min) * 100; + slider.style.setProperty('--fill', `${pct}%`); + } else { + const sel = row.querySelector('select') as HTMLSelectElement; + sel.value = String(value); + PARAMS[param.name] = value as any; + this.enforceSelects(); + } + } + + // Synchronize slider with corresponding num entry and vv + syncSlider(r: SliderRow) { + const setFill = () => { + const pct = (clamp(r.slider.valueAsNumber, r.param.min, r.param.max) - r.param.min) + / (r.param.max - r.param.min) * 100; + r.slider.style.setProperty('--fill', `${pct}%`); + }; + const sync = (src: 'slider' | 'num') => { + const raw = parseFloat(src === 'slider' ? r.slider.value : r.num.value); + const v = clamp(isNaN(raw) ? 0 : raw, r.param.min, r.param.max); + r.slider.value = String(v); + r.num.value = String(v); + PARAMS[r.param.name] = v; + setFill(); + }; + r.slider.addEventListener('input', () => sync('slider')); + r.num.addEventListener('input', () => sync('num')); + r.num.addEventListener('change', () => sync('num')); + setFill(); + } + + syncSelect(r: SelectRow) { + r.sel.addEventListener('change', () => { + PARAMS[r.param.name] = r.sel.value; + this.enforceSelects(); + }); + } + + initSidebar() { + const sidebar = document.getElementById('sidebar')!; + // Initialize sidebar input fields + const opts = document.getElementById('sidebar-opts')!; + for (const param of OPTIONS) { + let row: HTMLElement; + if (param.kind === 'slider') { + PARAMS[param.name] = param.value; + row = this.makeSlider(param); + this.syncSlider({ + row, + slider: row.querySelector('.dh-slider')!, + num: row.querySelector('input[type="number"]')!, + param + }); + } else { + PARAMS[param.name] = param.value; + row = this.makeSelect(param); + this.syncSelect({ + row, + sel: row.querySelector('select')!, + param + }); + } + this.rows.set(param.name, row); + opts.appendChild(row); + } + this.enforceSelects(); + + // Open/close sidebar + const sidebar_reopen = document.getElementById('sidebar-reopen')!; + const sidebar_close = document.getElementById('sidebar-close')!; + bindTooltip(sidebar_close, 'Close sidebar'); + bindTooltip(sidebar_reopen, 'Show sidebar'); + sidebar_close.addEventListener('click', () => { + sidebar.style.display = 'none'; + sidebar_reopen.style.display = 'flex'; + }); + sidebar_reopen.addEventListener('click', () => { + sidebar.style.display = ''; + sidebar_reopen.style.display = 'none'; + }); + + // Resize sidebar + const sidebar_resizer = document.getElementById('sidebar-resizer')!; + let dragging = false, startX = 0, startW = 0; + sidebar_resizer.addEventListener('mousedown', (e) => { + dragging = true; + sidebar_resizer.classList.add('dragging'); + startX = e.clientX; + startW = sidebar.offsetWidth; + document.body.style.userSelect = 'none'; + e.preventDefault(); + }); + window.addEventListener('mousemove', (e) => { + if (!dragging) return; + const w = clamp(startW + (e.clientX - startX), 160, window.innerWidth * 0.5); + sidebar.style.width = w + 'px'; + sidebar.style.maxWidth = 'none'; + }); + window.addEventListener('mouseup', () => { + dragging = false; + sidebar_resizer.classList.remove('dragging'); + document.body.style.userSelect = ''; + }); + } +} + + +function initInspector() { + const inspector = document.getElementById('inspector')!; + + // Switch tabs in inspector panel + document.querySelectorAll('#inspector-tabs .inspector-tab').forEach((tab) => { + tab.addEventListener('click', () => { + const name = tab.dataset.tab; + document.querySelectorAll('#inspector-tabs .inspector-tab').forEach((t) => { + const active = t.dataset.tab === name; + t.classList.toggle('text-v-blue', active); + t.classList.toggle('border-v-blue', active); + t.classList.toggle('bg-v-panel', active); + t.classList.toggle('text-v-fg', !active); + t.classList.toggle('border-transparent', !active); + }); + document.querySelectorAll('.inspector-pane').forEach((p) => { + p.style.display = p.dataset.pane === name ? '' : 'none'; + }); + }); + }); + + + // Open/close inspector + const inspector_reopen = document.getElementById('inspector-reopen')!; + const inspector_close = document.getElementById('inspector-close')!; + bindTooltip(inspector_close, 'Close inspector'); + bindTooltip(inspector_reopen, 'Show inspector'); + inspector_close.addEventListener('click', () => { + inspector.style.display = 'none'; + inspector_reopen.style.display = 'flex'; + }); + + inspector_reopen.addEventListener('click', () => { + inspector.style.display = ''; + inspector_reopen.style.display = 'none'; + }); +} + +// Reset the inspector panes +function resetInspector() { + const msg = 'This pane will populate after you generate artifacts'; + document.querySelectorAll('.inspector-pane').forEach((p) => { + p.innerHTML = `

${msg}

`; + }); +} + +// Fill the vertex inspector with vertex-edge adjacencies +// Fill the edge inspector with edge-vertex adjacencies, and offset-adjusted edge length +function populateInspector(outputs: VertexPrintOutputs, canvas: Canvas) { + const polyhedron = outputs.polyhedron; + const verticesPane = document.querySelector('.inspector-pane[data-pane="vertices"]')!; + const edgesPane = document.querySelector('.inspector-pane[data-pane="edges"]')!; + + const vertexCount = polyhedron.vertexFigures.length; + let vhtml = `

${vertexCount} vertices

`; + vhtml += `
index, edges
`; + for (const vf of polyhedron.vertexFigures) { + vhtml += `
v${vf.vertexIndex} [${vf.edges.join(", ")}]
`; + } + verticesPane.innerHTML = vhtml; + + for (let i = 0; i < polyhedron.vertexFigures.length; ++i) { + const vi = document.getElementById(`v${i}`); + vi.addEventListener('click', () => { + canvas.highlightVertex(i); + }); + } + + const edges = [...polyhedron.edges.entries()] + .map(([key, field]) => { + const [v1, v2] = key.split(",").map(Number); + return { name: field.name, v1, v2, offsetLength: field.offsetLength }; + }) + .sort((a, b) => a.name - b.name); + + let ehtml = `

${edges.length} edges

`; + ehtml += `
index, vertices, length (mm)
`; + for (const e of edges) { + ehtml += `
e${e.name} [${e.v1} ${e.v2}] ${e.offsetLength.toFixed(2)}
`; + } + edgesPane.innerHTML = ehtml; + + for (let i = 0; i < edges.length; ++i) { + const vi = document.getElementById(`e${i}`); + vi.addEventListener('click', () => { + canvas.highlightEdge(i); + }); + } + + document.addEventListener('click', (e) => { + const t = e.target as Node; + if (!verticesPane.contains(t) && !edgesPane.contains(t)) { + canvas.clearHighlights(); + } + }); +} + +const PRESETS: Record> = { + 'Platonic solids': { + 'Tetrahedron': { file: 'Tetrahedron.obj', scale: 50 }, + 'Cube': { file: 'Cube.obj', scale: 50 }, + 'Octahedron': { file: 'Octahedron.obj', scale: 50 }, + 'Dodecahedron': { file: 'Dodecahedron.obj', scale: 50 }, + 'Icosahedron': { file: 'Icosahedron.obj', scale: 50 }, + }, + 'Archimedean solids': { + 'Truncated Tetrahedron': { file: 'TruncatedTetrahedron.obj', scale: 50 }, + 'Cuboctahedron': { file: 'Cuboctahedron.obj', scale: 50 }, + 'Truncated Cube': { file: 'TruncatedCube.obj', scale: 50 }, + 'Truncated Octahedron': { file: 'TruncatedOctahedron.obj', scale: 50 }, + 'Rhombicuboctahedron': { file: 'Rhombicuboctahedron.obj', scale: 50 }, + 'Truncated Cuboctahedron': { file: 'TruncatedCuboctahedron.obj', scale: 50 }, + 'Snub Cube (laevo)': { file: 'LsnubCube.obj', scale: 50 }, + 'Icosidodecahedron': { file: 'Icosidodecahedron.obj', scale: 50 }, + 'Truncated Dodecahedron': { file: 'TruncatedDodecahedron.obj', scale: 50 }, + 'Truncated Icosahedron': { file: 'TruncatedIcosahedron.obj', scale: 50 }, + 'Rhombicosidodecahedron': { file: 'Rhombicosidodecahedron.obj', scale: 50 }, + 'Truncated Icosidodecahedron': { file: 'TruncatedIcosidodecahedron.obj', scale: 50 }, + 'Snub Dodecahedron (laevo)': { file: 'LsnubDodecahedron.obj', scale: 50 }, + }, + 'Catalan solids': { + 'Triakis Tetrahedron': { file: 'TriakisTetrahedron.obj', scale: 50 }, + 'Rhombic Dodecahedron': { file: 'RhombicDodecahedron.obj', scale: 50 }, + 'Triakis Octahedron': { file: 'TriakisOctahedron.obj', scale: 50 }, + 'Tetrakis Hexahedron': { file: 'TetrakisHexahedron.obj', scale: 50 }, + 'Deltoidal Icositetrahedron': { file: 'DeltoidalIcositetrahedron.obj', scale: 50 }, + 'Disdyakis Dodecahedron': { file: 'DisdyakisDodecahedron.obj', scale: 50 }, + 'Pentagonal Icositetrahedron (laevo)': { file: 'LpentagonalIcositetrahedron.obj', scale: 50 }, + 'Rhombic Triacontahedron': { file: 'RhombicTriacontahedron.obj', scale: 50 }, + 'Triakis Icosahedron': { file: 'TriakisIcosahedron.obj', scale: 50 }, + 'Pentakis Dodecahedron': { file: 'PentakisDodecahedron.obj', scale: 50 }, + 'Deltoidal Hexecontahedron': { file: 'DeltoidalHexecontahedron.obj', scale: 50 }, + 'Disdyakis Triacontahedron': { file: 'DisdyakisTriacontahedron.obj', scale: 50 }, + 'Pentagonal Hexecontahedron (laevo)': { file: 'LpentagonalHexecontahedron.obj', scale: 50 }, + }, + 'Misc': { + 'Stanford Bunny': { file: 'bunny.stl', scale: 6 }, + 'Rhombic Dodecahedron Tiling': { file: '13RhombicDodecahedra.obj', scale: 50 }, + }, +}; + +function initPresetsMenu(canvas: Canvas, sidebar: Sidebar) { + const presets_button = document.getElementById('presets-button')!; + const presets_menu = document.getElementById('presets-menu')!; + bindTooltip(presets_button, 'Open presets'); + + for (const [category, solids] of Object.entries(PRESETS)) { + const header = document.createElement('p'); + header.textContent = category; + header.className = 'font-mono text-xs px-3 pt-2 pb-1 text-v-fg/60 border-b border-v-border min-w-full'; + presets_menu.appendChild(header); + for (const [label, preset] of Object.entries(solids)) { + const { file, scale } = preset; + const item = document.createElement('button'); + item.type = 'button'; + item.textContent = label; + item.dataset.file = file; + item.className = 'block w-full cursor-pointer text-left font-mono text-xs px-3 py-1 text-v-fg hover:bg-v-blue hover:text-v-dark'; + item.addEventListener('click', async () => { + sidebar.setParam('scale', scale); + const data = await fetch(DATA_DIR + file).then((r) => r.arrayBuffer()) + canvas.loadGeometry(file, data); + }); + presets_menu.appendChild(item); + } + } + + presets_button.addEventListener('click', (e) => { + e.stopPropagation(); + presets_menu.classList.toggle('hidden'); + }); + + // Close the menu when clicking outside it or selecting an item. + document.addEventListener('click', (e) => { + if (!presets_menu.contains(e.target as Node) && e.target !== presets_button) { + presets_menu.classList.add('hidden'); + } + }); + presets_menu.addEventListener('click', (e) => { + const target = e.target as HTMLElement; + if (target.tagName === 'BUTTON') { + presets_menu.classList.add('hidden'); + } + }); +} + +function initUploadButton(canvas: Canvas) { + const button = document.getElementById('upload-button')!; + bindTooltip(button, 'Upload files'); + button.addEventListener('click', () => { + const input = document.createElement('input'); + input.type = 'file'; + input.accept = '.stl,.obj'; + input.addEventListener('change', async () => { + const file = input.files?.[0]; + if (!file) return; + const data = await file.arrayBuffer(); + const name = file.name; + void canvas.loadGeometry(name, data); + }); + input.click(); + }); +} + + +function initConstructButton(canvas: Canvas) { + const button = document.getElementById('construct-button')!; + bindTooltip(button, 'Vertexprint your structure'); + const label = button.querySelector('p')!; + const IDLE_TEXT = 'Construct'; + + button.addEventListener('click', async () => { + if (canvas.currentData.byteLength === 0) { + console.error('No model loaded to construct from'); + return; + } + + button.classList.add('opacity-70', 'pointer-events-none'); + let dots = 0; + label.textContent = 'Generating'; + const timer = window.setInterval(() => { + dots = (dots + 1) % 4; + label.textContent = 'Generating' + '.'.repeat(dots); + }, 400); + + const outputs = await vertexPrint(canvas.currentName, canvas.currentData, PARAMS); + canvas.loadVertexPrintOutputs(outputs); + canvas.loadEdges(outputs); + window.clearInterval(timer); + label.textContent = IDLE_TEXT; + button.classList.remove('opacity-70', 'pointer-events-none'); + enableDownloadButton(outputs); + populateInspector(outputs, canvas); + }); +} + +async function saveOutputs(outputs: VertexPrintOutputs) { + const zip = new JSZip(); + const polyhedron = outputs.polyhedron; + const baseName = (polyhedron.name || "vertexprint").replace(/[\\/:*?"<>|]/g, "_"); + for (let i = 0; i < outputs.stls.length; i++) { + const blob = new Blob([outputs.stls[i]], { type: "application/octet-stream" }); + zip.file(`${baseName}_v${i}.stl`, blob); + } + + return zip.generateAsync({ type: "blob" }).then((content) => { + const url = URL.createObjectURL(content); + const a = document.createElement("a"); + a.href = url; + a.download = `${baseName}.zip`; + document.body.appendChild(a); + a.click(); + a.remove(); + URL.revokeObjectURL(url); + }); +} + +function resetDownloadButton(): HTMLButtonElement { + const old = document.getElementById('download-button')!; + const button = old.cloneNode(true) as HTMLButtonElement; + old.replaceWith(button); + return button; +} + +function enableDownloadButton(outputs: VertexPrintOutputs) { + const button = resetDownloadButton(); + button.classList.remove('text-v-fg/70', 'cursor-not-allowed'); + button.classList.add('text-v-fg', 'cursor-pointer', 'hover:bg-v-blue', 'hover:text-v-dark'); + bindTooltip(button, 'Download generated artifacts.\nYou must generate artifacts before a download.'); + button.addEventListener('click', () => { void saveOutputs(outputs); }); +} + +function disableDownloadButton() { + const button = resetDownloadButton(); + button.classList.add('text-v-fg/70', 'cursor-not-allowed'); + button.classList.remove('text-v-fg', 'cursor-pointer', 'hover:bg-v-blue', 'hover:text-v-dark'); + bindTooltip(button, 'Download generated artifacts.\nYou must generate artifacts before a download.'); +} + +async function initMesh(canvas: Canvas, sidebar: Sidebar) { + sidebar.setParam('scale', 50); + const data = await fetch(DATA_DIR + DEFAULT_MODEL).then((r) => r.arrayBuffer()); + canvas.loadGeometry(DEFAULT_MODEL, data); +} + +async function init() { + const canvas = new Canvas(); + const sidebar = new Sidebar(); + initInspector(); + initPresetsMenu(canvas, sidebar); + initUploadButton(canvas); + initConstructButton(canvas); + disableDownloadButton(); + bindTooltip(document.getElementById('help-button')!, 'Help'); + bindTooltip(document.getElementById('source-link')!, 'Source code'); + initMesh(canvas, sidebar); +} + +init(); diff --git a/webapp/core.ts b/webapp/core.ts new file mode 100644 index 0000000..ff7a860 --- /dev/null +++ b/webapp/core.ts @@ -0,0 +1,787 @@ +import Matrix, { SingularValueDecomposition } from "ml-matrix"; +import { STLLoader } from "three/addons/loaders/STLLoader.js"; +import { mergeVertices } from "three/addons/utils/BufferGeometryUtils.js"; + +let VERTEXPRINT_SOURCE: string | null = null; + +async function loadSource(): Promise { + if (VERTEXPRINT_SOURCE !== null) { + return VERTEXPRINT_SOURCE; + } + VERTEXPRINT_SOURCE = await fetch("vertexprint.scad").then((r) => r.text()); + return VERTEXPRINT_SOURCE; +} + +export class VertexPrintParams { + edgeDiameter: number; + diameterTolerance: number; + diameterTaper: number; + wallThickness: number; + scale: number; + rodInset: number; + minPrinterOverhangAngle: number; + offsetType: string; + manualOffset: number; + renderQuality: "preview" | "final"; +}; + +export class VertexPrintOutputs { + polyhedron: Polyhedron; + stls: ArrayBuffer[]; + + constructor(polyhedron: Polyhedron, stls: ArrayBuffer[]) { + this.polyhedron = polyhedron; + this.stls = stls; + } +} + +export async function vertexPrint( + name: string, + data: ArrayBuffer, + options: VertexPrintParams, +): Promise { + const polyhedron = polyhedronFromFile(name, data, options); + if (!polyhedron) { + throw new Error(`Failed to parse polyhedron from ${name}`); + } + const scadSource = await loadSource(); + const args = new OpenscadArgs(polyhedron, options); + const count = polyhedron.vertexFigures.length; + const cliArgs: string[][] = []; + for (let i = 0; i < count; i++) { + cliArgs.push(args.toOpenscadArgs(i)) + } + const stls = await renderVertices(polyhedron.name, scadSource, cliArgs); + return new VertexPrintOutputs(polyhedron, stls); +} + +// Render vertices in parallel by processing contiguous chunks of vertices +// through a pool of web workers. +async function renderVertices( + name: string, + scadSource: string, + cliArgs: string[][], +): Promise { + const count = cliArgs.length; + const results: ArrayBuffer[] = new Array(count); + if (count === 0) return results; + + const workerCount = Math.min(navigator.hardwareConcurrency || 1, count); + const chunkSize = Math.ceil(count / workerCount); + + const workerUrl = new URL("./worker.js", import.meta.url); + const workerPromises: Promise[] = []; + + for (let w = 0; w < workerCount; w++) { + const start = w * chunkSize; + const end = Math.min(start + chunkSize, count); + if (start >= end) break; + + const worker = new Worker(workerUrl, { type: "module" }); + + workerPromises.push(new Promise((resolve, reject) => { + let pending = end - start; + const onMessage = (e: MessageEvent) => { + const m = e.data; + if (m.type === "error") { + // TODO: visual cue when an openscad render fails + worker.removeEventListener("message", onMessage); + worker.terminate(); + reject(new Error(m.message)); + return; + } + results[m.index] = m.buffer; + pending--; + if (pending === 0) { + worker.removeEventListener("message", onMessage); + worker.terminate(); + resolve(); + } + }; + worker.addEventListener("message", onMessage); + for (let i = start; i < end; i++) { + worker.postMessage({ + type: "render", + index: i, + cliArgs: cliArgs[i], + name, + source: scadSource, + }); + } + })); + } + await Promise.all(workerPromises); + return results; +} + + +function polyhedronFromFile(name: string, data: ArrayBuffer, options: VertexPrintParams) { + let polyhedron: Polyhedron; + try { + const isObj = name.toLowerCase().endsWith(".obj"); + polyhedron = isObj + ? parseObj(data, name, options) + : parseStl(data, name, options); + } catch (e) { + // TODO: show user an error message + console.error("Failed to load", name, e); + return; + } + return polyhedron; +} + +// Parse OBJ files into Polyhedron objects. +function parseObj( + data: ArrayBuffer, + filename: string, + options: VertexPrintParams, +): Polyhedron { + const text = new TextDecoder().decode(data); + const vertices: number[][] = []; + const faces: string[][] = []; + + const lines = text.split(/\r?\n/); + for (const rawLine of lines) { + const line = rawLine.trim(); + if (!line || line.startsWith("#")) { + continue; + } + + const parts = line.split(/\s+/); + if (parts.length === 0) { + continue; + } + + if (parts[0] === "v") { + const vertex = [parseFloat(parts[1]), parseFloat(parts[2]), parseFloat(parts[3])]; + vertices.push(vertex); + } else if (parts[0] === "f") { + // OBJ uses 1-based indexing, we convert to 0-based + const face: string[] = []; + for (let i = 1; i < parts.length; i++) { + const vertDef = parts[i]; + const indices = vertDef.split("/")[0]; + face.push(String(parseInt(indices, 10) - 1)); + } + faces.push(face); + } + } + if (vertices.length === 0) { + throw new Error(`No vertices found in OBJ file: ${filename}`); + } + + // os.path.basename(filepath).replace(".obj", "") + const base = filename.split(/[\\/]/).pop() ?? filename; + const name = base.replace(/\.obj$/i, ""); + + return new Polyhedron( + name, + faces, + new Matrix(vertices), + options, + ); +} + +// Parse STL files into Polyhedron objects. +function parseStl(data: ArrayBuffer, filename: string, options: VertexPrintParams): Polyhedron { + const parsed = new STLLoader().parse(data); + // Three.js's mergeVertices function deduplicates by all vertex attributes. + // Keep only the position attribute, to prevent vertices with per-face + // normals/colors/other junk from not merging. + for (const name of Object.keys(parsed.attributes)) { + if (name !== "position") { + parsed.deleteAttribute(name); + } + } + const geometry = mergeVertices(parsed); + const position = geometry.attributes.position; + const index = geometry.index; + if (!position || !index) { + throw new Error("No positions found in STL data"); + } + + const arr = position.array as ArrayLike; + const vertices: number[][] = []; + for (let i = 0; i < position.count; i++) { + vertices.push([arr[i * 3], arr[i * 3 + 1], arr[i * 3 + 2]]); + } + + const faces: string[][] = []; + for (let i = 0; i < index.count; i += 3) { + faces.push([String(index.getX(i)), String(index.getX(i + 1)), String(index.getX(i + 2))]); + } + + return new Polyhedron(filename, faces, new Matrix(vertices), options); +} + +// For some godforsaken reason ml-matrix does not provide cross products. +function cross(a: Matrix, b: Matrix): Matrix { + const [ax, ay, az] = a.to1DArray(); + const [bx, by, bz] = b.to1DArray(); + return Matrix.columnVector([ + ay * bz - az * by, + az * bx - ax * bz, + ax * by - ay * bx, + ]); +} + + +class VertexFigure { + vertex: Matrix; + vertexIndex: number; + vecs: Matrix; + neighbors: number[]; + std: Matrix; + euler: [number, number, number]; + options: VertexPrintParams; + + halfEdgeOffset: number[]; + vertexOffset: number; + + edges: number[] = []; + tag: number; + + constructor( + vertex: Matrix, + vertexIndex: number, + vecs: Matrix, + neighbors: number[], + tag: number, + options: VertexPrintParams, + ) { + this.vertex = vertex; + this.vertexIndex = vertexIndex; + this.vecs = vecs; + this.neighbors = neighbors; + this.tag = tag; + this.options = options; + + this.std = vecs; + this.euler = [0.0, 0.0, 0.0]; + + this.halfEdgeOffset = this.computeOffsets(); + this.vertexOffset = this.largestOffset(); + + const planeNormal = this.planeNormal(); + const normal = this.normal(); + if (normal !== null && planeNormal !== null) { + const direction = planeNormal.dot(normal) > 0 ? 1 : -1; + const [rotated, euler] = this.reorientTo( + Matrix.mul(planeNormal, direction), + ); + this.std = rotated; + this.euler = euler; + } + + } + + // flag + annotateEdgeNames(edges: Map): void { + this.edges = []; + for (const neighbor of this.neighbors) { + const edge: [number, number] = + this.vertexIndex < neighbor + ? [this.vertexIndex, neighbor] + : [neighbor, this.vertexIndex]; + this.edges.push(edges.get(edge.join(","))!.name); + } + } + + normalizable(): boolean { + return this.normal() !== null; + } + + normal(): Matrix | null { + const n = Matrix.columnVector(this.vecs.sum("column")); + const norm = n.norm(); + return norm > 1e-10 ? Matrix.mul(n, 1 / norm) : null; + } + + // flag + planeNormal(): Matrix | null { + const v = this.vecs.clone(); + if (this.options.offsetType === "auto_per_edge") { + // vecs *= (half_edge_offset[:, None] + rod_inset): scale each row. + const factors = Matrix.columnVector( + this.halfEdgeOffset.map((o) => o + this.options.rodInset), + ).repeat({ columns: v.columns }); + v.mul(factors); + } + if (v.rows < 3) { + return null; + } + // centered = vecs - np.mean(vecs, axis=0) + v.center("column"); + // _, _, vh = np.linalg.svd(centered); normal = vh[2] + const svd = new SingularValueDecomposition(v); + const diag = svd.diagonal; + const V = svd.rightSingularVectors; + let minIndex = 0; + let min = diag[0]; + for (let i = 1; i < diag.length; i++) { + if (diag[i] < min) { + min = diag[i]; + minIndex = i; + } + } + const normal = V.getColumnVector(minIndex); + return Matrix.mul(normal, -1 / normal.norm()); + } + + matrixToRotation(R: Matrix): [number, number, number] { + const sy = Math.sqrt(R.get(0, 0) ** 2 + R.get(1, 0) ** 2); + const singular = sy < 1e-6; + if (!singular) { + return [ + Math.atan2(R.get(2, 1), R.get(2, 2)), + Math.atan2(-R.get(2, 0), sy), + Math.atan2(R.get(1, 0), R.get(0, 0)), + ]; + } else if (R.get(2, 0) < 0) { + // y = 90 degrees + return [Math.atan2(-R.get(1, 2), R.get(1, 1)), Math.PI / 2, 0.0]; + } else { + // y = -90 degrees + return [Math.atan2(-R.get(1, 2), R.get(1, 1)), -Math.PI / 2, 0.0]; + } + } + + // Orient normal to target, then apply this rotation to all vectors in the + // figure + reorientTo( + normal: Matrix, + target: Matrix = Matrix.columnVector([0.0, 0.0, 1.0]), + ): [Matrix, [number, number, number]] { + const nn = normal.norm(); + if (nn < 1e-9) { + return [this.vecs, [0.0, 0.0, 0.0]]; + } + const uMean = Matrix.mul(normal, 1 / nn); + const axis = cross(uMean, target); + const lenAxis = axis.norm(); + const dotVal = uMean.dot(target); + if (lenAxis < 1e-6) { + if (dotVal > 0) { + return [this.vecs, [0.0, 0.0, 0.0]]; + } else { + // Flip y and z of every row vector. + const sign = new Matrix([[1, -1, -1]]).repeat({ + rows: this.vecs.rows, + }); + const flipped = Matrix.mul(this.vecs, sign); + return [flipped, [Math.PI, 0.0, 0.0]]; + } + } + const u = axis.mul(1 / lenAxis); + const u0 = u.get(0, 0); + const u1 = u.get(1, 0); + const u2 = u.get(2, 0); + const c = dotVal; + const s = lenAxis; + const C = 1 - c; + const R = new Matrix([ + [c + u0 * u0 * C, u0 * u1 * C - u2 * s, u0 * u2 * C + u1 * s], + [u1 * u0 * C + u2 * s, c + u1 * u1 * C, u1 * u2 * C - u0 * s], + [u2 * u0 * C - u1 * s, u2 * u1 * C + u0 * s, c + u2 * u2 * C], + ]); + const RT = R.transpose(); + const euler = this.matrixToRotation(RT); + const rotated = this.vecs.mmul(RT); + return [rotated, euler]; + } + + minCosDist(index: number): Matrix { + const scores: number[] = []; + const vIndex = this.vecs.getRowVector(index); + for (let i = 0; i < this.vecs.rows; i++) { + if (i === index) { + scores.push(-1000); + } else { + const vi = this.vecs.getRowVector(i); + scores.push(vIndex.dot(vi) / vi.norm()); + } + } + let max = scores[0]; + let maxIndex = 0; + for (let i = 1; i < scores.length; i++) { + if (scores[i] > max) { + max = scores[i]; + maxIndex = i; + } + } + return this.vecs.getRowVector(maxIndex); + } + + axisOffset(v0: Matrix, v1: Matrix): number { + const c = v0.dot(v1); + const s = cross(v0, v1).norm(); + const outerTubeRadius = + this.options.edgeDiameter / 2 + this.options.wallThickness; + const lSide = + (outerTubeRadius * c + this.options.edgeDiameter / 2) / s; + const lBase = (this.options.edgeDiameter / 2 * (1 + c)) / s; + if (s < 1e-9) { + return c > 0 ? 1e9 : 0; + } + return Math.max(lSide, lBase); + } + + offsetFromSingleVec(index: number): number { + const closest = this.minCosDist(index); + return this.axisOffset( + this.vecs.getRowVector(index), + closest, + ); + } + + computeOffsets(): number[] { + const out: number[] = []; + for (let i = 0; i < this.vecs.rows; i++) { + out.push(this.offsetFromSingleVec(i)); + } + return out; + } + + largestOffset(): number { + return Math.max(...this.halfEdgeOffset); + } +} + +type EdgeField = { length: number; offsetLength: number; name: number, offsets: [number, number] }; + +class Polyhedron { + name: string; + faces: string[][]; + vertices: Matrix; + options: VertexPrintParams; + + edges: Map; + vertexFigures: VertexFigure[]; + solidOffset: number; + + constructor( + name: string, + faces: string[][], + vertices: Matrix, + options: VertexPrintParams, + ) { + this.name = name; + this.faces = faces; + this.vertices = vertices; + this.options = options; + + this.edges = this.makeEdgelist(); + this.vertexFigures = this.annotateVertexFigures(); + this.solidOffset = this.largestOffset(); + this.computeEdgeLengths(); + for (const vf of this.vertexFigures) { + vf.annotateEdgeNames(this.edges); + } + } + + averageEdgeLength(): number { + if (this.edges.size === 0) { + return 0; + } + let total = 0; + for (const e of this.edges.values()) { + total += e.length; + } + return total / this.edges.size; + } + + // Largest offset among all vertex figure max offsets + largestOffset(): number { + if (this.vertexFigures.length === 0) { + return 0; + } + let max = this.vertexFigures[0].vertexOffset; + for (let i = 1; i < this.vertexFigures.length; i++) { + if (this.vertexFigures[i].vertexOffset > max) { + max = this.vertexFigures[i].vertexOffset; + } + } + return max; + } + + // Determine offsets to be subtracted from each end of a given edge + offsetForEdge(v1: number, v2: number): [number, number] { + const vf1 = this.vertexFigures[v1]; + const vf2 = this.vertexFigures[v2]; + switch (this.options.offsetType) { + case "fixed": + return [this.options.manualOffset, this.options.manualOffset]; + case "auto_per_vertex": + return [vf1.vertexOffset, vf2.vertexOffset]; + case "auto_per_edge": { + const i1 = vf1.neighbors.findIndex((n) => n === v2); + const i2 = vf2.neighbors.findIndex((n) => n === v1); + return [ + vf1.halfEdgeOffset[i1], + vf2.halfEdgeOffset[i2], + ]; + } + case "auto_global": + default: + return [this.solidOffset, this.solidOffset]; + } + } + + // rod length = scale * |v1 - v2| - offset(v1, v2) - offset(v2, v1) + // value appended to self.edges + computeEdgeLengths(): void { + type KLO = { key: string; length: number; offsetLength: number, offsets: [number, number] }; + const klo: KLO[] = []; + for (const key of this.edges.keys()) { + const [v1, v2] = key.split(",").map(Number); + const [v1_offset, v2_offset] = this.offsetForEdge(v1, v2); + const v1_arr = this.vertices.getRowVector(v1); + const v2_arr = this.vertices.getRowVector(v2); + const length = Matrix.sub(v2_arr, v1_arr).norm(); + const offsetLength = this.options.scale * length - v1_offset - v2_offset; + klo.push({ key, length, offsetLength, offsets: [v1_offset, v2_offset] }); + } + + klo.sort((x, y) => x.offsetLength - y.offsetLength); + const sorted = new Map(); + for (let i = 0; i < klo.length; i++) { + const { key, length, offsetLength, offsets } = klo[i]; + sorted.set(key, { length, offsetLength, name: i, offsets }); + } + this.edges = sorted; + } + + // Convert facelist into edgelist + makeEdgelist(): Map { + const edges = new Map(); + for (const face of this.faces) { + for (let k = 0; k < face.length; k++) { + const v1 = parseInt(face[k], 10); + const v2 = parseInt(face[(k + 1) % face.length], 10); + if (Number.isNaN(v1) || Number.isNaN(v2)) { + continue; + } + if (v1 < this.vertices.rows && v2 < this.vertices.rows) { + const key = + v1 < v2 ? `${v1},${v2}` : `${v2},${v1}`; + if (!edges.has(key)) { + edges.set(key, { length: 0, offsetLength: 0, name: -1, offsets: [0, 0] }); + } + } + } + } + return edges; + } + + // Distinct vertex figures should have distinct signatures. Probably. + vertexFigureSignature(vecs: Matrix): string { + const precision = 100000; + const n = vecs.rows; + const rows: Matrix[] = []; + for (let i = 0; i < n; i++) { + rows.push(vecs.getRowVector(i)); + } + const dots: number[] = []; + for (let i = 0; i < n; i++) { + for (let j = i; j < n; j++) { + dots.push(Math.round(rows[i].dot(rows[j]) * precision)); + } + } + const triples: number[] = []; + for (let i = 0; i < n; i++) { + for (let j = 0; j < n; j++) { + for (let k = 0; k < n; k++) { + triples.push( + Math.round(cross(rows[i], rows[j]).dot(rows[k]) * precision), + ); + } + } + } + dots.sort((a, b) => a - b); + triples.sort((a, b) => a - b); + return dots.join(",") + "|" + triples.join(","); + } + + // Construct vertex figure list + annotateVertexFigures(): VertexFigure[] { + const tags = new Map(); + let tag = 0; + const vertexFigures: VertexFigure[] = []; + + for (let i = 0; i < this.vertices.rows; i++) { + const neighbors: number[] = []; + for (const key of this.edges.keys()) { + const [a, b] = key.split(",").map(Number); + if (a === i) { + neighbors.push(b); + } else if (b === i) { + neighbors.push(a); + } + } + const vertex = this.vertices.getRowVector(i); + // Direction vectors from this vertex to each neighbor, normalized. + const vecs = new Matrix( + neighbors.map((n) => { + const nb = this.vertices.getRowVector(n); + const diff = Matrix.sub(nb, vertex); + const norm = diff.norm(); + const f = norm > 0 ? 1 / norm : 1; + return Matrix.mul(diff, f).to1DArray(); + }), + ); + + const signature = this.vertexFigureSignature(vecs); + if (!tags.has(signature)) { + tags.set(signature, tag); + tag++; + } + + const t = tags.get(signature)!; + vertexFigures.push( + new VertexFigure( + vertex, + i, + vecs, + neighbors, + t, + this.options, + ), + ); + } + + return vertexFigures; + } +} + +class OpenscadArgs { + vertices: Matrix; + edges: number[][]; + vertexFigures: Matrix[]; + eulers: number[][]; + tags: number[]; + vertexFigureEdges: number[][]; + offsets: number[][]; + options: VertexPrintParams + + constructor(polyhedron: Polyhedron, options: VertexPrintParams) { + const { + vertices, + edges, + vertexFigures, + eulers, + tags, + vertexFigureEdges, + } = this.polyhedronOptionsArray(polyhedron); + + this.vertices = vertices; + this.edges = edges; + this.vertexFigures = vertexFigures; + this.eulers = eulers; + this.tags = tags; + this.vertexFigureEdges = vertexFigureEdges; + this.offsets = this.polyhedronOffsetArray(polyhedron); + + this.options = options + } + + polyhedronOptionsArray(polyhedron: Polyhedron): { + vertices: Matrix; + edges: number[][]; + vertexFigures: Matrix[]; + eulers: number[][]; + tags: number[]; + vertexFigureEdges: number[][]; + } { + const vertices = polyhedron.vertices; + const edges: number[][] = []; + for (const key of polyhedron.edges.keys()) { + const [v1, v2] = key.split(",").map(Number); + edges.push([v1, v2]); + } + const vertexFigures: Matrix[] = []; + const eulers: number[][] = []; + const tags: number[] = []; + const vertexFigureEdges: number[][] = []; + + for (const vf of polyhedron.vertexFigures) { + vertexFigures.push(vf.std); + eulers.push(vf.euler); + tags.push(vf.tag); + vertexFigureEdges.push(vf.edges); + } + + return { vertices, edges, vertexFigures, eulers, tags, vertexFigureEdges }; + } + + polyhedronOffsetArray(polyhedron: Polyhedron): number[][] { + switch (polyhedron.options.offsetType) { + case "fixed": { + const value = polyhedron.options.manualOffset; + return polyhedron.vertexFigures.map((vf) => + Array(vf.vecs.rows).fill(value), + ); + } + case "auto_per_vertex": { + return polyhedron.vertexFigures.map((vf) => + Array(vf.vecs.rows).fill(vf.vertexOffset), + ); + } + case "auto_per_edge": { + return polyhedron.vertexFigures.map((vf) => + [...vf.halfEdgeOffset], + ); + } + case "auto_global": + default: { + const value = polyhedron.solidOffset; + return polyhedron.vertexFigures.map((vf) => + Array(vf.vecs.rows).fill(value), + ); + } + } + } + + // Convert a vertex to an unholy argument list that is passed to an + // openscad call + toOpenscadArgs(vertex: number): string[] { + const args: string[] = []; + args.push(`-DEDGE_DIAMETER=${this.options.edgeDiameter}`); + args.push(`-DDIAMETER_TOLERANCE_FIT=${this.options.diameterTolerance}`); + args.push(`-DDIAMETER_TAPER_DECREASE=${this.options.diameterTaper}`); + args.push(`-DWALL_THICKNESS=${this.options.wallThickness}`); + args.push(`-DROD_INSET=${this.options.rodInset}`); + args.push( + `-DMIN_PRINTER_OVERHANG_ANGLE=${this.options.minPrinterOverhangAngle}`, + ); + + const fparams = { + // fa, fs + "preview": [12, 2], + "final": [30, 0.2], + } + + args.push(`-DFA=${fparams[this.options.renderQuality][0]}`) + args.push(`-DFS=${fparams[this.options.renderQuality][1]}`) + + args.push(`-Dindex=${vertex}`) + // vertexFigures (std) + const vertexFigure = this.vertexFigures[vertex] + const rows: string[] = []; + for (let i = 0; i < vertexFigure.rows; i++) { + rows.push(`[${vertexFigure.getRow(i).join(",")}]`); + } + const vfStr = `[${rows.join(",")}]`; + args.push(`-Dvertex_figure=${vfStr}`); + + const tag = this.tags[vertex] + args.push(`-Dtag=${tag}`); + + const offset = `[${this.offsets[vertex].join(",")}]`; + args.push(`-Doffsets=${offset}`); + + const vertexFigureEdges = `[${this.vertexFigureEdges[vertex].join(",")}]`; + args.push(`-Dvertex_figure_edge=${vertexFigureEdges}`); + + return args; + } +} diff --git a/webapp/data/13RhombicDodecahedra.obj b/webapp/data/13RhombicDodecahedra.obj new file mode 100644 index 0000000..efbd62a --- /dev/null +++ b/webapp/data/13RhombicDodecahedra.obj @@ -0,0 +1,263 @@ +v 0.530330 0.530330 0.530330 +v 0.000000 0.000000 1.060660 +v 0.530330 -0.530330 0.530330 +v 1.060660 0.000000 0.000000 +v 0.530330 0.530330 -0.530330 +v 0.000000 1.060660 0.000000 +v -0.530330 0.530330 0.530330 +v 0.530330 -0.530330 -0.530330 +v 0.000000 0.000000 -1.060660 +v 0.000000 -1.060660 0.000000 +v -0.530330 -0.530330 -0.530330 +v -0.530330 0.530330 -0.530330 +v -1.060660 0.000000 0.000000 +v -0.530330 -0.530330 0.530330 +v -1.060660 -1.060660 1.060660 +v -0.530330 -1.590990 0.530330 +v -1.590990 -0.530330 0.530330 +v -0.530330 -1.590990 -0.530330 +v -1.060660 -1.060660 -1.060660 +v -1.060660 -2.121320 0.000000 +v -1.590990 -1.590990 -0.530330 +v -1.590990 -0.530330 -0.530330 +v -2.121320 -1.060660 0.000000 +v -1.590990 -1.590990 0.530330 +v -0.530330 0.530330 -1.590990 +v -1.060660 1.060660 -1.060660 +v -1.590990 0.530330 -0.530330 +v -0.530330 -0.530330 -1.590990 +v -1.060660 0.000000 -2.121320 +v -1.590990 -0.530330 -1.590990 +v -1.590990 0.530330 -1.590990 +v -2.121320 0.000000 -1.060660 +v 0.530330 -1.590990 -0.530330 +v 1.060660 -1.060660 -1.060660 +v 0.530330 -0.530330 -1.590990 +v 0.530330 -1.590990 -1.590990 +v 0.000000 -1.060660 -2.121320 +v 0.000000 -2.121320 -1.060660 +v -0.530330 -1.590990 -1.590990 +v -0.530330 1.590990 0.530330 +v -1.060660 1.060660 1.060660 +v -0.530330 1.590990 -0.530330 +v -1.060660 2.121320 0.000000 +v -1.590990 1.590990 0.530330 +v -1.590990 1.590990 -0.530330 +v -2.121320 1.060660 0.000000 +v -1.590990 0.530330 0.530330 +v -0.530330 0.530330 1.590990 +v -1.060660 0.000000 2.121320 +v -0.530330 -0.530330 1.590990 +v -1.590990 0.530330 1.590990 +v -2.121320 0.000000 1.060660 +v -1.590990 -0.530330 1.590990 +v 0.530330 -0.530330 1.590990 +v 0.000000 -1.060660 2.121320 +v 0.530330 -1.590990 1.590990 +v 1.060660 -1.060660 1.060660 +v 0.530330 -1.590990 0.530330 +v 0.000000 -2.121320 1.060660 +v -0.530330 -1.590990 1.590990 +v 1.590990 -0.530330 0.530330 +v 1.590990 -1.590990 0.530330 +v 2.121320 -1.060660 0.000000 +v 1.590990 -0.530330 -0.530330 +v 1.590990 -1.590990 -0.530330 +v 1.060660 -2.121320 0.000000 +v 1.590990 0.530330 -0.530330 +v 2.121320 0.000000 -1.060660 +v 1.590990 0.530330 -1.590990 +v 1.060660 1.060660 -1.060660 +v 1.590990 -0.530330 -1.590990 +v 1.060660 0.000000 -2.121320 +v 0.530330 0.530330 -1.590990 +v 0.530330 1.590990 -0.530330 +v 0.530330 1.590990 -1.590990 +v 0.000000 2.121320 -1.060660 +v 0.000000 1.060660 -2.121320 +v -0.530330 1.590990 -1.590990 +v 1.590990 1.590990 0.530330 +v 1.060660 1.060660 1.060660 +v 1.590990 0.530330 0.530330 +v 2.121320 1.060660 0.000000 +v 1.590990 1.590990 -0.530330 +v 1.060660 2.121320 0.000000 +v 0.530330 1.590990 0.530330 +v 1.590990 0.530330 1.590990 +v 1.060660 0.000000 2.121320 +v 1.590990 -0.530330 1.590990 +v 2.121320 0.000000 1.060660 +v 0.530330 0.530330 1.590990 +v 0.530330 1.590990 1.590990 +v 0.000000 1.060660 2.121320 +v 0.000000 2.121320 1.060660 +v -0.530330 1.590990 1.590990 +g dodecahedron_0 +f 1 2 3 4 +f 1 4 5 6 +f 1 6 7 2 +f 8 9 5 4 +f 8 4 3 10 +f 8 10 11 9 +f 12 9 11 13 +f 12 13 7 6 +f 12 6 5 9 +f 14 2 7 13 +f 14 13 11 10 +f 14 10 3 2 +g dodecahedron_1 +f 14 15 16 10 +f 14 10 11 13 +f 14 13 17 15 +f 18 19 11 10 +f 18 10 16 20 +f 18 20 21 19 +f 22 19 21 23 +f 22 23 17 13 +f 22 13 11 19 +f 24 15 17 23 +f 24 23 21 20 +f 24 20 16 15 +g dodecahedron_2 +f 12 13 11 9 +f 12 9 25 26 +f 12 26 27 13 +f 28 29 25 9 +f 28 9 11 19 +f 28 19 30 29 +f 31 29 30 32 +f 31 32 27 26 +f 31 26 25 29 +f 22 13 27 32 +f 22 32 30 19 +f 22 19 11 13 +g dodecahedron_3 +f 8 10 33 34 +f 8 34 35 9 +f 8 9 11 10 +f 36 37 35 34 +f 36 34 33 38 +f 36 38 39 37 +f 28 37 39 19 +f 28 19 11 9 +f 28 9 35 37 +f 18 10 11 19 +f 18 19 39 38 +f 18 38 33 10 +g dodecahedron_4 +f 40 41 7 6 +f 40 6 42 43 +f 40 43 44 41 +f 12 26 42 6 +f 12 6 7 13 +f 12 13 27 26 +f 45 26 27 46 +f 45 46 44 43 +f 45 43 42 26 +f 47 41 44 46 +f 47 46 27 13 +f 47 13 7 41 +g dodecahedron_5 +f 48 49 50 2 +f 48 2 7 41 +f 48 41 51 49 +f 14 13 7 2 +f 14 2 50 15 +f 14 15 17 13 +f 47 13 17 52 +f 47 52 51 41 +f 47 41 7 13 +f 53 49 51 52 +f 53 52 17 15 +f 53 15 50 49 +g dodecahedron_6 +f 54 55 56 57 +f 54 57 3 2 +f 54 2 50 55 +f 58 10 3 57 +f 58 57 56 59 +f 58 59 16 10 +f 14 10 16 15 +f 14 15 50 2 +f 14 2 3 10 +f 60 55 50 15 +f 60 15 16 59 +f 60 59 56 55 +g dodecahedron_7 +f 61 57 62 63 +f 61 63 64 4 +f 61 4 3 57 +f 65 34 64 63 +f 65 63 62 66 +f 65 66 33 34 +f 8 34 33 10 +f 8 10 3 4 +f 8 4 64 34 +f 58 57 3 10 +f 58 10 33 66 +f 58 66 62 57 +g dodecahedron_8 +f 67 4 64 68 +f 67 68 69 70 +f 67 70 5 4 +f 71 72 69 68 +f 71 68 64 34 +f 71 34 35 72 +f 73 72 35 9 +f 73 9 5 70 +f 73 70 69 72 +f 8 4 5 9 +f 8 9 35 34 +f 8 34 64 4 +g dodecahedron_9 +f 74 6 5 70 +f 74 70 75 76 +f 74 76 42 6 +f 73 77 75 70 +f 73 70 5 9 +f 73 9 25 77 +f 78 77 25 26 +f 78 26 42 76 +f 78 76 75 77 +f 12 6 42 26 +f 12 26 25 9 +f 12 9 5 6 +g dodecahedron_10 +f 79 80 81 82 +f 79 82 83 84 +f 79 84 85 80 +f 67 70 83 82 +f 67 82 81 4 +f 67 4 5 70 +f 74 70 5 6 +f 74 6 85 84 +f 74 84 83 70 +f 1 80 85 6 +f 1 6 5 4 +f 1 4 81 80 +g dodecahedron_11 +f 86 87 88 89 +f 86 89 81 80 +f 86 80 90 87 +f 61 4 81 89 +f 61 89 88 57 +f 61 57 3 4 +f 1 4 3 2 +f 1 2 90 80 +f 1 80 81 4 +f 54 87 90 2 +f 54 2 3 57 +f 54 57 88 87 +g dodecahedron_12 +f 91 92 90 80 +f 91 80 85 93 +f 91 93 94 92 +f 1 6 85 80 +f 1 80 90 2 +f 1 2 7 6 +f 40 6 7 41 +f 40 41 94 93 +f 40 93 85 6 +f 48 92 94 41 +f 48 41 7 2 +f 48 2 90 92 diff --git a/webapp/data/Cube.obj b/webapp/data/Cube.obj new file mode 100644 index 0000000..31f77d0 --- /dev/null +++ b/webapp/data/Cube.obj @@ -0,0 +1,14 @@ +v 0.5 0.5 0.5 +v 0.5 0.5 -0.5 +v 0.5 -0.5 0.5 +v 0.5 -0.5 -0.5 +v -0.5 0.5 0.5 +v -0.5 0.5 -0.5 +v -0.5 -0.5 0.5 +v -0.5 -0.5 -0.5 +f 1 2 6 5 +f 1 5 7 3 +f 1 3 4 2 +f 8 4 3 7 +f 8 7 5 6 +f 8 6 2 4 diff --git a/webapp/data/Cuboctahedron.obj b/webapp/data/Cuboctahedron.obj new file mode 100644 index 0000000..39a7418 --- /dev/null +++ b/webapp/data/Cuboctahedron.obj @@ -0,0 +1,26 @@ +v 0.7071067811865476 0.0 0.7071067811865476 +v 0.7071067811865476 0.0 -0.7071067811865476 +v -0.7071067811865476 0.0 0.7071067811865476 +v -0.7071067811865476 0.0 -0.7071067811865476 +v 0.7071067811865476 0.7071067811865476 0.0 +v 0.7071067811865476 -0.7071067811865476 0.0 +v -0.7071067811865476 0.7071067811865476 0.0 +v -0.7071067811865476 -0.7071067811865476 0.0 +v 0.0 0.7071067811865476 0.7071067811865476 +v 0.0 0.7071067811865476 -0.7071067811865476 +v 0.0 -0.7071067811865476 0.7071067811865476 +v 0.0 -0.7071067811865476 -0.7071067811865476 +f 1 6 2 5 +f 1 9 3 11 +f 8 3 7 4 +f 8 12 6 11 +f 10 2 12 4 +f 10 7 9 5 +f 1 5 9 +f 2 6 12 +f 3 8 11 +f 4 7 10 +f 5 2 10 +f 6 1 11 +f 7 3 9 +f 8 4 12 diff --git a/webapp/data/DeltoidalHexecontahedron.obj b/webapp/data/DeltoidalHexecontahedron.obj new file mode 100644 index 0000000..96682e5 --- /dev/null +++ b/webapp/data/DeltoidalHexecontahedron.obj @@ -0,0 +1,122 @@ +v 0.0 0.0 2.23606797749979 +v 0.0 0.0 -2.23606797749979 +v 2.23606797749979 0.0 0.0 +v -2.23606797749979 0.0 0.0 +v 0.0 2.23606797749979 0.0 +v 0.0 -2.23606797749979 0.0 +v 0.0 0.7834576353408995 2.051118718068096 +v 0.0 0.7834576353408995 -2.051118718068096 +v 0.0 -0.7834576353408995 2.051118718068096 +v 0.0 -0.7834576353408995 -2.051118718068096 +v 2.051118718068096 0.0 0.7834576353408995 +v 2.051118718068096 0.0 -0.7834576353408995 +v -2.051118718068096 0.0 0.7834576353408995 +v -2.051118718068096 0.0 -0.7834576353408995 +v 0.7834576353408995 2.051118718068096 0.0 +v 0.7834576353408995 -2.051118718068096 0.0 +v -0.7834576353408995 2.051118718068096 0.0 +v -0.7834576353408995 -2.051118718068096 0.0 +v 1.2060113295832984 0.0 1.9513673220832282 +v 1.2060113295832984 0.0 -1.9513673220832282 +v -1.2060113295832984 0.0 1.9513673220832282 +v -1.2060113295832984 0.0 -1.9513673220832282 +v 1.9513673220832282 1.2060113295832984 0.0 +v 1.9513673220832282 -1.2060113295832984 0.0 +v -1.9513673220832282 1.2060113295832984 0.0 +v -1.9513673220832282 -1.2060113295832984 0.0 +v 0.0 1.9513673220832282 1.2060113295832984 +v 0.0 1.9513673220832282 -1.2060113295832984 +v 0.0 -1.9513673220832282 1.2060113295832984 +v 0.0 -1.9513673220832282 -1.2060113295832984 +v 0.6909830056250525 1.118033988749895 1.8090169943749475 +v 0.6909830056250525 1.118033988749895 -1.8090169943749475 +v 0.6909830056250525 -1.118033988749895 1.8090169943749475 +v 0.6909830056250525 -1.118033988749895 -1.8090169943749475 +v -0.6909830056250525 1.118033988749895 1.8090169943749475 +v -0.6909830056250525 1.118033988749895 -1.8090169943749475 +v -0.6909830056250525 -1.118033988749895 1.8090169943749475 +v -0.6909830056250525 -1.118033988749895 -1.8090169943749475 +v 1.8090169943749475 0.6909830056250525 1.118033988749895 +v 1.8090169943749475 0.6909830056250525 -1.118033988749895 +v 1.8090169943749475 -0.6909830056250525 1.118033988749895 +v 1.8090169943749475 -0.6909830056250525 -1.118033988749895 +v -1.8090169943749475 0.6909830056250525 1.118033988749895 +v -1.8090169943749475 0.6909830056250525 -1.118033988749895 +v -1.8090169943749475 -0.6909830056250525 1.118033988749895 +v -1.8090169943749475 -0.6909830056250525 -1.118033988749895 +v 1.118033988749895 1.8090169943749475 0.6909830056250525 +v 1.118033988749895 1.8090169943749475 -0.6909830056250525 +v 1.118033988749895 -1.8090169943749475 0.6909830056250525 +v 1.118033988749895 -1.8090169943749475 -0.6909830056250525 +v -1.118033988749895 1.8090169943749475 0.6909830056250525 +v -1.118033988749895 1.8090169943749475 -0.6909830056250525 +v -1.118033988749895 -1.8090169943749475 0.6909830056250525 +v -1.118033988749895 -1.8090169943749475 -0.6909830056250525 +v 1.2676610827271964 1.2676610827271964 1.2676610827271964 +v 1.2676610827271964 1.2676610827271964 -1.2676610827271964 +v 1.2676610827271964 -1.2676610827271964 1.2676610827271964 +v 1.2676610827271964 -1.2676610827271964 -1.2676610827271964 +v -1.2676610827271964 1.2676610827271964 1.2676610827271964 +v -1.2676610827271964 1.2676610827271964 -1.2676610827271964 +v -1.2676610827271964 -1.2676610827271964 1.2676610827271964 +v -1.2676610827271964 -1.2676610827271964 -1.2676610827271964 +f 19 1 9 33 +f 19 33 57 41 +f 19 41 11 39 +f 19 39 55 31 +f 19 31 7 1 +f 20 2 8 32 +f 20 32 56 40 +f 20 40 12 42 +f 20 42 58 34 +f 20 34 10 2 +f 21 1 7 35 +f 21 35 59 43 +f 21 43 13 45 +f 21 45 61 37 +f 21 37 9 1 +f 22 2 10 38 +f 22 38 62 46 +f 22 46 14 44 +f 22 44 60 36 +f 22 36 8 2 +f 23 3 12 40 +f 23 40 56 48 +f 23 48 15 47 +f 23 47 55 39 +f 23 39 11 3 +f 24 3 11 41 +f 24 41 57 49 +f 24 49 16 50 +f 24 50 58 42 +f 24 42 12 3 +f 25 4 13 43 +f 25 43 59 51 +f 25 51 17 52 +f 25 52 60 44 +f 25 44 14 4 +f 26 4 14 46 +f 26 46 62 54 +f 26 54 18 53 +f 26 53 61 45 +f 26 45 13 4 +f 27 5 17 51 +f 27 51 59 35 +f 27 35 7 31 +f 27 31 55 47 +f 27 47 15 5 +f 28 5 15 48 +f 28 48 56 32 +f 28 32 8 36 +f 28 36 60 52 +f 28 52 17 5 +f 29 6 16 49 +f 29 49 57 33 +f 29 33 9 37 +f 29 37 61 53 +f 29 53 18 6 +f 30 6 18 54 +f 30 54 62 38 +f 30 38 10 34 +f 30 34 58 50 +f 30 50 16 6 diff --git a/webapp/data/DeltoidalIcositetrahedron.obj b/webapp/data/DeltoidalIcositetrahedron.obj new file mode 100644 index 0000000..91388ca --- /dev/null +++ b/webapp/data/DeltoidalIcositetrahedron.obj @@ -0,0 +1,50 @@ +v 0.0 0.0 1.4142135623730951 +v 0.0 0.0 -1.4142135623730951 +v 1.4142135623730951 0.0 0.0 +v -1.4142135623730951 0.0 0.0 +v 0.0 1.4142135623730951 0.0 +v 0.0 -1.4142135623730951 0.0 +v 1.0 0.0 1.0 +v 1.0 0.0 -1.0 +v -1.0 0.0 1.0 +v -1.0 0.0 -1.0 +v 1.0 1.0 0.0 +v 1.0 -1.0 0.0 +v -1.0 1.0 0.0 +v -1.0 -1.0 0.0 +v 0.0 1.0 1.0 +v 0.0 1.0 -1.0 +v 0.0 -1.0 1.0 +v 0.0 -1.0 -1.0 +v 0.7734590803390136 0.7734590803390136 0.7734590803390136 +v 0.7734590803390136 0.7734590803390136 -0.7734590803390136 +v 0.7734590803390136 -0.7734590803390136 0.7734590803390136 +v 0.7734590803390136 -0.7734590803390136 -0.7734590803390136 +v -0.7734590803390136 0.7734590803390136 0.7734590803390136 +v -0.7734590803390136 0.7734590803390136 -0.7734590803390136 +v -0.7734590803390136 -0.7734590803390136 0.7734590803390136 +v -0.7734590803390136 -0.7734590803390136 -0.7734590803390136 +f 1 7 19 15 +f 1 15 23 9 +f 1 9 25 17 +f 1 17 21 7 +f 2 10 24 16 +f 2 16 20 8 +f 2 8 22 18 +f 2 18 26 10 +f 3 8 20 11 +f 3 11 19 7 +f 3 7 21 12 +f 3 12 22 8 +f 4 9 23 13 +f 4 13 24 10 +f 4 10 26 14 +f 4 14 25 9 +f 5 11 20 16 +f 5 16 24 13 +f 5 13 23 15 +f 5 15 19 11 +f 6 12 21 17 +f 6 17 25 14 +f 6 14 26 18 +f 6 18 22 12 diff --git a/webapp/data/DisdyakisDodecahedron.obj b/webapp/data/DisdyakisDodecahedron.obj new file mode 100644 index 0000000..63eb2e3 --- /dev/null +++ b/webapp/data/DisdyakisDodecahedron.obj @@ -0,0 +1,74 @@ +v 0.0 0.0 2.6754174373368365 +v 0.0 0.0 -2.6754174373368365 +v 2.6754174373368365 0.0 0.0 +v -2.6754174373368365 0.0 0.0 +v 0.0 2.6754174373368365 0.0 +v 0.0 -2.6754174373368365 0.0 +v 1.6407544820340814 0.0 1.6407544820340814 +v 1.6407544820340814 0.0 -1.6407544820340814 +v -1.6407544820340814 0.0 1.6407544820340814 +v -1.6407544820340814 0.0 -1.6407544820340814 +v 1.6407544820340814 1.6407544820340814 0.0 +v 1.6407544820340814 -1.6407544820340814 0.0 +v -1.6407544820340814 1.6407544820340814 0.0 +v -1.6407544820340814 -1.6407544820340814 0.0 +v 0.0 1.6407544820340814 1.6407544820340814 +v 0.0 1.6407544820340814 -1.6407544820340814 +v 0.0 -1.6407544820340814 1.6407544820340814 +v 0.0 -1.6407544820340814 -1.6407544820340814 +v 1.4142135623730951 1.4142135623730951 1.4142135623730951 +v 1.4142135623730951 1.4142135623730951 -1.4142135623730951 +v 1.4142135623730951 -1.4142135623730951 1.4142135623730951 +v 1.4142135623730951 -1.4142135623730951 -1.4142135623730951 +v -1.4142135623730951 1.4142135623730951 1.4142135623730951 +v -1.4142135623730951 1.4142135623730951 -1.4142135623730951 +v -1.4142135623730951 -1.4142135623730951 1.4142135623730951 +v -1.4142135623730951 -1.4142135623730951 -1.4142135623730951 +f 1 7 19 +f 1 19 15 +f 1 15 23 +f 1 23 9 +f 1 9 25 +f 1 25 17 +f 1 17 21 +f 1 21 7 +f 2 8 22 +f 2 22 18 +f 2 18 26 +f 2 26 10 +f 2 10 24 +f 2 24 16 +f 2 16 20 +f 2 20 8 +f 3 7 21 +f 3 21 12 +f 3 12 22 +f 3 22 8 +f 3 8 20 +f 3 20 11 +f 3 11 19 +f 3 19 7 +f 4 9 23 +f 4 23 13 +f 4 13 24 +f 4 24 10 +f 4 10 26 +f 4 26 14 +f 4 14 25 +f 4 25 9 +f 5 11 20 +f 5 20 16 +f 5 16 24 +f 5 24 13 +f 5 13 23 +f 5 23 15 +f 5 15 19 +f 5 19 11 +f 6 12 21 +f 6 21 17 +f 6 17 25 +f 6 25 14 +f 6 14 26 +f 6 26 18 +f 6 18 22 +f 6 22 12 diff --git a/webapp/data/DisdyakisTriacontahedron.obj b/webapp/data/DisdyakisTriacontahedron.obj new file mode 100644 index 0000000..41e8658 --- /dev/null +++ b/webapp/data/DisdyakisTriacontahedron.obj @@ -0,0 +1,182 @@ +v 0.0 0.0 3.802983248181589 +v 0.0 0.0 -3.802983248181589 +v 3.802983248181589 0.0 0.0 +v -3.802983248181589 0.0 0.0 +v 0.0 3.802983248181589 0.0 +v 0.0 -3.802983248181589 0.0 +v 0.0 1.381966011250105 3.618033988749895 +v 0.0 1.381966011250105 -3.618033988749895 +v 0.0 -1.381966011250105 3.618033988749895 +v 0.0 -1.381966011250105 -3.618033988749895 +v 3.618033988749895 0.0 1.381966011250105 +v 3.618033988749895 0.0 -1.381966011250105 +v -3.618033988749895 0.0 1.381966011250105 +v -3.618033988749895 0.0 -1.381966011250105 +v 1.381966011250105 3.618033988749895 0.0 +v 1.381966011250105 -3.618033988749895 0.0 +v -1.381966011250105 3.618033988749895 0.0 +v -1.381966011250105 -3.618033988749895 0.0 +v 2.170820393249937 0.0 3.5124611797498106 +v 2.170820393249937 0.0 -3.5124611797498106 +v -2.170820393249937 0.0 3.5124611797498106 +v -2.170820393249937 0.0 -3.5124611797498106 +v 3.5124611797498106 2.170820393249937 0.0 +v 3.5124611797498106 -2.170820393249937 0.0 +v -3.5124611797498106 2.170820393249937 0.0 +v -3.5124611797498106 -2.170820393249937 0.0 +v 0.0 3.5124611797498106 2.170820393249937 +v 0.0 3.5124611797498106 -2.170820393249937 +v 0.0 -3.5124611797498106 2.170820393249937 +v 0.0 -3.5124611797498106 -2.170820393249937 +v 1.1751864530113494 1.9014916240907944 3.0766780771021436 +v 1.1751864530113494 1.9014916240907944 -3.0766780771021436 +v 1.1751864530113494 -1.9014916240907944 3.0766780771021436 +v 1.1751864530113494 -1.9014916240907944 -3.0766780771021436 +v -1.1751864530113494 1.9014916240907944 3.0766780771021436 +v -1.1751864530113494 1.9014916240907944 -3.0766780771021436 +v -1.1751864530113494 -1.9014916240907944 3.0766780771021436 +v -1.1751864530113494 -1.9014916240907944 -3.0766780771021436 +v 3.0766780771021436 1.1751864530113494 1.9014916240907944 +v 3.0766780771021436 1.1751864530113494 -1.9014916240907944 +v 3.0766780771021436 -1.1751864530113494 1.9014916240907944 +v 3.0766780771021436 -1.1751864530113494 -1.9014916240907944 +v -3.0766780771021436 1.1751864530113494 1.9014916240907944 +v -3.0766780771021436 1.1751864530113494 -1.9014916240907944 +v -3.0766780771021436 -1.1751864530113494 1.9014916240907944 +v -3.0766780771021436 -1.1751864530113494 -1.9014916240907944 +v 1.9014916240907944 3.0766780771021436 1.1751864530113494 +v 1.9014916240907944 3.0766780771021436 -1.1751864530113494 +v 1.9014916240907944 -3.0766780771021436 1.1751864530113494 +v 1.9014916240907944 -3.0766780771021436 -1.1751864530113494 +v -1.9014916240907944 3.0766780771021436 1.1751864530113494 +v -1.9014916240907944 3.0766780771021436 -1.1751864530113494 +v -1.9014916240907944 -3.0766780771021436 1.1751864530113494 +v -1.9014916240907944 -3.0766780771021436 -1.1751864530113494 +v 2.23606797749979 2.23606797749979 2.23606797749979 +v 2.23606797749979 2.23606797749979 -2.23606797749979 +v 2.23606797749979 -2.23606797749979 2.23606797749979 +v 2.23606797749979 -2.23606797749979 -2.23606797749979 +v -2.23606797749979 2.23606797749979 2.23606797749979 +v -2.23606797749979 2.23606797749979 -2.23606797749979 +v -2.23606797749979 -2.23606797749979 2.23606797749979 +v -2.23606797749979 -2.23606797749979 -2.23606797749979 +f 19 1 9 +f 19 9 33 +f 19 33 57 +f 19 57 41 +f 19 41 11 +f 19 11 39 +f 19 39 55 +f 19 55 31 +f 19 31 7 +f 19 7 1 +f 20 2 8 +f 20 8 32 +f 20 32 56 +f 20 56 40 +f 20 40 12 +f 20 12 42 +f 20 42 58 +f 20 58 34 +f 20 34 10 +f 20 10 2 +f 21 1 7 +f 21 7 35 +f 21 35 59 +f 21 59 43 +f 21 43 13 +f 21 13 45 +f 21 45 61 +f 21 61 37 +f 21 37 9 +f 21 9 1 +f 22 2 10 +f 22 10 38 +f 22 38 62 +f 22 62 46 +f 22 46 14 +f 22 14 44 +f 22 44 60 +f 22 60 36 +f 22 36 8 +f 22 8 2 +f 23 3 12 +f 23 12 40 +f 23 40 56 +f 23 56 48 +f 23 48 15 +f 23 15 47 +f 23 47 55 +f 23 55 39 +f 23 39 11 +f 23 11 3 +f 24 3 11 +f 24 11 41 +f 24 41 57 +f 24 57 49 +f 24 49 16 +f 24 16 50 +f 24 50 58 +f 24 58 42 +f 24 42 12 +f 24 12 3 +f 25 4 13 +f 25 13 43 +f 25 43 59 +f 25 59 51 +f 25 51 17 +f 25 17 52 +f 25 52 60 +f 25 60 44 +f 25 44 14 +f 25 14 4 +f 26 4 14 +f 26 14 46 +f 26 46 62 +f 26 62 54 +f 26 54 18 +f 26 18 53 +f 26 53 61 +f 26 61 45 +f 26 45 13 +f 26 13 4 +f 27 5 17 +f 27 17 51 +f 27 51 59 +f 27 59 35 +f 27 35 7 +f 27 7 31 +f 27 31 55 +f 27 55 47 +f 27 47 15 +f 27 15 5 +f 28 5 15 +f 28 15 48 +f 28 48 56 +f 28 56 32 +f 28 32 8 +f 28 8 36 +f 28 36 60 +f 28 60 52 +f 28 52 17 +f 28 17 5 +f 29 6 16 +f 29 16 49 +f 29 49 57 +f 29 57 33 +f 29 33 9 +f 29 9 37 +f 29 37 61 +f 29 61 53 +f 29 53 18 +f 29 18 6 +f 30 6 18 +f 30 18 54 +f 30 54 62 +f 30 62 38 +f 30 38 10 +f 30 10 34 +f 30 34 58 +f 30 58 50 +f 30 50 16 +f 30 16 6 diff --git a/webapp/data/Dodecahedron.obj b/webapp/data/Dodecahedron.obj new file mode 100644 index 0000000..764a56d --- /dev/null +++ b/webapp/data/Dodecahedron.obj @@ -0,0 +1,32 @@ +v 0.0 0.5 1.3090169943749475 +v 0.0 0.5 -1.3090169943749475 +v 0.0 -0.5 1.3090169943749475 +v 0.0 -0.5 -1.3090169943749475 +v 1.3090169943749475 0.0 0.5 +v 1.3090169943749475 0.0 -0.5 +v -1.3090169943749475 0.0 0.5 +v -1.3090169943749475 0.0 -0.5 +v 0.5 1.3090169943749475 0.0 +v 0.5 -1.3090169943749475 0.0 +v -0.5 1.3090169943749475 0.0 +v -0.5 -1.3090169943749475 0.0 +v 0.8090169943749475 0.8090169943749475 0.8090169943749475 +v 0.8090169943749475 0.8090169943749475 -0.8090169943749475 +v 0.8090169943749475 -0.8090169943749475 0.8090169943749475 +v 0.8090169943749475 -0.8090169943749475 -0.8090169943749475 +v -0.8090169943749475 0.8090169943749475 0.8090169943749475 +v -0.8090169943749475 0.8090169943749475 -0.8090169943749475 +v -0.8090169943749475 -0.8090169943749475 0.8090169943749475 +v -0.8090169943749475 -0.8090169943749475 -0.8090169943749475 +f 1 3 15 5 13 +f 1 13 9 11 17 +f 1 17 7 19 3 +f 8 7 17 11 18 +f 8 18 2 4 20 +f 8 20 12 19 7 +f 10 12 20 4 16 +f 10 16 6 5 15 +f 10 15 3 19 12 +f 14 2 18 11 9 +f 14 9 13 5 6 +f 14 6 16 4 2 diff --git a/webapp/data/GreatDodecahedron.obj b/webapp/data/GreatDodecahedron.obj new file mode 100644 index 0000000..a9950a9 --- /dev/null +++ b/webapp/data/GreatDodecahedron.obj @@ -0,0 +1,24 @@ +v 0.5 0.0 0.8090169943749475 +v 0.5 0.0 -0.8090169943749475 +v -0.5 0.0 0.8090169943749475 +v -0.5 0.0 -0.8090169943749475 +v 0.8090169943749475 0.5 0.0 +v 0.8090169943749475 -0.5 0.0 +v -0.8090169943749475 0.5 0.0 +v -0.8090169943749475 -0.5 0.0 +v 0.0 0.8090169943749475 0.5 +v 0.0 0.8090169943749475 -0.5 +v 0.0 -0.8090169943749475 0.5 +v 0.0 -0.8090169943749475 -0.5 +f 1 3 8 12 6 +f 1 6 2 10 9 +f 1 9 7 8 11 +f 2 4 7 9 5 +f 2 5 1 11 12 +f 2 12 8 7 10 +f 3 1 5 10 7 +f 3 7 4 12 11 +f 3 11 6 5 9 +f 4 2 6 11 8 +f 4 8 3 9 10 +f 4 10 5 6 12 diff --git a/webapp/data/GreatIcosahedron.obj b/webapp/data/GreatIcosahedron.obj new file mode 100644 index 0000000..78cf395 --- /dev/null +++ b/webapp/data/GreatIcosahedron.obj @@ -0,0 +1,32 @@ +v 0.0 -0.5 0.30901699437494745 +v 0.0 -0.5 -0.30901699437494745 +v 0.0 0.5 0.30901699437494745 +v 0.0 0.5 -0.30901699437494745 +v -0.5 0.30901699437494745 0.0 +v 0.5 0.30901699437494745 0.0 +v -0.5 -0.30901699437494745 0.0 +v 0.5 -0.30901699437494745 0.0 +v 0.30901699437494745 0.0 -0.5 +v 0.30901699437494745 0.0 0.5 +v -0.30901699437494745 0.0 -0.5 +v -0.30901699437494745 0.0 0.5 +f 1 3 11 +f 1 11 6 +f 1 6 5 +f 1 5 9 +f 1 9 3 +f 4 2 12 +f 4 12 8 +f 4 8 7 +f 4 7 10 +f 4 10 2 +f 3 7 8 +f 3 8 11 +f 11 8 12 +f 11 12 6 +f 6 12 2 +f 6 2 5 +f 5 2 10 +f 5 10 9 +f 9 10 7 +f 9 7 3 diff --git a/webapp/data/GreatStellatedDodecahedron.obj b/webapp/data/GreatStellatedDodecahedron.obj new file mode 100644 index 0000000..1453218 --- /dev/null +++ b/webapp/data/GreatStellatedDodecahedron.obj @@ -0,0 +1,32 @@ +v 0.5 0.0 0.19098300562505258 +v 0.5 0.0 -0.19098300562505258 +v -0.5 0.0 0.19098300562505258 +v -0.5 0.0 -0.19098300562505258 +v 0.0 0.19098300562505258 0.5 +v 0.0 0.19098300562505258 -0.5 +v 0.0 -0.19098300562505258 0.5 +v 0.0 -0.19098300562505258 -0.5 +v 0.19098300562505258 0.5 0.0 +v -0.19098300562505258 0.5 0.0 +v 0.19098300562505258 -0.5 0.0 +v -0.19098300562505258 -0.5 0.0 +v -0.30901699437494745 -0.30901699437494745 -0.30901699437494745 +v -0.30901699437494745 -0.30901699437494745 0.30901699437494745 +v 0.30901699437494745 -0.30901699437494745 -0.30901699437494745 +v 0.30901699437494745 -0.30901699437494745 0.30901699437494745 +v -0.30901699437494745 0.30901699437494745 -0.30901699437494745 +v -0.30901699437494745 0.30901699437494745 0.30901699437494745 +v 0.30901699437494745 0.30901699437494745 -0.30901699437494745 +v 0.30901699437494745 0.30901699437494745 0.30901699437494745 +f 1 3 15 5 13 +f 1 13 9 11 17 +f 1 17 7 19 3 +f 8 7 17 11 18 +f 8 18 2 4 20 +f 8 20 12 19 7 +f 10 12 20 4 16 +f 10 16 6 5 15 +f 10 15 3 19 12 +f 14 2 18 11 9 +f 14 9 13 5 6 +f 14 6 16 4 2 diff --git a/webapp/data/Icosahedron.obj b/webapp/data/Icosahedron.obj new file mode 100644 index 0000000..88d44f4 --- /dev/null +++ b/webapp/data/Icosahedron.obj @@ -0,0 +1,32 @@ +v 0.5 0.0 0.8090169943749475 +v 0.5 0.0 -0.8090169943749475 +v -0.5 0.0 0.8090169943749475 +v -0.5 0.0 -0.8090169943749475 +v 0.8090169943749475 0.5 0.0 +v 0.8090169943749475 -0.5 0.0 +v -0.8090169943749475 0.5 0.0 +v -0.8090169943749475 -0.5 0.0 +v 0.0 0.8090169943749475 0.5 +v 0.0 0.8090169943749475 -0.5 +v 0.0 -0.8090169943749475 0.5 +v 0.0 -0.8090169943749475 -0.5 +f 1 3 11 +f 1 11 6 +f 1 6 5 +f 1 5 9 +f 1 9 3 +f 4 2 12 +f 4 12 8 +f 4 8 7 +f 4 7 10 +f 4 10 2 +f 3 7 8 +f 3 8 11 +f 11 8 12 +f 11 12 6 +f 6 12 2 +f 6 2 5 +f 5 2 10 +f 5 10 9 +f 9 10 7 +f 9 7 3 diff --git a/webapp/data/Icosidodecahedron.obj b/webapp/data/Icosidodecahedron.obj new file mode 100644 index 0000000..1669844 --- /dev/null +++ b/webapp/data/Icosidodecahedron.obj @@ -0,0 +1,62 @@ +v 0.0 0.0 1.618033988749895 +v 0.0 0.0 -1.618033988749895 +v 1.618033988749895 0.0 0.0 +v -1.618033988749895 0.0 0.0 +v 0.0 1.618033988749895 0.0 +v 0.0 -1.618033988749895 0.0 +v 0.5 0.8090169943749475 1.3090169943749475 +v 0.5 0.8090169943749475 -1.3090169943749475 +v 0.5 -0.8090169943749475 1.3090169943749475 +v 0.5 -0.8090169943749475 -1.3090169943749475 +v -0.5 0.8090169943749475 1.3090169943749475 +v -0.5 0.8090169943749475 -1.3090169943749475 +v -0.5 -0.8090169943749475 1.3090169943749475 +v -0.5 -0.8090169943749475 -1.3090169943749475 +v 1.3090169943749475 0.5 0.8090169943749475 +v 1.3090169943749475 0.5 -0.8090169943749475 +v 1.3090169943749475 -0.5 0.8090169943749475 +v 1.3090169943749475 -0.5 -0.8090169943749475 +v -1.3090169943749475 0.5 0.8090169943749475 +v -1.3090169943749475 0.5 -0.8090169943749475 +v -1.3090169943749475 -0.5 0.8090169943749475 +v -1.3090169943749475 -0.5 -0.8090169943749475 +v 0.8090169943749475 1.3090169943749475 0.5 +v 0.8090169943749475 1.3090169943749475 -0.5 +v 0.8090169943749475 -1.3090169943749475 0.5 +v 0.8090169943749475 -1.3090169943749475 -0.5 +v -0.8090169943749475 1.3090169943749475 0.5 +v -0.8090169943749475 1.3090169943749475 -0.5 +v -0.8090169943749475 -1.3090169943749475 0.5 +v -0.8090169943749475 -1.3090169943749475 -0.5 +f 1 9 17 15 7 +f 1 11 19 21 13 +f 2 8 16 18 10 +f 2 14 22 20 12 +f 3 16 24 23 15 +f 3 17 25 26 18 +f 4 19 27 28 20 +f 4 22 30 29 21 +f 5 24 8 12 28 +f 5 27 11 7 23 +f 6 25 9 13 29 +f 6 30 14 10 26 +f 1 7 11 +f 1 13 9 +f 2 10 14 +f 2 12 8 +f 3 15 17 +f 3 18 16 +f 4 20 22 +f 4 21 19 +f 5 23 24 +f 5 28 27 +f 6 26 25 +f 6 29 30 +f 7 15 23 +f 8 24 16 +f 9 25 17 +f 10 18 26 +f 11 27 19 +f 12 20 28 +f 13 21 29 +f 14 30 22 diff --git a/webapp/data/LpentagonalHexecontahedron.obj b/webapp/data/LpentagonalHexecontahedron.obj new file mode 100644 index 0000000..6a6f82c --- /dev/null +++ b/webapp/data/LpentagonalHexecontahedron.obj @@ -0,0 +1,152 @@ +v -0.19289371135235903 -0.21848337012732122 -2.0970538352520878 +v -0.19289371135235903 0.21848337012732122 2.0970538352520878 +v 0.19289371135235903 0.21848337012732122 -2.0970538352520878 +v 0.19289371135235903 -0.21848337012732122 2.0970538352520878 +v -2.0970538352520878 -0.19289371135235903 -0.21848337012732122 +v -2.0970538352520878 0.19289371135235903 0.21848337012732122 +v 2.0970538352520878 0.19289371135235903 -0.21848337012732122 +v 2.0970538352520878 -0.19289371135235903 0.21848337012732122 +v -0.21848337012732122 -2.0970538352520878 -0.19289371135235903 +v -0.21848337012732122 2.0970538352520878 0.19289371135235903 +v 0.21848337012732122 2.0970538352520878 -0.19289371135235903 +v 0.21848337012732122 -2.0970538352520878 0.19289371135235903 +v 0.0 -0.7554672605165956 -1.9778389654202186 +v 0.0 -0.7554672605165956 1.9778389654202186 +v 0.0 0.7554672605165956 -1.9778389654202186 +v 0.0 0.7554672605165956 1.9778389654202186 +v -1.9778389654202186 0.0 -0.7554672605165956 +v -1.9778389654202186 0.0 0.7554672605165956 +v 1.9778389654202186 0.0 -0.7554672605165956 +v 1.9778389654202186 0.0 0.7554672605165956 +v -0.7554672605165956 -1.9778389654202186 0.0 +v -0.7554672605165956 1.9778389654202186 0.0 +v 0.7554672605165956 -1.9778389654202186 0.0 +v 0.7554672605165956 1.9778389654202186 0.0 +v -1.167123436475334 0.0 -1.8884453892836692 +v -1.167123436475334 0.0 1.8884453892836692 +v 1.167123436475334 0.0 -1.8884453892836692 +v 1.167123436475334 0.0 1.8884453892836692 +v -1.8884453892836692 -1.167123436475334 0.0 +v -1.8884453892836692 1.167123436475334 0.0 +v 1.8884453892836692 -1.167123436475334 0.0 +v 1.8884453892836692 1.167123436475334 0.0 +v 0.0 -1.8884453892836692 -1.167123436475334 +v 0.0 -1.8884453892836692 1.167123436475334 +v 0.0 1.8884453892836692 -1.167123436475334 +v 0.0 1.8884453892836692 1.167123436475334 +v -0.5677153694669214 0.8249575526762758 -1.8654013108176957 +v -0.5677153694669214 -0.8249575526762758 1.8654013108176957 +v 0.5677153694669214 -0.8249575526762758 -1.8654013108176957 +v 0.5677153694669214 0.8249575526762758 1.8654013108176957 +v -1.8654013108176957 0.5677153694669214 -0.8249575526762758 +v -1.8654013108176957 -0.5677153694669214 0.8249575526762758 +v 1.8654013108176957 -0.5677153694669214 -0.8249575526762758 +v 1.8654013108176957 0.5677153694669214 0.8249575526762758 +v -0.8249575526762758 1.8654013108176957 -0.5677153694669214 +v -0.8249575526762758 -1.8654013108176957 0.5677153694669214 +v 0.8249575526762758 -1.8654013108176957 -0.5677153694669214 +v 0.8249575526762758 1.8654013108176957 0.5677153694669214 +v -0.3748216581145623 -1.1370661338605041 -1.7461864409858263 +v -0.3748216581145623 1.1370661338605041 1.7461864409858263 +v 0.3748216581145623 1.1370661338605041 -1.7461864409858263 +v 0.3748216581145623 -1.1370661338605041 1.7461864409858263 +v -1.7461864409858263 -0.3748216581145623 -1.1370661338605041 +v -1.7461864409858263 0.3748216581145623 1.1370661338605041 +v 1.7461864409858263 0.3748216581145623 -1.1370661338605041 +v 1.7461864409858263 -0.3748216581145623 1.1370661338605041 +v -1.1370661338605041 -1.7461864409858263 -0.3748216581145623 +v -1.1370661338605041 1.7461864409858263 0.3748216581145623 +v 1.1370661338605041 1.7461864409858263 -0.3748216581145623 +v 1.1370661338605041 -1.7461864409858263 0.3748216581145623 +v -0.9212288883095505 -0.9599877013915838 -1.6469179406903744 +v -0.9212288883095505 0.9599877013915838 1.6469179406903744 +v 0.9212288883095505 0.9599877013915838 -1.6469179406903744 +v 0.9212288883095505 -0.9599877013915838 1.6469179406903744 +v -1.6469179406903744 -0.9212288883095505 -0.9599877013915838 +v -1.6469179406903744 0.9212288883095505 0.9599877013915838 +v 1.6469179406903744 0.9212288883095505 -0.9599877013915838 +v 1.6469179406903744 -0.9212288883095505 0.9599877013915838 +v -0.9599877013915838 -1.6469179406903744 -0.9212288883095505 +v -0.9599877013915838 1.6469179406903744 0.9212288883095505 +v 0.9599877013915838 1.6469179406903744 -0.9212288883095505 +v 0.9599877013915838 -1.6469179406903744 0.9212288883095505 +v -0.7283351769571915 1.2720962825758122 -1.527703070858505 +v -0.7283351769571915 -1.2720962825758122 1.527703070858505 +v 0.7283351769571915 -1.2720962825758122 -1.527703070858505 +v 0.7283351769571915 1.2720962825758122 1.527703070858505 +v -1.527703070858505 0.7283351769571915 -1.2720962825758122 +v -1.527703070858505 -0.7283351769571915 1.2720962825758122 +v 1.527703070858505 -0.7283351769571915 -1.2720962825758122 +v 1.527703070858505 0.7283351769571915 1.2720962825758122 +v -1.2720962825758122 1.527703070858505 -0.7283351769571915 +v -1.2720962825758122 -1.527703070858505 0.7283351769571915 +v 1.2720962825758122 -1.527703070858505 -0.7283351769571915 +v 1.2720962825758122 1.527703070858505 0.7283351769571915 +v -1.2223717049036231 -1.2223717049036231 -1.2223717049036231 +v -1.2223717049036231 -1.2223717049036231 1.2223717049036231 +v -1.2223717049036231 1.2223717049036231 -1.2223717049036231 +v -1.2223717049036231 1.2223717049036231 1.2223717049036231 +v 1.2223717049036231 -1.2223717049036231 -1.2223717049036231 +v 1.2223717049036231 -1.2223717049036231 1.2223717049036231 +v 1.2223717049036231 1.2223717049036231 -1.2223717049036231 +v 1.2223717049036231 1.2223717049036231 1.2223717049036231 +f 25 37 15 3 1 +f 25 77 87 73 37 +f 25 53 17 41 77 +f 25 61 85 65 53 +f 25 1 13 49 61 +f 26 38 14 4 2 +f 26 78 86 74 38 +f 26 54 18 42 78 +f 26 62 88 66 54 +f 26 2 16 50 62 +f 27 39 13 1 3 +f 27 79 89 75 39 +f 27 55 19 43 79 +f 27 63 91 67 55 +f 27 3 15 51 63 +f 28 40 16 2 4 +f 28 80 92 76 40 +f 28 56 20 44 80 +f 28 64 90 68 56 +f 28 4 14 52 64 +f 29 42 18 6 5 +f 29 82 86 78 42 +f 29 57 21 46 82 +f 29 65 85 69 57 +f 29 5 17 53 65 +f 30 41 17 5 6 +f 30 81 87 77 41 +f 30 58 22 45 81 +f 30 66 88 70 58 +f 30 6 18 54 66 +f 31 43 19 7 8 +f 31 83 89 79 43 +f 31 60 23 47 83 +f 31 68 90 72 60 +f 31 8 20 56 68 +f 32 44 20 8 7 +f 32 84 92 80 44 +f 32 59 24 48 84 +f 32 67 91 71 59 +f 32 7 19 55 67 +f 33 47 23 12 9 +f 33 75 89 83 47 +f 33 49 13 39 75 +f 33 69 85 61 49 +f 33 9 21 57 69 +f 34 46 21 9 12 +f 34 74 86 82 46 +f 34 52 14 38 74 +f 34 72 90 64 52 +f 34 12 23 60 72 +f 35 45 22 10 11 +f 35 73 87 81 45 +f 35 51 15 37 73 +f 35 71 91 63 51 +f 35 11 24 59 71 +f 36 48 24 11 10 +f 36 76 92 84 48 +f 36 50 16 40 76 +f 36 70 88 62 50 +f 36 10 22 58 70 diff --git a/webapp/data/LpentagonalIcositetrahedron.obj b/webapp/data/LpentagonalIcositetrahedron.obj new file mode 100644 index 0000000..fe0acc3 --- /dev/null +++ b/webapp/data/LpentagonalIcositetrahedron.obj @@ -0,0 +1,62 @@ +v 0.0 0.0 -1.3614101519264425 +v 0.0 0.0 1.3614101519264425 +v -1.3614101519264425 0.0 0.0 +v 1.3614101519264425 0.0 0.0 +v 0.0 -1.3614101519264425 0.0 +v 0.0 1.3614101519264425 0.0 +v -0.7401837413698572 0.21879664300048043 -1.0236561781126903 +v -0.7401837413698572 -0.21879664300048043 1.0236561781126903 +v 0.7401837413698572 -0.21879664300048043 -1.0236561781126903 +v 0.7401837413698572 0.21879664300048043 1.0236561781126903 +v -1.0236561781126903 0.7401837413698572 -0.21879664300048043 +v -1.0236561781126903 -0.7401837413698572 0.21879664300048043 +v 1.0236561781126903 -0.7401837413698572 -0.21879664300048043 +v 1.0236561781126903 0.7401837413698572 0.21879664300048043 +v -0.21879664300048043 1.0236561781126903 -0.7401837413698572 +v -0.21879664300048043 -1.0236561781126903 0.7401837413698572 +v 0.21879664300048043 -1.0236561781126903 -0.7401837413698572 +v 0.21879664300048043 1.0236561781126903 0.7401837413698572 +v -0.21879664300048043 -0.7401837413698572 -1.0236561781126903 +v -0.21879664300048043 0.7401837413698572 1.0236561781126903 +v 0.21879664300048043 0.7401837413698572 -1.0236561781126903 +v 0.21879664300048043 -0.7401837413698572 1.0236561781126903 +v -1.0236561781126903 -0.21879664300048043 -0.7401837413698572 +v -1.0236561781126903 0.21879664300048043 0.7401837413698572 +v 1.0236561781126903 0.21879664300048043 -0.7401837413698572 +v 1.0236561781126903 -0.21879664300048043 0.7401837413698572 +v -0.7401837413698572 -1.0236561781126903 -0.21879664300048043 +v -0.7401837413698572 1.0236561781126903 0.21879664300048043 +v 0.7401837413698572 1.0236561781126903 -0.21879664300048043 +v 0.7401837413698572 -1.0236561781126903 0.21879664300048043 +v -0.7401837413698572 -0.7401837413698572 -0.7401837413698572 +v -0.7401837413698572 -0.7401837413698572 0.7401837413698572 +v -0.7401837413698572 0.7401837413698572 -0.7401837413698572 +v -0.7401837413698572 0.7401837413698572 0.7401837413698572 +v 0.7401837413698572 -0.7401837413698572 -0.7401837413698572 +v 0.7401837413698572 -0.7401837413698572 0.7401837413698572 +v 0.7401837413698572 0.7401837413698572 -0.7401837413698572 +v 0.7401837413698572 0.7401837413698572 0.7401837413698572 +f 1 19 31 23 7 +f 1 9 35 17 19 +f 1 21 37 25 9 +f 1 7 33 15 21 +f 2 20 34 24 8 +f 2 10 38 18 20 +f 2 22 36 26 10 +f 2 8 32 16 22 +f 3 24 34 28 11 +f 3 12 32 8 24 +f 3 23 31 27 12 +f 3 11 33 7 23 +f 4 26 36 30 13 +f 4 14 38 10 26 +f 4 25 37 29 14 +f 4 13 35 9 25 +f 5 30 36 22 16 +f 5 17 35 13 30 +f 5 27 31 19 17 +f 5 16 32 12 27 +f 6 29 37 21 15 +f 6 18 38 14 29 +f 6 28 34 20 18 +f 6 15 33 11 28 diff --git a/webapp/data/LsnubCube.obj b/webapp/data/LsnubCube.obj new file mode 100644 index 0000000..0569f49 --- /dev/null +++ b/webapp/data/LsnubCube.obj @@ -0,0 +1,62 @@ +v 0.6212264105565853 0.33775397381375233 1.1426135089259621 +v 0.6212264105565853 -0.33775397381375233 -1.1426135089259621 +v -0.6212264105565853 -0.33775397381375233 1.1426135089259621 +v -0.6212264105565853 0.33775397381375233 -1.1426135089259621 +v 1.1426135089259621 0.6212264105565853 0.33775397381375233 +v 1.1426135089259621 -0.6212264105565853 -0.33775397381375233 +v -1.1426135089259621 -0.6212264105565853 0.33775397381375233 +v -1.1426135089259621 0.6212264105565853 -0.33775397381375233 +v 0.33775397381375233 1.1426135089259621 0.6212264105565853 +v 0.33775397381375233 -1.1426135089259621 -0.6212264105565853 +v -0.33775397381375233 -1.1426135089259621 0.6212264105565853 +v -0.33775397381375233 1.1426135089259621 -0.6212264105565853 +v 0.33775397381375233 -0.6212264105565853 1.1426135089259621 +v 0.33775397381375233 0.6212264105565853 -1.1426135089259621 +v -0.33775397381375233 0.6212264105565853 1.1426135089259621 +v -0.33775397381375233 -0.6212264105565853 -1.1426135089259621 +v 1.1426135089259621 -0.33775397381375233 0.6212264105565853 +v 1.1426135089259621 0.33775397381375233 -0.6212264105565853 +v -1.1426135089259621 0.33775397381375233 0.6212264105565853 +v -1.1426135089259621 -0.33775397381375233 -0.6212264105565853 +v 0.6212264105565853 -1.1426135089259621 0.33775397381375233 +v 0.6212264105565853 1.1426135089259621 -0.33775397381375233 +v -0.6212264105565853 1.1426135089259621 0.33775397381375233 +v -0.6212264105565853 -1.1426135089259621 -0.33775397381375233 +f 3 13 1 15 +f 4 14 2 16 +f 5 17 6 18 +f 8 20 7 19 +f 9 22 12 23 +f 10 21 11 24 +f 1 9 15 +f 2 10 16 +f 3 11 13 +f 4 12 14 +f 5 1 17 +f 6 2 18 +f 7 3 19 +f 8 4 20 +f 9 5 22 +f 10 6 21 +f 11 7 24 +f 12 8 23 +f 13 17 1 +f 14 18 2 +f 15 19 3 +f 16 20 4 +f 17 21 6 +f 18 22 5 +f 19 23 8 +f 20 24 7 +f 21 13 11 +f 22 14 12 +f 23 15 9 +f 24 16 10 +f 9 1 5 +f 10 2 6 +f 11 3 7 +f 12 4 8 +f 13 21 17 +f 14 22 18 +f 15 23 19 +f 16 24 20 diff --git a/webapp/data/LsnubDodecahedron.obj b/webapp/data/LsnubDodecahedron.obj new file mode 100644 index 0000000..c33da1b --- /dev/null +++ b/webapp/data/LsnubDodecahedron.obj @@ -0,0 +1,152 @@ +v 0.3748216581145623 -0.33092102472984425 2.0970538352520878 +v 0.3748216581145623 0.33092102472984425 -2.0970538352520878 +v -0.3748216581145623 0.33092102472984425 2.0970538352520878 +v -0.3748216581145623 -0.33092102472984425 -2.0970538352520878 +v 2.0970538352520878 -0.3748216581145623 0.33092102472984425 +v 2.0970538352520878 0.3748216581145623 -0.33092102472984425 +v -2.0970538352520878 0.3748216581145623 0.33092102472984425 +v -2.0970538352520878 -0.3748216581145623 -0.33092102472984425 +v 0.33092102472984425 -2.0970538352520878 0.3748216581145623 +v 0.33092102472984425 2.0970538352520878 -0.3748216581145623 +v -0.33092102472984425 2.0970538352520878 0.3748216581145623 +v -0.33092102472984425 -2.0970538352520878 -0.3748216581145623 +v 0.5677153694669214 0.6430296059140725 1.9778389654202186 +v 0.5677153694669214 -0.6430296059140725 -1.9778389654202186 +v -0.5677153694669214 -0.6430296059140725 1.9778389654202186 +v -0.5677153694669214 0.6430296059140725 -1.9778389654202186 +v 1.9778389654202186 0.5677153694669214 0.6430296059140725 +v 1.9778389654202186 -0.5677153694669214 -0.6430296059140725 +v -1.9778389654202186 -0.5677153694669214 0.6430296059140725 +v -1.9778389654202186 0.5677153694669214 -0.6430296059140725 +v 0.6430296059140725 1.9778389654202186 0.5677153694669214 +v 0.6430296059140725 -1.9778389654202186 -0.5677153694669214 +v -0.6430296059140725 -1.9778389654202186 0.5677153694669214 +v -0.6430296059140725 1.9778389654202186 -0.5677153694669214 +v 0.19289371135235903 -1.2495037884630271 1.7461864409858263 +v 0.19289371135235903 1.2495037884630271 -1.7461864409858263 +v -0.19289371135235903 1.2495037884630271 1.7461864409858263 +v -0.19289371135235903 -1.2495037884630271 -1.7461864409858263 +v 1.7461864409858263 -0.19289371135235903 1.2495037884630271 +v 1.7461864409858263 0.19289371135235903 -1.2495037884630271 +v -1.7461864409858263 0.19289371135235903 1.2495037884630271 +v -1.7461864409858263 -0.19289371135235903 -1.2495037884630271 +v 1.2495037884630271 -1.7461864409858263 0.19289371135235903 +v 1.2495037884630271 1.7461864409858263 -0.19289371135235903 +v -1.2495037884630271 1.7461864409858263 0.19289371135235903 +v -1.2495037884630271 -1.7461864409858263 -0.19289371135235903 +v 1.1031568350717538 -0.8475500467890608 1.6469179406903744 +v 1.1031568350717538 0.8475500467890608 -1.6469179406903744 +v -1.1031568350717538 0.8475500467890608 1.6469179406903744 +v -1.1031568350717538 -0.8475500467890608 -1.6469179406903744 +v 1.6469179406903744 -1.1031568350717538 0.8475500467890608 +v 1.6469179406903744 1.1031568350717538 -0.8475500467890608 +v -1.6469179406903744 1.1031568350717538 0.8475500467890608 +v -1.6469179406903744 -1.1031568350717538 -0.8475500467890608 +v 0.8475500467890608 -1.6469179406903744 1.1031568350717538 +v 0.8475500467890608 1.6469179406903744 -1.1031568350717538 +v -0.8475500467890608 1.6469179406903744 1.1031568350717538 +v -0.8475500467890608 -1.6469179406903744 -1.1031568350717538 +v 1.4152654162559821 0.7283351769571915 1.4540242293380155 +v 1.4152654162559821 -0.7283351769571915 -1.4540242293380155 +v -1.4152654162559821 -0.7283351769571915 1.4540242293380155 +v -1.4152654162559821 0.7283351769571915 -1.4540242293380155 +v 1.4540242293380155 1.4152654162559821 0.7283351769571915 +v 1.4540242293380155 -1.4152654162559821 -0.7283351769571915 +v -1.4540242293380155 -1.4152654162559821 0.7283351769571915 +v -1.4540242293380155 1.4152654162559821 -0.7283351769571915 +v 0.7283351769571915 1.4540242293380155 1.4152654162559821 +v 0.7283351769571915 -1.4540242293380155 -1.4152654162559821 +v -0.7283351769571915 -1.4540242293380155 1.4152654162559821 +v -0.7283351769571915 1.4540242293380155 -1.4152654162559821 +f 1 37 29 49 13 +f 2 38 30 50 14 +f 3 39 31 51 15 +f 4 40 32 52 16 +f 5 41 33 54 18 +f 6 42 34 53 17 +f 7 43 35 56 20 +f 8 44 36 55 19 +f 9 45 25 59 23 +f 10 46 26 60 24 +f 11 47 27 57 21 +f 12 48 28 58 22 +f 1 3 15 +f 2 4 16 +f 3 1 13 +f 4 2 14 +f 5 6 17 +f 6 5 18 +f 7 8 19 +f 8 7 20 +f 9 12 22 +f 10 11 21 +f 11 10 24 +f 12 9 23 +f 13 49 57 +f 14 50 58 +f 15 51 59 +f 16 52 60 +f 17 53 49 +f 18 54 50 +f 19 55 51 +f 20 56 52 +f 21 57 53 +f 22 58 54 +f 23 59 55 +f 24 60 56 +f 25 45 37 +f 26 46 38 +f 27 47 39 +f 28 48 40 +f 29 37 41 +f 30 38 42 +f 31 39 43 +f 32 40 44 +f 33 41 45 +f 34 42 46 +f 35 43 47 +f 36 44 48 +f 37 1 25 +f 38 2 26 +f 39 3 27 +f 40 4 28 +f 41 5 29 +f 42 6 30 +f 43 7 31 +f 44 8 32 +f 45 9 33 +f 46 10 34 +f 47 11 35 +f 48 12 36 +f 49 29 17 +f 50 30 18 +f 51 31 19 +f 52 32 20 +f 53 34 21 +f 54 33 22 +f 55 36 23 +f 56 35 24 +f 57 27 13 +f 58 28 14 +f 59 25 15 +f 60 26 16 +f 25 1 15 +f 26 2 16 +f 27 3 13 +f 28 4 14 +f 29 5 17 +f 30 6 18 +f 31 7 19 +f 32 8 20 +f 33 9 22 +f 34 10 21 +f 35 11 24 +f 36 12 23 +f 37 45 41 +f 38 46 42 +f 39 47 43 +f 40 48 44 +f 49 53 57 +f 50 54 58 +f 51 55 59 +f 52 56 60 diff --git a/webapp/data/Octahedron.obj b/webapp/data/Octahedron.obj new file mode 100644 index 0000000..72a451c --- /dev/null +++ b/webapp/data/Octahedron.obj @@ -0,0 +1,14 @@ +v 0.0 0.0 0.7071067811865476 +v 0.0 0.0 -0.7071067811865476 +v 0.7071067811865476 0.0 0.0 +v -0.7071067811865476 0.0 0.0 +v 0.0 0.7071067811865476 0.0 +v 0.0 -0.7071067811865476 0.0 +f 1 3 5 +f 1 5 4 +f 1 4 6 +f 1 6 3 +f 2 3 6 +f 2 6 4 +f 2 4 5 +f 2 5 3 diff --git a/webapp/data/PentakisDodecahedron.obj b/webapp/data/PentakisDodecahedron.obj new file mode 100644 index 0000000..c127642 --- /dev/null +++ b/webapp/data/PentakisDodecahedron.obj @@ -0,0 +1,92 @@ +v 0.0 0.9270509831248422 2.4270509831248424 +v 0.0 0.9270509831248422 -2.4270509831248424 +v 0.0 -0.9270509831248422 2.4270509831248424 +v 0.0 -0.9270509831248422 -2.4270509831248424 +v 2.4270509831248424 0.0 0.9270509831248422 +v 2.4270509831248424 0.0 -0.9270509831248422 +v -2.4270509831248424 0.0 0.9270509831248422 +v -2.4270509831248424 0.0 -0.9270509831248422 +v 0.9270509831248422 2.4270509831248424 0.0 +v 0.9270509831248422 -2.4270509831248424 0.0 +v -0.9270509831248422 2.4270509831248424 0.0 +v -0.9270509831248422 -2.4270509831248424 0.0 +v 1.3305869973355013 0.0 2.152934986677507 +v 1.3305869973355013 0.0 -2.152934986677507 +v -1.3305869973355013 0.0 2.152934986677507 +v -1.3305869973355013 0.0 -2.152934986677507 +v 2.152934986677507 1.3305869973355013 0.0 +v 2.152934986677507 -1.3305869973355013 0.0 +v -2.152934986677507 1.3305869973355013 0.0 +v -2.152934986677507 -1.3305869973355013 0.0 +v 0.0 2.152934986677507 1.3305869973355013 +v 0.0 2.152934986677507 -1.3305869973355013 +v 0.0 -2.152934986677507 1.3305869973355013 +v 0.0 -2.152934986677507 -1.3305869973355013 +v 1.5 1.5 1.5 +v 1.5 1.5 -1.5 +v 1.5 -1.5 1.5 +v 1.5 -1.5 -1.5 +v -1.5 1.5 1.5 +v -1.5 1.5 -1.5 +v -1.5 -1.5 1.5 +v -1.5 -1.5 -1.5 +f 13 1 3 +f 13 3 27 +f 13 27 5 +f 13 5 25 +f 13 25 1 +f 14 4 2 +f 14 2 26 +f 14 26 6 +f 14 6 28 +f 14 28 4 +f 15 3 1 +f 15 1 29 +f 15 29 7 +f 15 7 31 +f 15 31 3 +f 16 2 4 +f 16 4 32 +f 16 32 8 +f 16 8 30 +f 16 30 2 +f 17 5 6 +f 17 6 26 +f 17 26 9 +f 17 9 25 +f 17 25 5 +f 18 6 5 +f 18 5 27 +f 18 27 10 +f 18 10 28 +f 18 28 6 +f 19 8 7 +f 19 7 29 +f 19 29 11 +f 19 11 30 +f 19 30 8 +f 20 7 8 +f 20 8 32 +f 20 32 12 +f 20 12 31 +f 20 31 7 +f 21 9 11 +f 21 11 29 +f 21 29 1 +f 21 1 25 +f 21 25 9 +f 22 11 9 +f 22 9 26 +f 22 26 2 +f 22 2 30 +f 22 30 11 +f 23 12 10 +f 23 10 27 +f 23 27 3 +f 23 3 31 +f 23 31 12 +f 24 10 12 +f 24 12 32 +f 24 32 4 +f 24 4 28 +f 24 28 10 diff --git a/webapp/data/RhombicDodecahedron.obj b/webapp/data/RhombicDodecahedron.obj new file mode 100644 index 0000000..a612edb --- /dev/null +++ b/webapp/data/RhombicDodecahedron.obj @@ -0,0 +1,26 @@ +v 0.0 0.0 1.0606601717798212 +v 0.0 0.0 -1.0606601717798212 +v 1.0606601717798212 0.0 0.0 +v -1.0606601717798212 0.0 0.0 +v 0.0 1.0606601717798212 0.0 +v 0.0 -1.0606601717798212 0.0 +v 0.5303300858899106 0.5303300858899106 0.5303300858899106 +v 0.5303300858899106 0.5303300858899106 -0.5303300858899106 +v 0.5303300858899106 -0.5303300858899106 0.5303300858899106 +v 0.5303300858899106 -0.5303300858899106 -0.5303300858899106 +v -0.5303300858899106 0.5303300858899106 0.5303300858899106 +v -0.5303300858899106 0.5303300858899106 -0.5303300858899106 +v -0.5303300858899106 -0.5303300858899106 0.5303300858899106 +v -0.5303300858899106 -0.5303300858899106 -0.5303300858899106 +f 7 1 9 3 +f 7 3 8 5 +f 7 5 11 1 +f 10 2 8 3 +f 10 3 9 6 +f 10 6 14 2 +f 12 2 14 4 +f 12 4 11 5 +f 12 5 8 2 +f 13 1 11 4 +f 13 4 14 6 +f 13 6 9 1 diff --git a/webapp/data/RhombicTriacontahedron.obj b/webapp/data/RhombicTriacontahedron.obj new file mode 100644 index 0000000..4a3c8b2 --- /dev/null +++ b/webapp/data/RhombicTriacontahedron.obj @@ -0,0 +1,62 @@ +v 0.9045084971874737 0.0 1.4635254915624212 +v 0.9045084971874737 0.0 -1.4635254915624212 +v -0.9045084971874737 0.0 1.4635254915624212 +v -0.9045084971874737 0.0 -1.4635254915624212 +v 1.4635254915624212 0.9045084971874737 0.0 +v 1.4635254915624212 -0.9045084971874737 0.0 +v -1.4635254915624212 0.9045084971874737 0.0 +v -1.4635254915624212 -0.9045084971874737 0.0 +v 0.0 1.4635254915624212 0.9045084971874737 +v 0.0 1.4635254915624212 -0.9045084971874737 +v 0.0 -1.4635254915624212 0.9045084971874737 +v 0.0 -1.4635254915624212 -0.9045084971874737 +v 0.0 0.5590169943749475 1.4635254915624212 +v 0.0 0.5590169943749475 -1.4635254915624212 +v 0.0 -0.5590169943749475 1.4635254915624212 +v 0.0 -0.5590169943749475 -1.4635254915624212 +v 1.4635254915624212 0.0 0.5590169943749475 +v 1.4635254915624212 0.0 -0.5590169943749475 +v -1.4635254915624212 0.0 0.5590169943749475 +v -1.4635254915624212 0.0 -0.5590169943749475 +v 0.5590169943749475 1.4635254915624212 0.0 +v 0.5590169943749475 -1.4635254915624212 0.0 +v -0.5590169943749475 1.4635254915624212 0.0 +v -0.5590169943749475 -1.4635254915624212 0.0 +v 0.9045084971874737 0.9045084971874737 0.9045084971874737 +v 0.9045084971874737 0.9045084971874737 -0.9045084971874737 +v 0.9045084971874737 -0.9045084971874737 0.9045084971874737 +v 0.9045084971874737 -0.9045084971874737 -0.9045084971874737 +v -0.9045084971874737 0.9045084971874737 0.9045084971874737 +v -0.9045084971874737 0.9045084971874737 -0.9045084971874737 +v -0.9045084971874737 -0.9045084971874737 0.9045084971874737 +v -0.9045084971874737 -0.9045084971874737 -0.9045084971874737 +f 1 13 3 15 +f 1 15 11 27 +f 1 27 6 17 +f 2 14 10 26 +f 2 26 5 18 +f 2 18 6 28 +f 3 29 7 19 +f 3 19 8 31 +f 3 31 11 15 +f 4 20 7 30 +f 4 30 10 14 +f 4 14 2 16 +f 5 21 9 25 +f 5 25 1 17 +f 5 17 6 18 +f 8 19 7 20 +f 8 20 4 32 +f 8 32 12 24 +f 9 23 7 29 +f 9 29 3 13 +f 9 13 1 25 +f 10 30 7 23 +f 10 23 9 21 +f 10 21 5 26 +f 11 31 8 24 +f 11 24 12 22 +f 11 22 6 27 +f 12 32 4 16 +f 12 16 2 28 +f 12 28 6 22 diff --git a/webapp/data/Rhombicosacron.obj b/webapp/data/Rhombicosacron.obj new file mode 100644 index 0000000..4921cb0 --- /dev/null +++ b/webapp/data/Rhombicosacron.obj @@ -0,0 +1,110 @@ +v 0.0 0.6180339887498949 1.618033988749895 +v 0.0 0.6180339887498949 -1.618033988749895 +v 0.0 -0.6180339887498949 1.618033988749895 +v 0.0 -0.6180339887498949 -1.618033988749895 +v 1.618033988749895 0.0 0.6180339887498949 +v 1.618033988749895 0.0 -0.6180339887498949 +v -1.618033988749895 0.0 0.6180339887498949 +v -1.618033988749895 0.0 -0.6180339887498949 +v 0.6180339887498949 1.618033988749895 0.0 +v 0.6180339887498949 -1.618033988749895 0.0 +v -0.6180339887498949 1.618033988749895 0.0 +v -0.6180339887498949 -1.618033988749895 0.0 +v 0.0 0.0 1.3416407864998738 +v 0.0 0.0 -1.3416407864998738 +v 1.3416407864998738 0.0 0.0 +v -1.3416407864998738 0.0 0.0 +v 0.0 1.3416407864998738 0.0 +v 0.0 -1.3416407864998738 0.0 +v 0.41458980337503154 0.6708203932499369 1.0854101966249685 +v 0.41458980337503154 0.6708203932499369 -1.0854101966249685 +v 0.41458980337503154 -0.6708203932499369 1.0854101966249685 +v 0.41458980337503154 -0.6708203932499369 -1.0854101966249685 +v -0.41458980337503154 0.6708203932499369 1.0854101966249685 +v -0.41458980337503154 0.6708203932499369 -1.0854101966249685 +v -0.41458980337503154 -0.6708203932499369 1.0854101966249685 +v -0.41458980337503154 -0.6708203932499369 -1.0854101966249685 +v 1.0854101966249685 0.41458980337503154 0.6708203932499369 +v 1.0854101966249685 0.41458980337503154 -0.6708203932499369 +v 1.0854101966249685 -0.41458980337503154 0.6708203932499369 +v 1.0854101966249685 -0.41458980337503154 -0.6708203932499369 +v -1.0854101966249685 0.41458980337503154 0.6708203932499369 +v -1.0854101966249685 0.41458980337503154 -0.6708203932499369 +v -1.0854101966249685 -0.41458980337503154 0.6708203932499369 +v -1.0854101966249685 -0.41458980337503154 -0.6708203932499369 +v 0.6708203932499369 1.0854101966249685 0.41458980337503154 +v 0.6708203932499369 1.0854101966249685 -0.41458980337503154 +v 0.6708203932499369 -1.0854101966249685 0.41458980337503154 +v 0.6708203932499369 -1.0854101966249685 -0.41458980337503154 +v -0.6708203932499369 1.0854101966249685 0.41458980337503154 +v -0.6708203932499369 1.0854101966249685 -0.41458980337503154 +v -0.6708203932499369 -1.0854101966249685 0.41458980337503154 +v -0.6708203932499369 -1.0854101966249685 -0.41458980337503154 +v 1.0 1.0 1.0 +v 1.0 1.0 -1.0 +v 1.0 -1.0 1.0 +v 1.0 -1.0 -1.0 +v -1.0 1.0 1.0 +v -1.0 1.0 -1.0 +v -1.0 -1.0 1.0 +v -1.0 -1.0 -1.0 +f 1 17 9 23 +f 1 29 45 19 +f 1 33 7 13 +f 2 17 11 20 +f 2 34 50 24 +f 2 30 6 14 +f 3 18 12 21 +f 3 31 47 25 +f 3 27 5 13 +f 4 18 10 26 +f 4 28 44 22 +f 4 32 8 14 +f 5 13 1 29 +f 5 36 44 27 +f 5 38 10 15 +f 6 14 4 28 +f 6 37 45 30 +f 6 35 9 15 +f 7 13 3 31 +f 7 42 50 33 +f 7 40 11 16 +f 8 14 2 34 +f 8 39 47 32 +f 8 41 12 16 +f 9 15 5 36 +f 9 23 47 35 +f 9 24 2 17 +f 10 15 6 37 +f 10 26 50 38 +f 10 25 3 18 +f 11 16 8 39 +f 11 20 44 40 +f 11 19 1 17 +f 12 16 7 42 +f 12 21 45 41 +f 12 22 4 18 +f 43 21 3 27 +f 43 39 11 19 +f 43 28 6 35 +f 44 22 46 20 +f 44 27 43 28 +f 44 40 48 36 +f 45 19 43 21 +f 45 30 46 29 +f 45 41 49 37 +f 46 20 2 30 +f 46 42 12 22 +f 46 29 5 38 +f 47 25 49 23 +f 47 32 48 31 +f 47 35 43 39 +f 48 26 4 32 +f 48 36 9 24 +f 48 31 7 40 +f 49 23 1 33 +f 49 37 10 25 +f 49 34 8 41 +f 50 24 48 26 +f 50 33 49 34 +f 50 38 46 42 diff --git a/webapp/data/Rhombicosidodecahedron.obj b/webapp/data/Rhombicosidodecahedron.obj new file mode 100644 index 0000000..c717b4f --- /dev/null +++ b/webapp/data/Rhombicosidodecahedron.obj @@ -0,0 +1,122 @@ +v 0.5 0.5 2.118033988749895 +v 0.5 0.5 -2.118033988749895 +v 0.5 -0.5 2.118033988749895 +v 0.5 -0.5 -2.118033988749895 +v -0.5 0.5 2.118033988749895 +v -0.5 0.5 -2.118033988749895 +v -0.5 -0.5 2.118033988749895 +v -0.5 -0.5 -2.118033988749895 +v 2.118033988749895 0.5 0.5 +v 2.118033988749895 0.5 -0.5 +v 2.118033988749895 -0.5 0.5 +v 2.118033988749895 -0.5 -0.5 +v -2.118033988749895 0.5 0.5 +v -2.118033988749895 0.5 -0.5 +v -2.118033988749895 -0.5 0.5 +v -2.118033988749895 -0.5 -0.5 +v 0.5 2.118033988749895 0.5 +v 0.5 2.118033988749895 -0.5 +v 0.5 -2.118033988749895 0.5 +v 0.5 -2.118033988749895 -0.5 +v -0.5 2.118033988749895 0.5 +v -0.5 2.118033988749895 -0.5 +v -0.5 -2.118033988749895 0.5 +v -0.5 -2.118033988749895 -0.5 +v 0.0 1.3090169943749475 1.8090169943749475 +v 0.0 1.3090169943749475 -1.8090169943749475 +v 0.0 -1.3090169943749475 1.8090169943749475 +v 0.0 -1.3090169943749475 -1.8090169943749475 +v 1.8090169943749475 0.0 1.3090169943749475 +v 1.8090169943749475 0.0 -1.3090169943749475 +v -1.8090169943749475 0.0 1.3090169943749475 +v -1.8090169943749475 0.0 -1.3090169943749475 +v 1.3090169943749475 1.8090169943749475 0.0 +v 1.3090169943749475 -1.8090169943749475 0.0 +v -1.3090169943749475 1.8090169943749475 0.0 +v -1.3090169943749475 -1.8090169943749475 0.0 +v 1.3090169943749475 0.8090169943749475 1.618033988749895 +v 1.3090169943749475 0.8090169943749475 -1.618033988749895 +v 1.3090169943749475 -0.8090169943749475 1.618033988749895 +v 1.3090169943749475 -0.8090169943749475 -1.618033988749895 +v -1.3090169943749475 0.8090169943749475 1.618033988749895 +v -1.3090169943749475 0.8090169943749475 -1.618033988749895 +v -1.3090169943749475 -0.8090169943749475 1.618033988749895 +v -1.3090169943749475 -0.8090169943749475 -1.618033988749895 +v 1.618033988749895 1.3090169943749475 0.8090169943749475 +v 1.618033988749895 1.3090169943749475 -0.8090169943749475 +v 1.618033988749895 -1.3090169943749475 0.8090169943749475 +v 1.618033988749895 -1.3090169943749475 -0.8090169943749475 +v -1.618033988749895 1.3090169943749475 0.8090169943749475 +v -1.618033988749895 1.3090169943749475 -0.8090169943749475 +v -1.618033988749895 -1.3090169943749475 0.8090169943749475 +v -1.618033988749895 -1.3090169943749475 -0.8090169943749475 +v 0.8090169943749475 1.618033988749895 1.3090169943749475 +v 0.8090169943749475 1.618033988749895 -1.3090169943749475 +v 0.8090169943749475 -1.618033988749895 1.3090169943749475 +v 0.8090169943749475 -1.618033988749895 -1.3090169943749475 +v -0.8090169943749475 1.618033988749895 1.3090169943749475 +v -0.8090169943749475 1.618033988749895 -1.3090169943749475 +v -0.8090169943749475 -1.618033988749895 1.3090169943749475 +v -0.8090169943749475 -1.618033988749895 -1.3090169943749475 +f 25 53 17 21 57 +f 26 58 22 18 54 +f 27 59 23 19 55 +f 28 56 20 24 60 +f 29 37 1 3 39 +f 30 40 4 2 38 +f 31 43 7 5 41 +f 32 42 6 8 44 +f 33 45 9 10 46 +f 34 48 12 11 47 +f 35 50 14 13 49 +f 36 51 15 16 52 +f 1 37 53 25 +f 2 26 54 38 +f 3 27 55 39 +f 4 40 56 28 +f 5 25 57 41 +f 6 42 58 26 +f 7 43 59 27 +f 8 28 60 44 +f 9 45 37 29 +f 10 30 38 46 +f 11 29 39 47 +f 12 48 40 30 +f 13 31 41 49 +f 14 50 42 32 +f 15 51 43 31 +f 16 32 44 52 +f 17 53 45 33 +f 18 33 46 54 +f 19 34 47 55 +f 20 56 48 34 +f 21 35 49 57 +f 22 58 50 35 +f 23 59 51 36 +f 24 36 52 60 +f 1 5 7 3 +f 2 4 8 6 +f 9 11 12 10 +f 13 14 16 15 +f 17 18 22 21 +f 19 23 24 20 +f 25 5 1 +f 26 2 6 +f 27 3 7 +f 28 8 4 +f 29 11 9 +f 30 10 12 +f 31 13 15 +f 32 16 14 +f 33 18 17 +f 34 19 20 +f 35 21 22 +f 36 24 23 +f 37 45 53 +f 38 54 46 +f 39 55 47 +f 40 48 56 +f 41 57 49 +f 42 50 58 +f 43 51 59 +f 44 60 52 diff --git a/webapp/data/Rhombicuboctahedron.obj b/webapp/data/Rhombicuboctahedron.obj new file mode 100644 index 0000000..23ed846 --- /dev/null +++ b/webapp/data/Rhombicuboctahedron.obj @@ -0,0 +1,50 @@ +v 0.5 0.5 1.2071067811865475 +v 0.5 0.5 -1.2071067811865475 +v 0.5 -0.5 1.2071067811865475 +v 0.5 -0.5 -1.2071067811865475 +v -0.5 0.5 1.2071067811865475 +v -0.5 0.5 -1.2071067811865475 +v -0.5 -0.5 1.2071067811865475 +v -0.5 -0.5 -1.2071067811865475 +v 1.2071067811865475 0.5 0.5 +v 1.2071067811865475 0.5 -0.5 +v 1.2071067811865475 -0.5 0.5 +v 1.2071067811865475 -0.5 -0.5 +v -1.2071067811865475 0.5 0.5 +v -1.2071067811865475 0.5 -0.5 +v -1.2071067811865475 -0.5 0.5 +v -1.2071067811865475 -0.5 -0.5 +v 0.5 1.2071067811865475 0.5 +v 0.5 1.2071067811865475 -0.5 +v 0.5 -1.2071067811865475 0.5 +v 0.5 -1.2071067811865475 -0.5 +v -0.5 1.2071067811865475 0.5 +v -0.5 1.2071067811865475 -0.5 +v -0.5 -1.2071067811865475 0.5 +v -0.5 -1.2071067811865475 -0.5 +f 1 5 7 3 +f 2 4 8 6 +f 9 11 12 10 +f 13 14 16 15 +f 17 18 22 21 +f 19 23 24 20 +f 1 3 11 9 +f 1 17 21 5 +f 8 4 20 24 +f 8 16 14 6 +f 12 4 2 10 +f 12 11 19 20 +f 13 15 7 5 +f 13 21 22 14 +f 18 2 6 22 +f 18 17 9 10 +f 23 15 16 24 +f 23 19 3 7 +f 1 9 17 +f 2 18 10 +f 3 19 11 +f 4 12 20 +f 5 21 13 +f 6 14 22 +f 7 15 23 +f 8 24 16 diff --git a/webapp/data/SmallStellatedDodecahedron.obj b/webapp/data/SmallStellatedDodecahedron.obj new file mode 100644 index 0000000..54c7fe3 --- /dev/null +++ b/webapp/data/SmallStellatedDodecahedron.obj @@ -0,0 +1,24 @@ +v 0.0 0.5 -0.30901699437494745 +v 0.0 0.5 0.30901699437494745 +v 0.0 -0.5 -0.30901699437494745 +v 0.0 -0.5 0.30901699437494745 +v 0.5 -0.30901699437494745 0.0 +v -0.5 -0.30901699437494745 0.0 +v 0.5 0.30901699437494745 0.0 +v -0.5 0.30901699437494745 0.0 +v -0.30901699437494745 0.0 0.5 +v -0.30901699437494745 0.0 -0.5 +v 0.30901699437494745 0.0 0.5 +v 0.30901699437494745 0.0 -0.5 +f 1 3 8 12 6 +f 1 6 2 10 9 +f 1 9 7 8 11 +f 2 4 7 9 5 +f 2 5 1 11 12 +f 2 12 8 7 10 +f 3 1 5 10 7 +f 3 7 4 12 11 +f 3 11 6 5 9 +f 4 2 6 11 8 +f 4 8 3 9 10 +f 4 10 5 6 12 diff --git a/webapp/data/Tetrahedron.obj b/webapp/data/Tetrahedron.obj new file mode 100644 index 0000000..ed56961 --- /dev/null +++ b/webapp/data/Tetrahedron.obj @@ -0,0 +1,8 @@ +v 0.3535533905932738 -0.3535533905932738 0.3535533905932738 +v 0.3535533905932738 0.3535533905932738 -0.3535533905932738 +v -0.3535533905932738 0.3535533905932738 0.3535533905932738 +v -0.3535533905932738 -0.3535533905932738 -0.3535533905932738 +f 1 2 3 +f 2 1 4 +f 3 4 1 +f 4 3 2 diff --git a/webapp/data/TetrakisHexahedron.obj b/webapp/data/TetrakisHexahedron.obj new file mode 100644 index 0000000..2583006 --- /dev/null +++ b/webapp/data/TetrakisHexahedron.obj @@ -0,0 +1,38 @@ +v 0.0 0.0 1.590990257669732 +v 0.0 0.0 -1.590990257669732 +v 1.590990257669732 0.0 0.0 +v -1.590990257669732 0.0 0.0 +v 0.0 1.590990257669732 0.0 +v 0.0 -1.590990257669732 0.0 +v 1.0606601717798212 1.0606601717798212 1.0606601717798212 +v 1.0606601717798212 1.0606601717798212 -1.0606601717798212 +v 1.0606601717798212 -1.0606601717798212 1.0606601717798212 +v 1.0606601717798212 -1.0606601717798212 -1.0606601717798212 +v -1.0606601717798212 1.0606601717798212 1.0606601717798212 +v -1.0606601717798212 1.0606601717798212 -1.0606601717798212 +v -1.0606601717798212 -1.0606601717798212 1.0606601717798212 +v -1.0606601717798212 -1.0606601717798212 -1.0606601717798212 +f 1 7 11 +f 1 11 13 +f 1 13 9 +f 1 9 7 +f 2 8 10 +f 2 10 14 +f 2 14 12 +f 2 12 8 +f 3 7 9 +f 3 9 10 +f 3 10 8 +f 3 8 7 +f 4 11 12 +f 4 12 14 +f 4 14 13 +f 4 13 11 +f 5 7 8 +f 5 8 12 +f 5 12 11 +f 5 11 7 +f 6 9 13 +f 6 13 14 +f 6 14 10 +f 6 10 9 diff --git a/webapp/data/TriakisIcosahedron.obj b/webapp/data/TriakisIcosahedron.obj new file mode 100644 index 0000000..c7a6e7e --- /dev/null +++ b/webapp/data/TriakisIcosahedron.obj @@ -0,0 +1,92 @@ +v 1.8090169943749475 0.0 2.9270509831248424 +v 1.8090169943749475 0.0 -2.9270509831248424 +v -1.8090169943749475 0.0 2.9270509831248424 +v -1.8090169943749475 0.0 -2.9270509831248424 +v 2.9270509831248424 1.8090169943749475 0.0 +v 2.9270509831248424 -1.8090169943749475 0.0 +v -2.9270509831248424 1.8090169943749475 0.0 +v -2.9270509831248424 -1.8090169943749475 0.0 +v 0.0 2.9270509831248424 1.8090169943749475 +v 0.0 2.9270509831248424 -1.8090169943749475 +v 0.0 -2.9270509831248424 1.8090169943749475 +v 0.0 -2.9270509831248424 -1.8090169943749475 +v 0.0 1.0495531792613397 2.747765896306699 +v 0.0 1.0495531792613397 -2.747765896306699 +v 0.0 -1.0495531792613397 2.747765896306699 +v 0.0 -1.0495531792613397 -2.747765896306699 +v 2.747765896306699 0.0 1.0495531792613397 +v 2.747765896306699 0.0 -1.0495531792613397 +v -2.747765896306699 0.0 1.0495531792613397 +v -2.747765896306699 0.0 -1.0495531792613397 +v 1.0495531792613397 2.747765896306699 0.0 +v 1.0495531792613397 -2.747765896306699 0.0 +v -1.0495531792613397 2.747765896306699 0.0 +v -1.0495531792613397 -2.747765896306699 0.0 +v 1.698212717045359 1.698212717045359 1.698212717045359 +v 1.698212717045359 1.698212717045359 -1.698212717045359 +v 1.698212717045359 -1.698212717045359 1.698212717045359 +v 1.698212717045359 -1.698212717045359 -1.698212717045359 +v -1.698212717045359 1.698212717045359 1.698212717045359 +v -1.698212717045359 1.698212717045359 -1.698212717045359 +v -1.698212717045359 -1.698212717045359 1.698212717045359 +v -1.698212717045359 -1.698212717045359 -1.698212717045359 +f 13 1 9 +f 13 9 3 +f 13 3 1 +f 14 2 4 +f 14 4 10 +f 14 10 2 +f 15 1 3 +f 15 3 11 +f 15 11 1 +f 16 2 12 +f 16 12 4 +f 16 4 2 +f 17 1 6 +f 17 6 5 +f 17 5 1 +f 18 2 5 +f 18 5 6 +f 18 6 2 +f 19 3 7 +f 19 7 8 +f 19 8 3 +f 20 4 8 +f 20 8 7 +f 20 7 4 +f 21 5 10 +f 21 10 9 +f 21 9 5 +f 22 6 11 +f 22 11 12 +f 22 12 6 +f 23 7 9 +f 23 9 10 +f 23 10 7 +f 24 8 12 +f 24 12 11 +f 24 11 8 +f 25 1 5 +f 25 5 9 +f 25 9 1 +f 26 2 10 +f 26 10 5 +f 26 5 2 +f 27 1 11 +f 27 11 6 +f 27 6 1 +f 28 2 6 +f 28 6 12 +f 28 12 2 +f 29 3 9 +f 29 9 7 +f 29 7 3 +f 30 4 7 +f 30 7 10 +f 30 10 4 +f 31 3 8 +f 31 8 11 +f 31 11 3 +f 32 4 12 +f 32 12 8 +f 32 8 4 diff --git a/webapp/data/TriakisOctahedron.obj b/webapp/data/TriakisOctahedron.obj new file mode 100644 index 0000000..7e8073d --- /dev/null +++ b/webapp/data/TriakisOctahedron.obj @@ -0,0 +1,38 @@ +v 0.0 0.0 2.414213562373095 +v 0.0 0.0 -2.414213562373095 +v 2.414213562373095 0.0 0.0 +v -2.414213562373095 0.0 0.0 +v 0.0 2.414213562373095 0.0 +v 0.0 -2.414213562373095 0.0 +v 1.0 1.0 1.0 +v 1.0 1.0 -1.0 +v 1.0 -1.0 1.0 +v 1.0 -1.0 -1.0 +v -1.0 1.0 1.0 +v -1.0 1.0 -1.0 +v -1.0 -1.0 1.0 +v -1.0 -1.0 -1.0 +f 7 1 3 +f 7 3 5 +f 7 5 1 +f 8 2 5 +f 8 5 3 +f 8 3 2 +f 9 1 6 +f 9 6 3 +f 9 3 1 +f 10 2 3 +f 10 3 6 +f 10 6 2 +f 11 1 5 +f 11 5 4 +f 11 4 1 +f 12 2 4 +f 12 4 5 +f 12 5 2 +f 13 1 4 +f 13 4 6 +f 13 6 1 +f 14 2 6 +f 14 6 4 +f 14 4 2 diff --git a/webapp/data/TriakisTetrahedron.obj b/webapp/data/TriakisTetrahedron.obj new file mode 100644 index 0000000..d169f71 --- /dev/null +++ b/webapp/data/TriakisTetrahedron.obj @@ -0,0 +1,20 @@ +v 1.0606601717798212 1.0606601717798212 1.0606601717798212 +v 1.0606601717798212 -1.0606601717798212 -1.0606601717798212 +v -1.0606601717798212 -1.0606601717798212 1.0606601717798212 +v -1.0606601717798212 1.0606601717798212 -1.0606601717798212 +v 0.6363961030678927 -0.6363961030678927 0.6363961030678927 +v 0.6363961030678927 0.6363961030678927 -0.6363961030678927 +v -0.6363961030678927 0.6363961030678927 0.6363961030678927 +v -0.6363961030678927 -0.6363961030678927 -0.6363961030678927 +f 5 1 3 +f 5 3 2 +f 5 2 1 +f 6 1 2 +f 6 2 4 +f 6 4 1 +f 7 1 4 +f 7 4 3 +f 7 3 1 +f 8 2 3 +f 8 3 4 +f 8 4 2 diff --git a/webapp/data/TruncatedCube.obj b/webapp/data/TruncatedCube.obj new file mode 100644 index 0000000..e3a96a4 --- /dev/null +++ b/webapp/data/TruncatedCube.obj @@ -0,0 +1,38 @@ +v 1.2071067811865475 0.5 1.2071067811865475 +v 1.2071067811865475 0.5 -1.2071067811865475 +v 1.2071067811865475 -0.5 1.2071067811865475 +v 1.2071067811865475 -0.5 -1.2071067811865475 +v -1.2071067811865475 0.5 1.2071067811865475 +v -1.2071067811865475 0.5 -1.2071067811865475 +v -1.2071067811865475 -0.5 1.2071067811865475 +v -1.2071067811865475 -0.5 -1.2071067811865475 +v 1.2071067811865475 1.2071067811865475 0.5 +v 1.2071067811865475 1.2071067811865475 -0.5 +v 1.2071067811865475 -1.2071067811865475 0.5 +v 1.2071067811865475 -1.2071067811865475 -0.5 +v -1.2071067811865475 1.2071067811865475 0.5 +v -1.2071067811865475 1.2071067811865475 -0.5 +v -1.2071067811865475 -1.2071067811865475 0.5 +v -1.2071067811865475 -1.2071067811865475 -0.5 +v 0.5 1.2071067811865475 1.2071067811865475 +v 0.5 1.2071067811865475 -1.2071067811865475 +v 0.5 -1.2071067811865475 1.2071067811865475 +v 0.5 -1.2071067811865475 -1.2071067811865475 +v -0.5 1.2071067811865475 1.2071067811865475 +v -0.5 1.2071067811865475 -1.2071067811865475 +v -0.5 -1.2071067811865475 1.2071067811865475 +v -0.5 -1.2071067811865475 -1.2071067811865475 +f 1 3 11 12 4 2 10 9 +f 1 17 21 5 7 23 19 3 +f 13 14 6 8 16 15 7 5 +f 13 21 17 9 10 18 22 14 +f 20 24 8 6 22 18 2 4 +f 20 12 11 19 23 15 16 24 +f 1 9 17 +f 2 18 10 +f 3 19 11 +f 4 12 20 +f 5 21 13 +f 6 14 22 +f 7 15 23 +f 8 24 16 diff --git a/webapp/data/TruncatedCuboctahedron.obj b/webapp/data/TruncatedCuboctahedron.obj new file mode 100644 index 0000000..8076157 --- /dev/null +++ b/webapp/data/TruncatedCuboctahedron.obj @@ -0,0 +1,74 @@ +v 1.2071067811865475 0.5 1.9142135623730951 +v 1.2071067811865475 0.5 -1.9142135623730951 +v 1.2071067811865475 -0.5 1.9142135623730951 +v 1.2071067811865475 -0.5 -1.9142135623730951 +v -1.2071067811865475 0.5 1.9142135623730951 +v -1.2071067811865475 0.5 -1.9142135623730951 +v -1.2071067811865475 -0.5 1.9142135623730951 +v -1.2071067811865475 -0.5 -1.9142135623730951 +v 1.9142135623730951 1.2071067811865475 0.5 +v 1.9142135623730951 1.2071067811865475 -0.5 +v 1.9142135623730951 -1.2071067811865475 0.5 +v 1.9142135623730951 -1.2071067811865475 -0.5 +v -1.9142135623730951 1.2071067811865475 0.5 +v -1.9142135623730951 1.2071067811865475 -0.5 +v -1.9142135623730951 -1.2071067811865475 0.5 +v -1.9142135623730951 -1.2071067811865475 -0.5 +v 0.5 1.9142135623730951 1.2071067811865475 +v 0.5 1.9142135623730951 -1.2071067811865475 +v 0.5 -1.9142135623730951 1.2071067811865475 +v 0.5 -1.9142135623730951 -1.2071067811865475 +v -0.5 1.9142135623730951 1.2071067811865475 +v -0.5 1.9142135623730951 -1.2071067811865475 +v -0.5 -1.9142135623730951 1.2071067811865475 +v -0.5 -1.9142135623730951 -1.2071067811865475 +v 0.5 1.2071067811865475 1.9142135623730951 +v 0.5 1.2071067811865475 -1.9142135623730951 +v 0.5 -1.2071067811865475 1.9142135623730951 +v 0.5 -1.2071067811865475 -1.9142135623730951 +v -0.5 1.2071067811865475 1.9142135623730951 +v -0.5 1.2071067811865475 -1.9142135623730951 +v -0.5 -1.2071067811865475 1.9142135623730951 +v -0.5 -1.2071067811865475 -1.9142135623730951 +v 1.9142135623730951 0.5 1.2071067811865475 +v 1.9142135623730951 0.5 -1.2071067811865475 +v 1.9142135623730951 -0.5 1.2071067811865475 +v 1.9142135623730951 -0.5 -1.2071067811865475 +v -1.9142135623730951 0.5 1.2071067811865475 +v -1.9142135623730951 0.5 -1.2071067811865475 +v -1.9142135623730951 -0.5 1.2071067811865475 +v -1.9142135623730951 -0.5 -1.2071067811865475 +v 1.2071067811865475 1.9142135623730951 0.5 +v 1.2071067811865475 1.9142135623730951 -0.5 +v 1.2071067811865475 -1.9142135623730951 0.5 +v 1.2071067811865475 -1.9142135623730951 -0.5 +v -1.2071067811865475 1.9142135623730951 0.5 +v -1.2071067811865475 1.9142135623730951 -0.5 +v -1.2071067811865475 -1.9142135623730951 0.5 +v -1.2071067811865475 -1.9142135623730951 -0.5 +f 1 25 29 5 7 31 27 3 +f 2 4 28 32 8 6 30 26 +f 9 33 35 11 12 36 34 10 +f 13 14 38 40 16 15 39 37 +f 17 41 42 18 22 46 45 21 +f 19 23 47 48 24 20 44 43 +f 1 33 9 41 17 25 +f 2 26 18 42 10 34 +f 3 27 19 43 11 35 +f 4 36 12 44 20 28 +f 5 29 21 45 13 37 +f 6 38 14 46 22 30 +f 7 39 15 47 23 31 +f 8 32 24 48 16 40 +f 1 3 35 33 +f 2 34 36 4 +f 5 37 39 7 +f 6 8 40 38 +f 9 10 42 41 +f 11 43 44 12 +f 13 45 46 14 +f 15 16 48 47 +f 17 21 29 25 +f 18 26 30 22 +f 19 27 31 23 +f 20 24 32 28 diff --git a/webapp/data/TruncatedDodecahedron.obj b/webapp/data/TruncatedDodecahedron.obj new file mode 100644 index 0000000..fcef10b --- /dev/null +++ b/webapp/data/TruncatedDodecahedron.obj @@ -0,0 +1,92 @@ +v 0.0 0.5 2.9270509831248424 +v 0.0 0.5 -2.9270509831248424 +v 0.0 -0.5 2.9270509831248424 +v 0.0 -0.5 -2.9270509831248424 +v 2.9270509831248424 0.0 0.5 +v 2.9270509831248424 0.0 -0.5 +v -2.9270509831248424 0.0 0.5 +v -2.9270509831248424 0.0 -0.5 +v 0.5 2.9270509831248424 0.0 +v 0.5 -2.9270509831248424 0.0 +v -0.5 2.9270509831248424 0.0 +v -0.5 -2.9270509831248424 0.0 +v 0.5 1.3090169943749475 2.618033988749895 +v 0.5 1.3090169943749475 -2.618033988749895 +v 0.5 -1.3090169943749475 2.618033988749895 +v 0.5 -1.3090169943749475 -2.618033988749895 +v -0.5 1.3090169943749475 2.618033988749895 +v -0.5 1.3090169943749475 -2.618033988749895 +v -0.5 -1.3090169943749475 2.618033988749895 +v -0.5 -1.3090169943749475 -2.618033988749895 +v 2.618033988749895 0.5 1.3090169943749475 +v 2.618033988749895 0.5 -1.3090169943749475 +v 2.618033988749895 -0.5 1.3090169943749475 +v 2.618033988749895 -0.5 -1.3090169943749475 +v -2.618033988749895 0.5 1.3090169943749475 +v -2.618033988749895 0.5 -1.3090169943749475 +v -2.618033988749895 -0.5 1.3090169943749475 +v -2.618033988749895 -0.5 -1.3090169943749475 +v 1.3090169943749475 2.618033988749895 0.5 +v 1.3090169943749475 2.618033988749895 -0.5 +v 1.3090169943749475 -2.618033988749895 0.5 +v 1.3090169943749475 -2.618033988749895 -0.5 +v -1.3090169943749475 2.618033988749895 0.5 +v -1.3090169943749475 2.618033988749895 -0.5 +v -1.3090169943749475 -2.618033988749895 0.5 +v -1.3090169943749475 -2.618033988749895 -0.5 +v 1.3090169943749475 1.618033988749895 2.118033988749895 +v 1.3090169943749475 1.618033988749895 -2.118033988749895 +v 1.3090169943749475 -1.618033988749895 2.118033988749895 +v 1.3090169943749475 -1.618033988749895 -2.118033988749895 +v -1.3090169943749475 1.618033988749895 2.118033988749895 +v -1.3090169943749475 1.618033988749895 -2.118033988749895 +v -1.3090169943749475 -1.618033988749895 2.118033988749895 +v -1.3090169943749475 -1.618033988749895 -2.118033988749895 +v 2.118033988749895 1.3090169943749475 1.618033988749895 +v 2.118033988749895 1.3090169943749475 -1.618033988749895 +v 2.118033988749895 -1.3090169943749475 1.618033988749895 +v 2.118033988749895 -1.3090169943749475 -1.618033988749895 +v -2.118033988749895 1.3090169943749475 1.618033988749895 +v -2.118033988749895 1.3090169943749475 -1.618033988749895 +v -2.118033988749895 -1.3090169943749475 1.618033988749895 +v -2.118033988749895 -1.3090169943749475 -1.618033988749895 +v 1.618033988749895 2.118033988749895 1.3090169943749475 +v 1.618033988749895 2.118033988749895 -1.3090169943749475 +v 1.618033988749895 -2.118033988749895 1.3090169943749475 +v 1.618033988749895 -2.118033988749895 -1.3090169943749475 +v -1.618033988749895 2.118033988749895 1.3090169943749475 +v -1.618033988749895 2.118033988749895 -1.3090169943749475 +v -1.618033988749895 -2.118033988749895 1.3090169943749475 +v -1.618033988749895 -2.118033988749895 -1.3090169943749475 +f 1 3 15 39 47 23 21 45 37 13 +f 2 4 20 44 52 28 26 50 42 18 +f 3 1 17 41 49 25 27 51 43 19 +f 4 2 14 38 46 22 24 48 40 16 +f 5 6 22 46 54 30 29 53 45 21 +f 6 5 23 47 55 31 32 56 48 24 +f 7 8 28 52 60 36 35 59 51 27 +f 8 7 25 49 57 33 34 58 50 26 +f 9 11 33 57 41 17 13 37 53 29 +f 10 12 36 60 44 20 16 40 56 32 +f 11 9 30 54 38 14 18 42 58 34 +f 12 10 31 55 39 15 19 43 59 35 +f 1 13 17 +f 2 18 14 +f 3 19 15 +f 4 16 20 +f 5 21 23 +f 6 24 22 +f 7 27 25 +f 8 26 28 +f 9 29 30 +f 10 32 31 +f 11 34 33 +f 12 35 36 +f 37 45 53 +f 38 54 46 +f 39 55 47 +f 40 48 56 +f 41 57 49 +f 42 50 58 +f 43 51 59 +f 44 60 52 diff --git a/webapp/data/TruncatedIcosahedron.obj b/webapp/data/TruncatedIcosahedron.obj new file mode 100644 index 0000000..df0f59c --- /dev/null +++ b/webapp/data/TruncatedIcosahedron.obj @@ -0,0 +1,92 @@ +v 0.5 0.0 2.4270509831248424 +v 0.5 0.0 -2.4270509831248424 +v -0.5 0.0 2.4270509831248424 +v -0.5 0.0 -2.4270509831248424 +v 2.4270509831248424 0.5 0.0 +v 2.4270509831248424 -0.5 0.0 +v -2.4270509831248424 0.5 0.0 +v -2.4270509831248424 -0.5 0.0 +v 0.0 2.4270509831248424 0.5 +v 0.0 2.4270509831248424 -0.5 +v 0.0 -2.4270509831248424 0.5 +v 0.0 -2.4270509831248424 -0.5 +v 1.0 0.8090169943749475 2.118033988749895 +v 1.0 0.8090169943749475 -2.118033988749895 +v 1.0 -0.8090169943749475 2.118033988749895 +v 1.0 -0.8090169943749475 -2.118033988749895 +v -1.0 0.8090169943749475 2.118033988749895 +v -1.0 0.8090169943749475 -2.118033988749895 +v -1.0 -0.8090169943749475 2.118033988749895 +v -1.0 -0.8090169943749475 -2.118033988749895 +v 2.118033988749895 1.0 0.8090169943749475 +v 2.118033988749895 1.0 -0.8090169943749475 +v 2.118033988749895 -1.0 0.8090169943749475 +v 2.118033988749895 -1.0 -0.8090169943749475 +v -2.118033988749895 1.0 0.8090169943749475 +v -2.118033988749895 1.0 -0.8090169943749475 +v -2.118033988749895 -1.0 0.8090169943749475 +v -2.118033988749895 -1.0 -0.8090169943749475 +v 0.8090169943749475 2.118033988749895 1.0 +v 0.8090169943749475 2.118033988749895 -1.0 +v 0.8090169943749475 -2.118033988749895 1.0 +v 0.8090169943749475 -2.118033988749895 -1.0 +v -0.8090169943749475 2.118033988749895 1.0 +v -0.8090169943749475 2.118033988749895 -1.0 +v -0.8090169943749475 -2.118033988749895 1.0 +v -0.8090169943749475 -2.118033988749895 -1.0 +v 0.5 1.618033988749895 1.8090169943749475 +v 0.5 1.618033988749895 -1.8090169943749475 +v 0.5 -1.618033988749895 1.8090169943749475 +v 0.5 -1.618033988749895 -1.8090169943749475 +v -0.5 1.618033988749895 1.8090169943749475 +v -0.5 1.618033988749895 -1.8090169943749475 +v -0.5 -1.618033988749895 1.8090169943749475 +v -0.5 -1.618033988749895 -1.8090169943749475 +v 1.8090169943749475 0.5 1.618033988749895 +v 1.8090169943749475 0.5 -1.618033988749895 +v 1.8090169943749475 -0.5 1.618033988749895 +v 1.8090169943749475 -0.5 -1.618033988749895 +v -1.8090169943749475 0.5 1.618033988749895 +v -1.8090169943749475 0.5 -1.618033988749895 +v -1.8090169943749475 -0.5 1.618033988749895 +v -1.8090169943749475 -0.5 -1.618033988749895 +v 1.618033988749895 1.8090169943749475 0.5 +v 1.618033988749895 1.8090169943749475 -0.5 +v 1.618033988749895 -1.8090169943749475 0.5 +v 1.618033988749895 -1.8090169943749475 -0.5 +v -1.618033988749895 1.8090169943749475 0.5 +v -1.618033988749895 1.8090169943749475 -0.5 +v -1.618033988749895 -1.8090169943749475 0.5 +v -1.618033988749895 -1.8090169943749475 -0.5 +f 1 3 19 43 39 15 +f 2 4 18 42 38 14 +f 3 1 13 37 41 17 +f 4 2 16 40 44 20 +f 5 6 24 48 46 22 +f 6 5 21 45 47 23 +f 7 8 27 51 49 25 +f 8 7 26 50 52 28 +f 9 10 34 58 57 33 +f 10 9 29 53 54 30 +f 11 12 32 56 55 31 +f 12 11 35 59 60 36 +f 13 45 21 53 29 37 +f 14 38 30 54 22 46 +f 15 39 31 55 23 47 +f 16 48 24 56 32 40 +f 17 41 33 57 25 49 +f 18 50 26 58 34 42 +f 19 51 27 59 35 43 +f 20 44 36 60 28 52 +f 1 15 47 45 13 +f 2 14 46 48 16 +f 3 17 49 51 19 +f 4 20 52 50 18 +f 5 22 54 53 21 +f 6 23 55 56 24 +f 7 25 57 58 26 +f 8 28 60 59 27 +f 9 33 41 37 29 +f 10 30 38 42 34 +f 11 31 39 43 35 +f 12 36 44 40 32 diff --git a/webapp/data/TruncatedIcosidodecahedron.obj b/webapp/data/TruncatedIcosidodecahedron.obj new file mode 100644 index 0000000..a86675d --- /dev/null +++ b/webapp/data/TruncatedIcosidodecahedron.obj @@ -0,0 +1,182 @@ +v 0.5 0.5 3.73606797749979 +v 0.5 0.5 -3.73606797749979 +v 0.5 -0.5 3.73606797749979 +v 0.5 -0.5 -3.73606797749979 +v -0.5 0.5 3.73606797749979 +v -0.5 0.5 -3.73606797749979 +v -0.5 -0.5 3.73606797749979 +v -0.5 -0.5 -3.73606797749979 +v 3.73606797749979 0.5 0.5 +v 3.73606797749979 0.5 -0.5 +v 3.73606797749979 -0.5 0.5 +v 3.73606797749979 -0.5 -0.5 +v -3.73606797749979 0.5 0.5 +v -3.73606797749979 0.5 -0.5 +v -3.73606797749979 -0.5 0.5 +v -3.73606797749979 -0.5 -0.5 +v 0.5 3.73606797749979 0.5 +v 0.5 3.73606797749979 -0.5 +v 0.5 -3.73606797749979 0.5 +v 0.5 -3.73606797749979 -0.5 +v -0.5 3.73606797749979 0.5 +v -0.5 3.73606797749979 -0.5 +v -0.5 -3.73606797749979 0.5 +v -0.5 -3.73606797749979 -0.5 +v 1.0 1.3090169943749475 3.4270509831248424 +v 1.0 1.3090169943749475 -3.4270509831248424 +v 1.0 -1.3090169943749475 3.4270509831248424 +v 1.0 -1.3090169943749475 -3.4270509831248424 +v -1.0 1.3090169943749475 3.4270509831248424 +v -1.0 1.3090169943749475 -3.4270509831248424 +v -1.0 -1.3090169943749475 3.4270509831248424 +v -1.0 -1.3090169943749475 -3.4270509831248424 +v 3.4270509831248424 1.0 1.3090169943749475 +v 3.4270509831248424 1.0 -1.3090169943749475 +v 3.4270509831248424 -1.0 1.3090169943749475 +v 3.4270509831248424 -1.0 -1.3090169943749475 +v -3.4270509831248424 1.0 1.3090169943749475 +v -3.4270509831248424 1.0 -1.3090169943749475 +v -3.4270509831248424 -1.0 1.3090169943749475 +v -3.4270509831248424 -1.0 -1.3090169943749475 +v 1.3090169943749475 3.4270509831248424 1.0 +v 1.3090169943749475 3.4270509831248424 -1.0 +v 1.3090169943749475 -3.4270509831248424 1.0 +v 1.3090169943749475 -3.4270509831248424 -1.0 +v -1.3090169943749475 3.4270509831248424 1.0 +v -1.3090169943749475 3.4270509831248424 -1.0 +v -1.3090169943749475 -3.4270509831248424 1.0 +v -1.3090169943749475 -3.4270509831248424 -1.0 +v 0.5 2.118033988749895 3.118033988749895 +v 0.5 2.118033988749895 -3.118033988749895 +v 0.5 -2.118033988749895 3.118033988749895 +v 0.5 -2.118033988749895 -3.118033988749895 +v -0.5 2.118033988749895 3.118033988749895 +v -0.5 2.118033988749895 -3.118033988749895 +v -0.5 -2.118033988749895 3.118033988749895 +v -0.5 -2.118033988749895 -3.118033988749895 +v 3.118033988749895 0.5 2.118033988749895 +v 3.118033988749895 0.5 -2.118033988749895 +v 3.118033988749895 -0.5 2.118033988749895 +v 3.118033988749895 -0.5 -2.118033988749895 +v -3.118033988749895 0.5 2.118033988749895 +v -3.118033988749895 0.5 -2.118033988749895 +v -3.118033988749895 -0.5 2.118033988749895 +v -3.118033988749895 -0.5 -2.118033988749895 +v 2.118033988749895 3.118033988749895 0.5 +v 2.118033988749895 3.118033988749895 -0.5 +v 2.118033988749895 -3.118033988749895 0.5 +v 2.118033988749895 -3.118033988749895 -0.5 +v -2.118033988749895 3.118033988749895 0.5 +v -2.118033988749895 3.118033988749895 -0.5 +v -2.118033988749895 -3.118033988749895 0.5 +v -2.118033988749895 -3.118033988749895 -0.5 +v 1.8090169943749475 1.618033988749895 2.9270509831248424 +v 1.8090169943749475 1.618033988749895 -2.9270509831248424 +v 1.8090169943749475 -1.618033988749895 2.9270509831248424 +v 1.8090169943749475 -1.618033988749895 -2.9270509831248424 +v -1.8090169943749475 1.618033988749895 2.9270509831248424 +v -1.8090169943749475 1.618033988749895 -2.9270509831248424 +v -1.8090169943749475 -1.618033988749895 2.9270509831248424 +v -1.8090169943749475 -1.618033988749895 -2.9270509831248424 +v 2.9270509831248424 1.8090169943749475 1.618033988749895 +v 2.9270509831248424 1.8090169943749475 -1.618033988749895 +v 2.9270509831248424 -1.8090169943749475 1.618033988749895 +v 2.9270509831248424 -1.8090169943749475 -1.618033988749895 +v -2.9270509831248424 1.8090169943749475 1.618033988749895 +v -2.9270509831248424 1.8090169943749475 -1.618033988749895 +v -2.9270509831248424 -1.8090169943749475 1.618033988749895 +v -2.9270509831248424 -1.8090169943749475 -1.618033988749895 +v 1.618033988749895 2.9270509831248424 1.8090169943749475 +v 1.618033988749895 2.9270509831248424 -1.8090169943749475 +v 1.618033988749895 -2.9270509831248424 1.8090169943749475 +v 1.618033988749895 -2.9270509831248424 -1.8090169943749475 +v -1.618033988749895 2.9270509831248424 1.8090169943749475 +v -1.618033988749895 2.9270509831248424 -1.8090169943749475 +v -1.618033988749895 -2.9270509831248424 1.8090169943749475 +v -1.618033988749895 -2.9270509831248424 -1.8090169943749475 +v 1.3090169943749475 2.4270509831248424 2.618033988749895 +v 1.3090169943749475 2.4270509831248424 -2.618033988749895 +v 1.3090169943749475 -2.4270509831248424 2.618033988749895 +v 1.3090169943749475 -2.4270509831248424 -2.618033988749895 +v -1.3090169943749475 2.4270509831248424 2.618033988749895 +v -1.3090169943749475 2.4270509831248424 -2.618033988749895 +v -1.3090169943749475 -2.4270509831248424 2.618033988749895 +v -1.3090169943749475 -2.4270509831248424 -2.618033988749895 +v 2.618033988749895 1.3090169943749475 2.4270509831248424 +v 2.618033988749895 1.3090169943749475 -2.4270509831248424 +v 2.618033988749895 -1.3090169943749475 2.4270509831248424 +v 2.618033988749895 -1.3090169943749475 -2.4270509831248424 +v -2.618033988749895 1.3090169943749475 2.4270509831248424 +v -2.618033988749895 1.3090169943749475 -2.4270509831248424 +v -2.618033988749895 -1.3090169943749475 2.4270509831248424 +v -2.618033988749895 -1.3090169943749475 -2.4270509831248424 +v 2.4270509831248424 2.618033988749895 1.3090169943749475 +v 2.4270509831248424 2.618033988749895 -1.3090169943749475 +v 2.4270509831248424 -2.618033988749895 1.3090169943749475 +v 2.4270509831248424 -2.618033988749895 -1.3090169943749475 +v -2.4270509831248424 2.618033988749895 1.3090169943749475 +v -2.4270509831248424 2.618033988749895 -1.3090169943749475 +v -2.4270509831248424 -2.618033988749895 1.3090169943749475 +v -2.4270509831248424 -2.618033988749895 -1.3090169943749475 +f 1 3 27 75 107 59 57 105 73 25 +f 2 26 74 106 58 60 108 76 28 4 +f 5 29 77 109 61 63 111 79 31 7 +f 6 8 32 80 112 64 62 110 78 30 +f 9 10 34 82 114 66 65 113 81 33 +f 11 35 83 115 67 68 116 84 36 12 +f 13 37 85 117 69 70 118 86 38 14 +f 15 16 40 88 120 72 71 119 87 39 +f 17 21 45 93 101 53 49 97 89 41 +f 18 42 90 98 50 54 102 94 46 22 +f 19 43 91 99 51 55 103 95 47 23 +f 20 24 48 96 104 56 52 100 92 44 +f 1 25 49 53 29 5 +f 2 6 30 54 50 26 +f 3 7 31 55 51 27 +f 4 28 52 56 32 8 +f 9 33 57 59 35 11 +f 10 12 36 60 58 34 +f 13 15 39 63 61 37 +f 14 38 62 64 40 16 +f 17 41 65 66 42 18 +f 19 20 44 68 67 43 +f 21 22 46 70 69 45 +f 23 47 71 72 48 24 +f 73 105 81 113 89 97 +f 74 98 90 114 82 106 +f 75 99 91 115 83 107 +f 76 108 84 116 92 100 +f 77 101 93 117 85 109 +f 78 110 86 118 94 102 +f 79 111 87 119 95 103 +f 80 104 96 120 88 112 +f 1 5 7 3 +f 2 4 8 6 +f 9 11 12 10 +f 13 14 16 15 +f 17 18 22 21 +f 19 23 24 20 +f 25 73 97 49 +f 26 50 98 74 +f 27 51 99 75 +f 28 76 100 52 +f 29 53 101 77 +f 30 78 102 54 +f 31 79 103 55 +f 32 56 104 80 +f 33 81 105 57 +f 34 58 106 82 +f 35 59 107 83 +f 36 84 108 60 +f 37 61 109 85 +f 38 86 110 62 +f 39 87 111 63 +f 40 64 112 88 +f 41 89 113 65 +f 42 66 114 90 +f 43 67 115 91 +f 44 92 116 68 +f 45 69 117 93 +f 46 94 118 70 +f 47 95 119 71 +f 48 72 120 96 diff --git a/webapp/data/TruncatedOctahedron.obj b/webapp/data/TruncatedOctahedron.obj new file mode 100644 index 0000000..6e81783 --- /dev/null +++ b/webapp/data/TruncatedOctahedron.obj @@ -0,0 +1,38 @@ +v 0.7071067811865476 0.0 1.4142135623730951 +v 0.7071067811865476 0.0 -1.4142135623730951 +v -0.7071067811865476 0.0 1.4142135623730951 +v -0.7071067811865476 0.0 -1.4142135623730951 +v 1.4142135623730951 0.7071067811865476 0.0 +v 1.4142135623730951 -0.7071067811865476 0.0 +v -1.4142135623730951 0.7071067811865476 0.0 +v -1.4142135623730951 -0.7071067811865476 0.0 +v 0.0 1.4142135623730951 0.7071067811865476 +v 0.0 1.4142135623730951 -0.7071067811865476 +v 0.0 -1.4142135623730951 0.7071067811865476 +v 0.0 -1.4142135623730951 -0.7071067811865476 +v 0.0 0.7071067811865476 1.4142135623730951 +v 0.0 0.7071067811865476 -1.4142135623730951 +v 0.0 -0.7071067811865476 1.4142135623730951 +v 0.0 -0.7071067811865476 -1.4142135623730951 +v 1.4142135623730951 0.0 0.7071067811865476 +v 1.4142135623730951 0.0 -0.7071067811865476 +v -1.4142135623730951 0.0 0.7071067811865476 +v -1.4142135623730951 0.0 -0.7071067811865476 +v 0.7071067811865476 1.4142135623730951 0.0 +v 0.7071067811865476 -1.4142135623730951 0.0 +v -0.7071067811865476 1.4142135623730951 0.0 +v -0.7071067811865476 -1.4142135623730951 0.0 +f 1 15 11 22 6 17 +f 2 14 10 21 5 18 +f 3 13 9 23 7 19 +f 4 16 12 24 8 20 +f 5 21 9 13 1 17 +f 6 22 12 16 2 18 +f 8 24 11 15 3 19 +f 7 23 10 14 4 20 +f 1 13 3 15 +f 2 16 4 14 +f 5 17 6 18 +f 7 20 8 19 +f 9 21 10 23 +f 11 24 12 22 diff --git a/webapp/data/TruncatedTetrahedron.obj b/webapp/data/TruncatedTetrahedron.obj new file mode 100644 index 0000000..a6ee75b --- /dev/null +++ b/webapp/data/TruncatedTetrahedron.obj @@ -0,0 +1,20 @@ +v 0.3535533905932738 -0.3535533905932738 1.0606601717798212 +v 0.3535533905932738 0.3535533905932738 -1.0606601717798212 +v -0.3535533905932738 0.3535533905932738 1.0606601717798212 +v -0.3535533905932738 -0.3535533905932738 -1.0606601717798212 +v 1.0606601717798212 -0.3535533905932738 0.3535533905932738 +v 1.0606601717798212 0.3535533905932738 -0.3535533905932738 +v -1.0606601717798212 0.3535533905932738 0.3535533905932738 +v -1.0606601717798212 -0.3535533905932738 -0.3535533905932738 +v 0.3535533905932738 -1.0606601717798212 0.3535533905932738 +v 0.3535533905932738 1.0606601717798212 -0.3535533905932738 +v -0.3535533905932738 1.0606601717798212 0.3535533905932738 +v -0.3535533905932738 -1.0606601717798212 -0.3535533905932738 +f 1 5 6 10 11 3 +f 2 6 5 9 12 4 +f 3 7 8 12 9 1 +f 4 8 7 11 10 2 +f 1 9 5 +f 2 10 6 +f 3 11 7 +f 4 12 8 diff --git a/webapp/fonts/pixelifysans-latin.woff2 b/webapp/fonts/pixelifysans-latin.woff2 new file mode 100644 index 0000000..fe867e9 Binary files /dev/null and b/webapp/fonts/pixelifysans-latin.woff2 differ diff --git a/webapp/index.html b/webapp/index.html new file mode 100644 index 0000000..e6ecf52 --- /dev/null +++ b/webapp/index.html @@ -0,0 +1,86 @@ + + + + Vertexprint + + + + + + + +
+ + + + + +
+
+ + +
+ +
+
+
+
+

This pane will populate after you generate artifacts

+
+ +
+
+ + + + +
+ + diff --git a/webapp/openscad-wasm b/webapp/openscad-wasm new file mode 160000 index 0000000..54ae2e7 --- /dev/null +++ b/webapp/openscad-wasm @@ -0,0 +1 @@ +Subproject commit 54ae2e7333d426951950aa6e238da4cc90304807 diff --git a/webapp/package-lock.json b/webapp/package-lock.json new file mode 100644 index 0000000..586d678 --- /dev/null +++ b/webapp/package-lock.json @@ -0,0 +1,2148 @@ +{ + "name": "webapp", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "@tailwindcss/cli": "^4.3.1", + "esbuild": "^0.28.1", + "jszip": "^3.10.1", + "ml-matrix": "^6.13.0", + "tailwindcss": "^4.3.1", + "three": "^0.184.0", + "typescript": "^6.0.3" + }, + "devDependencies": { + "@iconify/json": "^2.2.490", + "@iconify/tailwind4": "^1.2.3", + "@types/three": "^0.185.0" + } + }, + "node_modules/@antfu/install-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", + "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "package-manager-detector": "^1.3.0", + "tinyexec": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@cyberalien/svg-utils": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@cyberalien/svg-utils/-/svg-utils-1.2.15.tgz", + "integrity": "sha512-ZbKU6npzW5PNocdoLVJYfKzaP+c/RpT6JUkoaKrW1DOcw6lyXub8XtcNpI3xok6FnyNjS6ZbsrrtjTnS9yeZAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@iconify/types": "^2.0.0" + } + }, + "node_modules/@dimforge/rapier3d-compat": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz", + "integrity": "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@iconify/json": { + "version": "2.2.490", + "resolved": "https://registry.npmjs.org/@iconify/json/-/json-2.2.490.tgz", + "integrity": "sha512-4hhzvLDCJ+oqkr6Awf5rZvgrAnELX/Jt9yWaQBYpWmh7jCGR76inYOrcMFXZrsTfhsIlJ+d/ziG9Ss/XBRNgXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@iconify/types": "*", + "pathe": "^2.0.3" + } + }, + "node_modules/@iconify/tailwind4": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@iconify/tailwind4/-/tailwind4-1.2.3.tgz", + "integrity": "sha512-z8SKiMHRASJKF/IY//87MF88lcB7ulxh8vlhQXXLWsBkNtOh6ese9R41MyGpQeqXdRvQVt+/fX2glQtHFjQ+MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@iconify/tools": "^5.0.5", + "@iconify/types": "^2.0.0", + "@iconify/utils": "^3.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/cyberalien" + }, + "peerDependencies": { + "tailwindcss": ">= 4.0.0" + } + }, + "node_modules/@iconify/tools": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/@iconify/tools/-/tools-5.0.12.tgz", + "integrity": "sha512-aFPwSFmFphUPVjNLUkgxgUPSPVgTEEjv0mE7NgvfoQBuE5TtsMrKa4HTY65cM5oXZ2a1xKQcW/RKhiOKEiAarw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cyberalien/svg-utils": "^1.2.15", + "@iconify/types": "^2.0.0", + "@iconify/utils": "^3.1.3", + "fflate": "^0.8.3", + "modern-tar": "^0.7.6", + "pathe": "^2.0.3", + "svgo": "^4.0.1" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@iconify/utils": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.3.tgz", + "integrity": "sha512-LPKOXPn/zV+zis1oOfGWogaXVpqUybF3ZS6SCZIsz8vg0ivVp9+fVqyYB7xq0aiST/VhUQYGO1qo6uoYSiEJqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^1.1.0", + "@iconify/types": "^2.0.0", + "import-meta-resolve": "^4.2.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/cli": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/cli/-/cli-4.3.1.tgz", + "integrity": "sha512-ZWPy20rF+TBfTImxDMG3Wr75Y3RpaPlo9lc+oJbInlMyjT+XPkTVKVIL5RZ7JirXuIahcfHoLNFRmDorKi+JQQ==", + "license": "MIT", + "dependencies": { + "@parcel/watcher": "2.5.1", + "@tailwindcss/node": "4.3.1", + "@tailwindcss/oxide": "4.3.1", + "enhanced-resolve": "5.21.6", + "mri": "^1.2.0", + "picocolors": "^1.1.1", + "tailwindcss": "4.3.1" + }, + "bin": { + "tailwindcss": "dist/index.mjs" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.1.tgz", + "integrity": "sha512-6NDaqRoAMSXD1mr/RXu0HBvNE9a2n5tHPsxu9XHLws8o4Twes5rBM2205SUUiJ9goAtadrN6xTGX0UDEwp/N4A==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "5.21.6", + "jiti": "^2.7.0", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.1" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.1.tgz", + "integrity": "sha512-yVPyo8RNkabVr3O2EhHEE0Rewu7YKzc1DhIqfL46LKveFrmu9XbDazNOJY7/GRuvw1h6u3utWnR29H/p5JPlgA==", + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.1", + "@tailwindcss/oxide-darwin-arm64": "4.3.1", + "@tailwindcss/oxide-darwin-x64": "4.3.1", + "@tailwindcss/oxide-freebsd-x64": "4.3.1", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.1", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.1", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.1", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.1", + "@tailwindcss/oxide-linux-x64-musl": "4.3.1", + "@tailwindcss/oxide-wasm32-wasi": "4.3.1", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.1", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.1" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.1.tgz", + "integrity": "sha512-SVlyf61g374l5cHyg8x9kf5xmLcOaxvOTsbsqDnSsDJaKOEFZ7GCvi84VAVGpxojYOs1+3K6M0UjXfqPU8vmOQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.1.tgz", + "integrity": "sha512-hVnWLwv+e/l7c4WKyVtHVrIPvYdqWHjRB3MDIqARynzFtnQg85kmQEFCbV9Ja0VVx4xXTIiDWY60Y7iz/iNoDA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.1.tgz", + "integrity": "sha512-Cf7abu0WVgbhU7ANgPUnSAvm7nCvMweusHb8FnaHlLfv/Caq4GYaEZg7ZImzzmjx4lIAfuS8q+eLIS7A7IzxIg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.1.tgz", + "integrity": "sha512-ZZqzX2Y+GXtXXfqSfpJhDm60OoZfvLHLCgm+J7NVqgHHJjG/m9ugZI77RwTsVd4fnBJuCFP6Ae6kTJb71UdS8g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.1.tgz", + "integrity": "sha512-/Ah/xik0LaMYfv9DZ0S/t4pBlBNYOcqtRwusjgovHkvT8ixueWCLyJjsaF5kQIckjb4IT8Q6K6p/iPmZMixYgg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.1.tgz", + "integrity": "sha512-gqdFoVJlw444GvpnheZLHmvTzSxI/cOUUh2KSNejQjTcYkW062SVD+En0rUgD+QV91bz1XGIGtt1HJd48xUGbQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.1.tgz", + "integrity": "sha512-Bwv9KwOvE0VKa86xPFif9b9c3Y1NxOV1P0gLti/IYaWEsQYZXDlxfGEtA8mdDZ7SG3wyNXAWYT5SIn3giL57oA==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.1.tgz", + "integrity": "sha512-Ymi8O8T15HYQdOUWUtTI6ldN0neHP85FC+Qz32xTcZ7iJXtem/x8ITev0o1e9e5rkqj4lONZfTRLvkmin1+tKg==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.1.tgz", + "integrity": "sha512-M+P/91qJ6uILLw4k2G93GMDRAXj61SMvFQYt39AqvUqYgExXpLL5aepfns7sj4HiAQeolirQF9E0lzRvdf4zPQ==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.1.tgz", + "integrity": "sha512-zsM8uOeqvVGHsAXsJxsT28ttosFahLJKCLOTUBqRAtKnVgGSRitds9T432QiT8b77Yga7JIBkulIRRlJPtYhRA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.10.0", + "@emnapi/runtime": "^1.10.0", + "@emnapi/wasi-threads": "^1.2.1", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.1.tgz", + "integrity": "sha512-aiNvSq9BsVk8V513lDKlrCFAgf8qBMPZTpgEhInL+NwQqs97mYmupVMrPrgBBSL8Pv/0zXu9MrMF9rMun1ZeNg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.1.tgz", + "integrity": "sha512-xDEyu1rg290472FEGaKHnzyDyh5QH+AlWvsU5hMoMtPpzmKlRI0jaYKCgSHDYtaQWZOYbMaduSyCwFwY4n1HmA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tweenjs/tween.js": { + "version": "23.1.3", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz", + "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/stats.js": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz", + "integrity": "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/three": { + "version": "0.185.0", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.185.0.tgz", + "integrity": "sha512-O2Uy8Cj4Nonr8dWUUbifMdPe8B0Mq7EdOHb89S4+kjUw/KhbjTZrUuYlrQ1bpUKG+EP9QJnN7qNxbHGlGoLHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@dimforge/rapier3d-compat": "~0.12.0", + "@tweenjs/tween.js": "~23.1.3", + "@types/stats.js": "*", + "@types/webxr": ">=0.5.17", + "fflate": "~0.8.2", + "meshoptimizer": "~1.1.1" + } + }, + "node_modules/@types/webxr": { + "version": "0.5.24", + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz", + "integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==", + "dev": true, + "license": "MIT" + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "license": "Apache-2.0", + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.21.6", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.6.tgz", + "integrity": "sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "node_modules/fflate": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz", + "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==", + "dev": true, + "license": "MIT" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/is-any-array": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-any-array/-/is-any-array-3.0.0.tgz", + "integrity": "sha512-o4h+tylWykC4BD1vaejp6gDxoM13bwW8FGuNs4yIKpj8xbBJcRxJx8vZpq0dCr7ZDEfeKjmsi/euolKhX6f/ww==", + "license": "MIT" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss/node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/meshoptimizer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-1.1.1.tgz", + "integrity": "sha512-oRFNWJRDA/WTrVj7NWvqa5HqE1t9MYDj2VaWirQCzCCrAd2GHrqR/sQezCxiWATPNlKTcRaPRHPJwIRoPBAp5g==", + "dev": true, + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/ml-array-max": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ml-array-max/-/ml-array-max-2.0.0.tgz", + "integrity": "sha512-QQZ4kENwpWmyNb98UXRDFXrmtIXuXtt1+bSbda/2KA85+F+rrJP8hZk6QOkCQXM2Th9mUDYdq/PNByPdT9ID4A==", + "license": "MIT", + "dependencies": { + "is-any-array": "^3.0.0" + } + }, + "node_modules/ml-array-min": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ml-array-min/-/ml-array-min-2.0.0.tgz", + "integrity": "sha512-GRj6Ky6sW9vGL6yIjgsHmXZ9YgrdmcQ8nCxPqEGeKc6dkfYg1XDYxGFxADUjNuZyoCd5PUscWAS4N+cFaX6hFg==", + "license": "MIT", + "dependencies": { + "is-any-array": "^3.0.0" + } + }, + "node_modules/ml-array-rescale": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ml-array-rescale/-/ml-array-rescale-2.0.0.tgz", + "integrity": "sha512-2GGtKfSno94/kIloWGvpp/U5Q5vLvLrza+SAaGsLeo6Xj4mEbA6Gqx+oTfZFkxnd1grT2X007HfJNs3T5BsiVg==", + "license": "MIT", + "dependencies": { + "is-any-array": "^3.0.0", + "ml-array-max": "^2.0.0", + "ml-array-min": "^2.0.0" + } + }, + "node_modules/ml-matrix": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/ml-matrix/-/ml-matrix-6.13.0.tgz", + "integrity": "sha512-QpV0UTUkglg6vPUgThKGBEtit2ac6habSoZ33bwI9rU0UHZLqw6G3ukTIE8zWiUF3sjK8YAlhx/o/b9layzH8A==", + "license": "MIT", + "dependencies": { + "is-any-array": "^3.0.0", + "ml-array-rescale": "^2.0.0" + } + }, + "node_modules/modern-tar": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/modern-tar/-/modern-tar-0.7.6.tgz", + "integrity": "sha512-sweCIVXzx1aIGTCdzcMlSZt1h8k5Tmk08VNAuRk3IU28XamGiOH5ypi11g6De2CH7PhYqSSnGy2A/EFhbWnVKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT" + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/package-manager-detector": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", + "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", + "dev": true, + "license": "MIT" + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/svgo": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.1.tgz", + "integrity": "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^11.1.0", + "css-select": "^5.1.0", + "css-tree": "^3.0.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.1.1", + "sax": "^1.5.0" + }, + "bin": { + "svgo": "bin/svgo.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/tailwindcss": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.1.tgz", + "integrity": "sha512-hk+TB1m+K8CYNrP6rjQaq/Y+4Zylwpa87mLYBKCunwnnQ9p+fHb7kmSfGqyEJoxF/O6CDyABWVFEafNSYKll+Q==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/three": { + "version": "0.184.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.184.0.tgz", + "integrity": "sha512-wtTRjG92pM5eUg/KuUnHsqSAlPM296brTOcLgMRqEeylYTh/CdtvKUvCyyCQTzFuStieWxvZb8mVTMvdPyUpxg==", + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + } + } +} diff --git a/webapp/package.json b/webapp/package.json new file mode 100644 index 0000000..9ad91e1 --- /dev/null +++ b/webapp/package.json @@ -0,0 +1,20 @@ +{ + "dependencies": { + "@tailwindcss/cli": "^4.3.1", + "esbuild": "^0.28.1", + "jszip": "^3.10.1", + "ml-matrix": "^6.13.0", + "tailwindcss": "^4.3.1", + "three": "^0.184.0", + "typescript": "^6.0.3" + }, + "scripts": { + "build:openscad-wasm": "make -C openscad-wasm", + "build": "esbuild app.ts --bundle --outfile=a.js --format=esm --target=es2022 --minify --tree-shaking=true --loader:.stl=file && esbuild worker.ts --bundle --outfile=worker.js --format=esm --target=es2022 --minify --tree-shaking=true && tailwindcss -i style.css -o a.css --minify && cp openscad-wasm/build/openscad.wasm.js openscad-wasm/build/openscad.wasm ." + }, + "devDependencies": { + "@iconify/json": "^2.2.490", + "@iconify/tailwind4": "^1.2.3", + "@types/three": "^0.185.0" + } +} diff --git a/webapp/style.css b/webapp/style.css new file mode 100644 index 0000000..1fb0f6c --- /dev/null +++ b/webapp/style.css @@ -0,0 +1,93 @@ +@import "tailwindcss"; + +/* Icon tailwind plugin */ +@plugin "@iconify/tailwind4"; + +/* Pixelify Sans, vendored from Google Fonts (no runtime fetch). */ +@font-face { + font-family: "Pixelify Sans"; + font-style: normal; + font-weight: 400 700; + font-display: swap; + src: url("fonts/pixelifysans-latin.woff2") format("woff2"); +} + +@theme { + --color-v-fg: #B1B1B3; + --color-v-bg: #232327; + --color-v-dark: #0C0C0D; + --color-v-panel: #18181A; + --color-v-border: #38383D; + --color-v-blue: #75BAFF; + --color-v-green: #86DE74; + --font-pixel: "Pixelify Sans", monospace; +} + +/* Tailwind has no utilities for sliders */ +.dh-slider { + --fill: 0%; + background: linear-gradient(to right, + var(--color-v-blue) 0%, var(--color-v-blue) var(--fill), + var(--color-v-border) var(--fill), var(--color-v-border) 100%); + background-size: 100% 4px; + background-position: center; + background-repeat: no-repeat; +} +.dh-slider::-webkit-slider-runnable-track { + height: 4px; + background: transparent; + border-radius: 0px; +} +.dh-slider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 12px; + height: 12px; + margin-top: -4px; + background: var(--color-v-blue); + border: 0px; + border-radius: 0px; +} +.dh-slider::-moz-range-track { + height: 4px; + background: transparent; + border-radius: 0px; +} +.dh-slider::-moz-range-progress { + height: 4px; + background: var(--color-v-blue); + border-radius: 0px; +} +.dh-slider::-moz-range-thumb { + width: 12px; + height: 12px; + background: var(--color-v-blue); + border: 0px; + border-radius: 0px; +} + +input[type="number"] { color-scheme: dark; } +input[type="number"] { -moz-appearance: textfield; } + +/* Highlight while resizing. The cursor can slip off the 2px wide handle, so + * :hover fails */ +#sidebar-resizer.dragging { background-color: var(--color-v-blue); } + +/* Text on hover is underlined */ +.opt-label:hover { text-decoration: underline; } + +.dh-tooltip { + position: fixed; + z-index: 100; + max-width: 256px; + padding: 8px 8px; + background: var(--color-v-dark); + color: var(--color-v-fg); + border: 1px solid var(--color-v-border); + font-family: monospace; + font-size: 11px; + line-height: 1.4; + pointer-events: none; +} +.dh-tooltip p { margin: 0; } +.dh-tooltip p + p { margin-top: 4px; } diff --git a/webapp/tsconfig.json b/webapp/tsconfig.json new file mode 100644 index 0000000..2a6cb0f --- /dev/null +++ b/webapp/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "noImplicitAny": true, + "removeComments": true, + "preserveConstEnums": true, + // "sourceMap": true, + "skipLibCheck": true, + "strict": false, + "outDir": ".", + "allowImportingTsExtensions": false + }, + "include": ["**/*.ts"], + "exclude": ["**/*.spec.ts", "**/*.js", "node_modules", "openscad", "three"] +} diff --git a/webapp/vertexprint.scad b/webapp/vertexprint.scad new file mode 100644 index 0000000..e73132a --- /dev/null +++ b/webapp/vertexprint.scad @@ -0,0 +1,237 @@ +// The js script sets all of these global variables with the -D flag +// Values here are defaults. +EDGE_DIAMETER = 3.0; +DIAMETER_TOLERANCE_FIT = 0.35; +DIAMETER_TAPER_DECREASE = 0.10; +WALL_THICKNESS = 1.2; +RADIUS = 200; +ROD_INSET = 8; +MIN_PRINTER_OVERHANG_ANGLE = 30; + +LABEL_VERTICES=false; +TUBULAR_SUPPORTS=true; + +TUBE_DEPTH = ROD_INSET+WALL_THICKNESS; +OUTER_TUBE_RADIUS = EDGE_DIAMETER/2+WALL_THICKNESS; + +INDEX = 0; + +FA=12; +FS=1; + +// Openscad hangs if you don't set these lists with a flag +vertex_figure = []; +vertex_figure_edge = []; +tag = ""; +index = 0.0; +offsets = []; + + +// Locus of lowest points along a cylinder. v is axis, r is radius, l is parameter +function lowest_line_on_cylinder(v, l, r) = + let( + uv = v / norm(v), + center = uv * l, + down = [0, 0, -1], + proj = down - (down * uv) * uv + ) + (norm(proj) < 1e-9) + ? center + [r, 0, 0] + : center + r * (proj / norm(proj)); + +// Convert a unit vector to euler angles +function direction_to_euler(v) = + [ + 0, + atan2(norm([v[0], v[1]]), v[2]), + atan2(v[1], v[0]) + ]; + +// Calculates the smallest translation length l such that the outer cylinders (radius R) +// just touch the inner cylinders (radius r). +function axis_offset(v0, v1, R, r) = + let( + c = v0 * v1, + s = norm(cross(v0, v1)), + + // Mode 1: Rim of outer contacts side of inner (active for acute angles) + l_side = (R * c + r) / s, + + // Mode 2: Rim of inner touches base of outer (active for obtuse angles) + // Uses identity cot(theta/2) = (1 + cos theta) / sin theta + l_base = (r * (1 + c)) / s + ) + // Handle parallel case to avoid division by zero + s < 1e-9 ? (c > 0 ? 1e9 : 0) : + max(l_side, l_base); + + +// Find the vector most aligned with -z (lowest vector) +function lowest_vector(vecs) = + let( + z_components = [for(v=vecs) v[2]], + min_z = min(z_components), + min_ix = search(min_z, z_components)[0] + ) + min_ix; + +// Return the vector with minimum cosine distance to t; assume that t = vecs[0] +function min_cos_dist(index, vecs) = + let ( + scores = [for(i=[1:len(vecs)-1]) + i == index ? + -1000 : + (vecs[index] * vecs[i]) / norm(vecs[i])], + ix = search(max(scores), scores)[0] + ) + vecs[ix+1]; + +// Height to translate the vertex holder before making the xy cut +function offset_from_single_vec(index, vecs) = + let ( closest = min_cos_dist(index, vecs) ) + axis_offset(vecs[index], closest, OUTER_TUBE_RADIUS, EDGE_DIAMETER/2); + +// Concat two lists of vectors, but keep only their z-components +function concat_z(l1, l2) = [for(l=[l1, l2], a=l) a.z]; + +function cutoff_height(vecs, offsets) = + let ( + lowest_bottom_points = [for (i=[0:len(vecs)-1]) + lowest_line_on_cylinder(vecs[i], offsets[i], OUTER_TUBE_RADIUS)], + lowest_top_points = [for (i=[0:len(vecs)-1]) + lowest_line_on_cylinder(vecs[i], offsets[i] + TUBE_DEPTH, OUTER_TUBE_RADIUS)], + z_components = concat_z(lowest_bottom_points, lowest_top_points), + lowest_point = min(z_components) + ) + lowest_point; + +module tubular_vertex_holder(vecs, offsets=[], edge_list=[], index) { + offsets = len(offsets) == 0 ? + [for (i=[0:len(vecs)-1]) offset_from_single_vec(i, vecs)] : + offsets; + + vertex_offset = max(offsets); + + cutoff = cutoff_height(vecs, offsets); + + difference() { + for(i=[0:len(vecs)-1]) { + v = vecs[i]; + rotation = direction_to_euler(v); + half_edge_offset = offsets[i]; + + // Add support structure if v sits below the minimum overhang angle + if (rotation[1] > 90 - MIN_PRINTER_OVERHANG_ANGLE && TUBULAR_SUPPORTS) { + lowest_top_point = lowest_line_on_cylinder( + v, + half_edge_offset+TUBE_DEPTH, + OUTER_TUBE_RADIUS); + + base_inset = + abs(lowest_top_point.z - cutoff) + / tan(MIN_PRINTER_OVERHANG_ANGLE); + + clamped_base_position = min( + max(half_edge_offset + TUBE_DEPTH - base_inset, 0), + norm([lowest_top_point.x, lowest_top_point.y])); + + tube_top_to_cutoff_plane = + -OUTER_TUBE_RADIUS + -lowest_top_point.z + +cutoff + +(half_edge_offset+TUBE_DEPTH)*v.z; + + hull() { + // Move endpoint to base position along cutoff plane; + translate(clamped_base_position * [v.x, v.y, 0]) + // Move endpoint downwards to cutoff plane + translate([0, 0, tube_top_to_cutoff_plane]) + rotate(rotation) + cube([0.1, OUTER_TUBE_RADIUS, 0.1], center=true); + + translate([0, 0, tube_top_to_cutoff_plane]) + rotate(rotation) + cube([0.1, OUTER_TUBE_RADIUS, 0.1], center=true); + + + translate(half_edge_offset * v) + rotate(rotation) + difference() { + union() { + cylinder(r=OUTER_TUBE_RADIUS, h=TUBE_DEPTH); + translate([0, 0, -half_edge_offset]) + cylinder(r=OUTER_TUBE_RADIUS, h=WALL_THICKNESS+half_edge_offset); + } + translate([-50+(EDGE_DIAMETER+DIAMETER_TOLERANCE_FIT)/2, 0, 0]) + cube([100, 100, 100], center=true); + } + } + } + + // Tubes + translate(half_edge_offset * v) + rotate(rotation) + difference() { + union() { + cylinder(r=OUTER_TUBE_RADIUS, h=TUBE_DEPTH); + translate([0, 0, -half_edge_offset]) + cylinder(r=OUTER_TUBE_RADIUS, h=WALL_THICKNESS+half_edge_offset); + } + // Add text to tube holders + if (LABEL_VERTICES) { + rotate([0, 0, 90]) + intersection() { + translate([0, 0, TUBE_DEPTH-WALL_THICKNESS]) + rotate([0, 90, 0]) + linear_extrude(20) + text(str(edge_list[i]), valign="center", size = OUTER_TUBE_RADIUS); + difference() { + cylinder(r=OUTER_TUBE_RADIUS, h=RADIUS, center=true); + cylinder(r=OUTER_TUBE_RADIUS-0.5, h=RADIUS, center=true); + }; + }; + rotate([0, 0, 270]) + intersection() { + translate([0, 0, TUBE_DEPTH-WALL_THICKNESS]) + rotate([0, 90, 0]) + linear_extrude(20) + text(str(index), valign="center", size = OUTER_TUBE_RADIUS); + difference() { + cylinder(r=OUTER_TUBE_RADIUS, h=RADIUS, center=true); + cylinder(r=OUTER_TUBE_RADIUS-0.5, h=RADIUS, center=true); + }; + }; + } + } + } + + for(i=[0:len(vecs)-1]) { + v = vecs[i]; + rotation = direction_to_euler(v); + half_edge_offset = offsets[i]; + translate(half_edge_offset * v) + rotate(rotation) + // A tiny offset is added to the length of the internal cylinder + // to prevent z-fighting on the top surface + union() { + cylinder( + d1=EDGE_DIAMETER+DIAMETER_TOLERANCE_FIT-DIAMETER_TAPER_DECREASE, + d2=EDGE_DIAMETER+DIAMETER_TOLERANCE_FIT, + h=TUBE_DEPTH); + cylinder( + d=EDGE_DIAMETER+DIAMETER_TOLERANCE_FIT, + h=RADIUS); + } + } + + // Flat bottom plane + translate([0, 0, -50+cutoff]) + cube([100, 100, 100], center=true); + } +} + +module vertex_holder(index) { + tubular_vertex_holder(vertex_figure, offsets, vertex_figure_edge, index); +} + +vertex_holder($fa=FA, $fs=FS); diff --git a/webapp/worker.ts b/webapp/worker.ts new file mode 100644 index 0000000..281ca8d --- /dev/null +++ b/webapp/worker.ts @@ -0,0 +1,40 @@ +import OpenSCAD from "./openscad-wasm/build/openscad.wasm.js"; + +let instancePromise: Promise | null = null; + +self.onmessage = async (e: MessageEvent) => { + const msg = e.data; + try { + if (msg.type === "render") { + if (!instancePromise) { + instancePromise = OpenSCAD({ + noInitialRun: true, + noExitRuntime: true, + locateFile: (path: string) => + new URL(`./${path}`, import.meta.url).href, + }); + } + const inst = await instancePromise; + inst.FS.writeFile("./input.scad", msg.source); + const filename = `v_${msg.name}_${msg.index}.stl`; + inst.callMain([ + "./input.scad", + ...msg.cliArgs, + "-o", + filename, + ]); + const out = inst.FS.readFile(`./${filename}`, { encoding: "binary" }); + const buffer = out.slice().buffer; + (self as unknown as Worker).postMessage( + { type: "rendered", index: msg.index, buffer }, + [buffer], + ); + } + } catch (err: any) { + (self as unknown as Worker).postMessage({ + type: "error", + index: msg.index, + message: String(err?.message ?? err), + }); + } +};