@harpern/edu-modelhub-viewer-core 0.1.0-beta.0 → 0.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,21 @@ here.
6
6
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
7
7
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
8
8
 
9
+ ## [0.1.0-beta.1] - 2026-09-13
10
+
11
+ ### Fixed
12
+
13
+ - The published package could not be installed. It declared a runtime
14
+ dependency on `@osuecampus/edu-modelhub-extractor`, which is not on the
15
+ registry yet, so `0.1.0-beta.0` failed to resolve for every consumer. The
16
+ extractor is now compiled into the bundle and is a build input rather than
17
+ a runtime dependency, and the payload types are declared here instead of
18
+ imported, so nothing in the published files points at a package the
19
+ consumer does not have.
20
+ - `GlbParseError` is no longer re-exported. Use `isGlbParseError(error)`,
21
+ which checks the error name: with the parser bundled in, a class identity
22
+ check against a separate copy would silently fail.
23
+
9
24
  ## [Unreleased]
10
25
 
11
26
  ### Added
package/README.md CHANGED
@@ -42,6 +42,9 @@ package manager tells you when to switch.
42
42
  `three` is a peer dependency. `three-mesh-bvh` is an optional peer: install
43
43
  it to get marker occlusion, leave it out and markers simply never dim.
44
44
 
45
+ Nothing else is required. The EDU_modelhub parser and validator are compiled
46
+ into this package, so it installs and runs on its own.
47
+
45
48
  ## What it does
46
49
 
47
50
  **Metadata, with the precedence the spec requires.** One fetch, embedded
@@ -6,7 +6,7 @@
6
6
  * are throttled: a held arrow key would otherwise flood the live region and
7
7
  * drown out everything else.
8
8
  */
9
- import type { EduModelHub } from "@osuecampus/edu-modelhub-extractor";
9
+ import type { EduModelHub } from "./types.js";
10
10
  /** Default gap between camera announcements, in milliseconds. */
11
11
  export declare const CAMERA_ANNOUNCE_INTERVAL = 500;
12
12
  /** A one-line summary spoken when a model finishes loading. */
@@ -12,7 +12,7 @@
12
12
  * undeclared payload is now read as Y-up and only an explicit marker is
13
13
  * converted.
14
14
  */
15
- import type { EduModelHub } from "@osuecampus/edu-modelhub-extractor";
15
+ import type { EduModelHub } from "./types.js";
16
16
  /** A position as stored in a payload, or as handed to a renderer. */
17
17
  export type Vec3 = [number, number, number];
18
18
  /** True when `value` is three finite numbers. */
package/dist/index.cjs CHANGED
@@ -1,21 +1,21 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const h=require("@osuecampus/edu-modelhub-extractor"),m=require("three");function R(e){return e.filter(t=>t.severity==="error")}function v(e){const[t,i=""]=e.split("?",2);if(!/\.glb$/i.test(t))return null;const n=t.replace(/\.glb$/i,".modelhub.json");return i?`${n}?${i}`:n}function K(e,t){var o;if(!e||e.startsWith("blob:")||e.startsWith("data:"))return"";const i=t??((o=globalThis.location)==null?void 0:o.href),n=i?new URL(e,i).href:e,r=n.lastIndexOf("/");return r===-1?"":n.slice(0,r+1)}async function $(e,t=fetch){const i=await t(e);if(!i.ok)throw new Error(`Failed to fetch ${e} (HTTP ${i.status}).`);return i.arrayBuffer()}function _(e){const t=h.parseGlb(e);if(!t)return{payload:null,source:"none",findings:[],problem:"This model carries no EDU_modelhub metadata."};const i=h.validate(t);return{payload:t,source:"embedded",findings:i,problem:null}}async function q(e,t={}){const i=t.fetchImpl??fetch,n=await $(e,i),r=_(n);if(r.payload)return{...r,bytes:n};if(t.sidecar===!1)return{...r,bytes:n};const s=(t.sidecarUrl??v)(e);if(!s)return{...r,bytes:n};try{const c=await i(s);if(c.ok){const u=h.adaptSidecar(await c.json());return{payload:u,source:"sidecar",findings:h.validate(u),problem:null,bytes:n}}}catch{}return{payload:null,source:"none",findings:[],problem:"This model has no EDU_modelhub metadata, either embedded in the file or in a sidecar beside it.",bytes:n}}function j(e,t="EDU_modelhub",i=console){if(!(e!=null&&e.length))return;const n=R(e),r=e.filter(o=>o.severity!=="error");n.length&&i.warn(`${t}: ${n.length} validation error(s)`,n),r.length&&i.info(`${t}: ${r.length} validation warning(s)`,r)}function T(e){return Array.isArray(e)&&e.length===3&&e.every(t=>typeof t=="number"&&Number.isFinite(t))}function A(e){return e==="blender"}function x(e,t){if(!T(e))return null;const[i,n,r]=e;return A(t)?[i,r,-n]:[i,n,r]}function Z(e,t){if(!T(e))return null;const[i,n,r]=e;return A(t)?[i,-r,n]:[i,n,r]}function C(e){if(!Array.isArray(e))return[];const t=new Set;return e.map((i,n)=>{const o=(typeof(i==null?void 0:i.id)=="string"?i.id.trim():"")||`annotation-${n+1}`;let s=o,c=2;for(;t.has(s);)s=`${o}-${c++}`;return t.add(s),{...i??{},id:s}})}function M(e){const t=C(e==null?void 0:e.annotations),i=n=>Number.isFinite(n.readingOrder)?n.readingOrder:Number.MAX_SAFE_INTEGER;return t.map((n,r)=>({annotation:n,index:r})).sort((n,r)=>i(n.annotation)-i(r.annotation)||n.index-r.index).map(n=>n.annotation)}function S(e){return(Array.isArray(e==null?void 0:e.regions)?e.regions:[]).map((i,n)=>({region:i,index:n})).sort((i,n)=>(i.region.order??Number.MAX_SAFE_INTEGER)-(n.region.order??Number.MAX_SAFE_INTEGER)||i.index-n.index).map(i=>i.region)}function I(e){var t;return e?((t=e.label)==null?void 0:t.trim())||e.id||"Unnamed region":"Unknown region"}function X(e){if(!e)return[];const t=[],i=/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g;let n=0,r;for(;(r=i.exec(e))!==null;)r.index>n&&t.push({text:e.slice(n,r.index)}),t.push({text:r[1],url:r[2]}),n=r.index+r[0].length;return n<e.length&&t.push({text:e.slice(n)}),t.filter(o=>o.text.trim()||o.url)}function Q(e){const t=e==null?void 0:e.coordinateSpace,i=S(e),n=M(e),r=new Map(i.map(a=>[a.id,a])),o=new Map(n.map(a=>[a.id,a])),s=new Map;for(const a of n){const l=a.regionId;if(!l)continue;const d=s.get(l);d?d.push(a):s.set(l,[a])}const c=[];for(const a of n){const l=a.anchor;if(!l||l.type!=="marker")continue;const d=x(l.position,t);if(!d)continue;const f=a.camera,p=f?x(f.eye,t):null,g=f?x(f.target,t):null;c.push({annotation:a,position:d,...p&&g?{camera:{eye:p,target:g,...f!=null&&f.fov?{fov:f.fov}:{}}}:{}})}const u=new Set(i.filter(a=>a.visibilityDefault==="hidden").map(a=>a.id));return{payload:e,regions:i,regionsById:r,annotations:n,annotationsById:o,annotationsByRegion:s,markerAnchors:c,hiddenRegionIds:u}}function Y(e){return e.isMesh===!0}function J(e,t,i=[]){var u;const n=new Map,r=new Map,o=new Map,s=new Map;for(const[a,l]of((t==null?void 0:t.meshes)??[]).entries())for(const d of l.primitives??[]){const f=(u=d.extras)==null?void 0:u.modelhubRegionId;typeof f=="string"&&f&&(s.set(a,f),l.name&&r.set(l.name,f))}for(const a of(t==null?void 0:t.nodes)??[]){if(a.mesh===void 0||!a.name)continue;const l=s.get(a.mesh);l&&o.set(a.name,l)}const c=new Set(i.map(a=>a.id));return e.traverse(a=>{var p,g;if(!Y(a))return;const l=(g=(p=a.geometry)==null?void 0:p.userData)==null?void 0:g.modelhubRegionId,d=typeof l=="string"&&l||o.get(a.name)||r.get(a.name)||(c.has(a.name)?a.name:void 0);if(!d)return;const f=n.get(d);f?f.push(a):n.set(d,[a]),a.userData.modelhubOriginalMaterial===void 0&&(a.userData.modelhubOriginalMaterial=a.material)}),n}function ee(e){const t={};for(const[i,n]of e)t[i]=n.length;return t}function B(e,t,i){for(const n of e.get(t)??[])n.visible=i}function te(e,t){for(const i of e.keys())B(e,i,!t.has(i))}function ne(e,t,i){for(const n of e.get(t)??[])n.material=i}function ie(e,t){for(const i of e.get(t)??[]){const n=i.userData.modelhubOriginalMaterial;n&&(i.material=n)}}const k="#2196f3",re="#ff5722";function N(e,t={}){const{fill:i=k,text:n="#ffffff",textStroke:r="rgba(0, 0, 0, 0.45)"}=t,o=256,s=document.createElement("canvas");s.width=o,s.height=o;const c=s.getContext("2d");return c&&(c.beginPath(),c.arc(o/2,o/2,o/2-4,0,Math.PI*2),c.fillStyle=i,c.fill(),c.strokeStyle="#ffffff",c.lineWidth=8,c.stroke(),c.fillStyle=n,c.strokeStyle=r,c.lineWidth=10,c.font="bold 132px Arial, sans-serif",c.textAlign="center",c.textBaseline="middle",c.strokeText(String(e),o/2,o/2),c.fillText(String(e),o/2,o/2)),s}function D(e,t=.2,i){const n=new m.SpriteMaterial({map:new m.CanvasTexture(N(e,i)),transparent:!0,depthTest:!1}),r=new m.Sprite(n);return r.scale.set(t,t,1),r.renderOrder=999,r}function oe(e,t,i){var r;const n=e.material;(r=n.map)==null||r.dispose(),n.map=new m.CanvasTexture(N(t,{fill:i})),n.needsUpdate=!0}function se(e,t,i=.2,n){return t.map((r,o)=>{const s=D(o+1,i,n);return s.position.set(...r.position),s.name=`modelhub_marker_${r.annotation.id}`,s.userData.annotationId=r.annotation.id,s.userData.displayNumber=o+1,e.add(s),s})}function ae(e){const{camera:t,model:i,anchors:n,width:r,height:o,isOccluded:s}=e,c=e.referenceDistance??1,u=new m.Vector3,a={};i.updateMatrixWorld();for(const l of n){u.set(...l.position).applyMatrix4(i.matrixWorld);const d=u.distanceTo(t.position),f=s?s(u,d):!1,p=u.clone().project(t);a[l.annotation.id]={x:(p.x*.5+.5)*r,y:(-p.y*.5+.5)*o,scale:Math.min(1.6,Math.max(.55,c/Math.max(d,.001))),zIndex:Math.max(1,Math.round((1-p.z)*1e5))+(f?0:2e5),inView:p.z<1&&p.x>-1.05&&p.x<1.05&&p.y>-1.05&&p.y<1.05,occluded:f}}return a}const L=.03,ce={enabled:!1,isOccluded:()=>!1,dispose:()=>{}};async function le(e){var o;let t=[],i=[];if(e.traverse(s=>{if(!s.isMesh)return;const c=s.geometry;c!=null&&c.computeBoundsTree&&(t.push(c),i.push(s))}),!t.length)return ce;for(const s of t)s.boundsTree||(o=s.computeBoundsTree)==null||o.call(s,{maxLeafTris:8});const n=new m.Raycaster;n.firstHitOnly=!0;const r=new m.Vector3;return{enabled:!0,isOccluded(s,c,u){return r.subVectors(c,s.position).normalize(),n.set(s.position,r),n.near=0,n.far=Math.max(0,u-L),n.camera=s,n.intersectObjects(i,!1).length>0},dispose(){var s;for(const c of t)(s=c.disposeBoundsTree)==null||s.call(c);t=[],i=[]}}}const y={headlight:{ambient:.7,lights:[{color:[1,1,1],intensity:.9,position:[4,8,6]},{color:[1,1,1],intensity:2.4,position:[0,0,0],attach:!0,spot:!0}]},studio:{ambient:.85,lights:[{color:[.78,.8,1],intensity:1.45,position:[1.35,6.75,-7.26]},{color:[1,.88,.71],intensity:1.5,position:[-5.71,3.5,7.43]},{color:[.42,.74,1],intensity:.25,position:[7.23,6.78,1.29]}]},"dual-cam":{ambient:.6,lights:[{color:[.6,.48,.3],intensity:1.6,position:[-1,0,1],attach:!0},{color:[.42,.42,.6],intensity:1.6,position:[1,0,1],attach:!0}]},"studio-cam":{ambient:.7,lights:[{color:[.78,.8,1],intensity:1.35,position:[-1,.3,1],attach:!0},{color:[1,.88,.71],intensity:1.4,position:[1,.3,1],attach:!0},{color:[.42,.74,1],intensity:.25,position:[7.23,6.78,1.29]}]}},U="headlight";function ue(){return Object.keys(y)}function de(e){var t;(t=e.parent)==null||t.remove(e)}function fe(e){var c;const{scene:t,camera:i,exposure:n=1}=e;(c=e.previous)==null||c.remove();const r=y[e.preset??""]??y[U],o=[],s=new m.AmbientLight(16777215,r.ambient*n);t.add(s),o.push(s);for(const u of r.lights){let a;if(u.spot){const d=new m.SpotLight(new m.Color(...u.color));d.angle=.5,d.penumbra=.9,d.decay=0,a=d}else a=new m.DirectionalLight(new m.Color(...u.color));a.intensity=u.intensity,a.position.set(...u.position);const l=a.target;u.attach?(l==null||l.position.set(0,0,u.spot?-1:0),i.add(a),l&&i.add(l)):(t.add(a),l&&t.add(l)),o.push(a),l&&o.push(l)}return{lights:o,remove(){for(const u of o)de(u);o.length=0}}}const b=[{id:"white",label:"White",hex:"#ffffff",light:!0},{id:"light-grey",label:"Light grey",hex:"#d4d4d4",light:!0},{id:"medium-grey",label:"Medium grey",hex:"#808080",light:!1},{id:"dark-grey",label:"Dark grey",hex:"#252525",light:!1},{id:"black",label:"Black",hex:"#000000",light:!1}],F="dark-grey";function V(e){if(typeof e!="string")return"";const t=e.trim().replace(/^#/,"");return/^[0-9a-f]{6}$/i.test(t)?`#${t.toLowerCase()}`:/^[0-9a-f]{3}$/i.test(t)?`#${[...t].map(i=>i+i).join("").toLowerCase()}`:""}function P(e){const t=Number.parseInt(e.replace("#",""),16);return Number.isFinite(t)?(.299*(t>>16&255)+.587*(t>>8&255)+.114*(t&255))/255:0}function me(e,t){const i=V(t);return i?{id:"custom",label:"Custom",hex:i,light:P(i)>.5}:b.find(n=>n.id===e)??b.find(n=>n.id===F)??b[0]}const G=2;function w(e){e.updateMatrixWorld(!0);const t=new m.Box3,i=new m.Box3;return e.traverse(n=>{const r=n,o=n;r.isSkinnedMesh?(r.computeBoundingBox(),r.boundingBox&&(i.copy(r.boundingBox).applyMatrix4(r.matrixWorld),t.union(i))):o.isMesh&&o.geometry&&(o.geometry.boundingBox||o.geometry.computeBoundingBox(),o.geometry.boundingBox&&(i.copy(o.geometry.boundingBox).applyMatrix4(o.matrixWorld),t.union(i)))}),t.isEmpty()?new m.Box3().setFromObject(e):t}function pe(e,t=G){const n=w(e).getSize(new m.Vector3),r=Math.max(n.x,n.y,n.z)||1,o=t/r;e.scale.multiplyScalar(o);const s=w(e);return e.position.sub(s.getCenter(new m.Vector3)),o}function ge(e,t=1.3){return t/Math.tan(e*Math.PI/360)}const z=.5,W=1.5;function he(e,t,i={}){t.updateMatrixWorld();const n=new m.Vector3(...e.position).applyMatrix4(t.matrixWorld);if(e.camera){const u=new m.Vector3(...e.camera.eye).applyMatrix4(t.matrixWorld),l=new m.Vector3(...e.camera.target).applyMatrix4(t.matrixWorld).clone().sub(u);l.lengthSq()<1e-10&&l.copy(n).sub(u),l.normalize();const d=Math.max(n.distanceTo(u),.2);return{position:u,target:u.clone().addScaledVector(l,d),...e.camera.fov?{fov:e.camera.fov}:{}}}const r=i.outward??!0,o=new m.Vector3(n.x,0,n.z);o.lengthSq()<1e-8&&o.set(0,0,1),o.normalize();const s=r?-(i.outwardDistance??z):i.inwardDistance??W,c=n.clone().addScaledVector(o,s);return c.y=n.y,{position:c,target:n}}const xe={cancel:()=>{}};function be(e){const{camera:t,controls:i,to:n,onFrame:r,onDone:o}=e,s=()=>{r==null||r(),o==null||o()};if(e.reducedMotion)return t.position.copy(n.position),i.target.copy(n.target),i.update(),s(),xe;const c=e.durationMs??900,u=t.position.clone(),a=i.target.clone(),l=performance.now();let d=null;const f=p=>{const g=Math.min((p-l)/c,1),O=g*(2-g);t.position.lerpVectors(u,n.position,O),i.target.lerpVectors(a,n.target,O),i.update(),r==null||r(),g<1?d=requestAnimationFrame(f):(d=null,o==null||o())};return d=requestAnimationFrame(f),{cancel(){d!==null&&cancelAnimationFrame(d),d=null}}}function ye(e,t,i){const n=t||i;e.fov!==n&&(e.fov=n,e.updateProjectionMatrix())}const H=500;function Me(e){var r,o,s;if(!e)return"Model loaded, but it carries no educational metadata.";const t=[];(r=e.model)!=null&&r.title&&t.push(`${e.model.title} loaded`),(o=e.model)!=null&&o.altText&&t.push(e.model.altText);const i=((s=e.regions)==null?void 0:s.length)??0,n=M(e).length;return i&&t.push(`${i} labelled ${i===1?"region":"regions"}`),n&&t.push(`${n} ${n===1?"annotation":"annotations"}`),t.map(c=>c.replace(/\.$/,"")).join(". ")+"."}function Ie(e,t,i){return`Annotation ${t+1} of ${i}: ${e||"Untitled"}.`}function we(e,t=H,i=()=>Date.now()){let n=-1/0,r=null,o=null;const s=a=>{n=i(),r=null,e(a)};return{announce:a=>{const l=i()-n;if(l>=t){s(a);return}r=a,o||(o=setTimeout(()=>{o=null,r!==null&&s(r)},t-l))},flush:()=>{o&&(clearTimeout(o),o=null),r!==null&&s(r)}}}function E(e,t){return`## ${e}
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const p=require("three"),_=new Set(["__proto__","constructor","prototype"]);function U(e,t){const n=new WeakSet,r=[{value:e,path:""}],o=(i,s)=>{r.push({value:i,path:s})};for(;r.length>0;){const{value:i,path:s}=r.pop();if(!(typeof i!="object"||i===null||n.has(i)))if(n.add(i),Array.isArray(i))for(let c=0;c<i.length;c++)o(i[c],`${s}[${c}]`);else t(i,s,o)}}function ie(e){const t=[];return U(e,(n,r,o)=>{for(const i of Object.keys(n)){const s=r===""?i:`${r}.${i}`;_.has(i)?t.push({path:s,key:i}):o(n[i],s)}}),t}function se(e){return U(e,(t,n,r)=>{for(const o of Object.keys(t))_.has(o)?delete t[o]:r(t[o],n)}),e}const E=1179937895,L=2,ae=1313821514,ce=21;class y extends Error{constructor(t){super(t),this.name="GlbParseError"}}function ue(e){return e instanceof ArrayBuffer?new DataView(e):new DataView(e.buffer,e.byteOffset,e.byteLength)}function le(e){return e===null?"null":Array.isArray(e)?"an array":typeof e}function de(e){const t=ue(e);if(t.byteLength<ce)throw new y(`Input too short to be a GLB file (${t.byteLength} bytes).`);const n=t.getUint32(0,!0);if(n!==E)throw new y(`Invalid GLB magic: expected 0x${E.toString(16)}, got 0x${n.toString(16)}.`);const r=t.getUint32(4,!0);if(r!==L)throw new y(`Unsupported GLB version: expected ${L}, got ${r}.`);const o=t.getUint32(8,!0);if(o>t.byteLength)throw new y(`GLB header reports ${o} bytes but only ${t.byteLength} bytes were supplied.`);if(t.byteLength<20)throw new y("GLB file truncated before first chunk header.");const i=t.getUint32(12,!0),s=t.getUint32(16,!0);if(s!==ae)throw new y(`First GLB chunk is not JSON (type 0x${s.toString(16)}).`);if(20+i>o)throw new y(`JSON chunk claims ${i} bytes but the declared GLB length is only ${o} bytes.`);if(20+i>t.byteLength)throw new y(`JSON chunk claims ${i} bytes but only ${t.byteLength-20} bytes remain in the buffer.`);const c=new Uint8Array(t.buffer,t.byteOffset+20,i),l=new TextDecoder("utf-8").decode(c);let a;try{a=JSON.parse(l)}catch(u){throw new y(`Failed to parse GLB JSON chunk: ${u.message}`)}if(a===null||typeof a!="object"||Array.isArray(a))throw new y(`GLB JSON chunk root must be an object, got ${le(a)}.`);return a}function fe(e){const t=de(e).extensions;if(t===null||typeof t!="object"||Array.isArray(t))return null;const n=t.EDU_modelhub;return n===void 0?null:se(n)}const me=/^[a-z][a-z0-9_]*$/,he=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/,pe=/^[a-z]{2}$/,ge=/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;function d(e,t){return{field:e,message:t,severity:"error"}}function $(e,t){return{field:e,message:t,severity:"warning"}}function x(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function w(e){return typeof e=="string"&&e.trim()!==""}function F(e){return typeof e=="number"&&Number.isInteger(e)&&e>=0}function h(e){if(e===void 0)return"undefined";let t;if(typeof e=="string")t=`"${e}"`;else try{const n=JSON.stringify(e);if(n===void 0)return typeof e;t=n}catch{return Array.isArray(e)?"an array":typeof e}return t.length>60?`${t.slice(0,57)}...`:t}function G(e){const t=[],n=e;if(!x(n))return[d("",`Payload must be a JSON object, got ${h(n)}.`)];ye(n,t),be(n.specVersion,t),$e(n.coordinateSpace,t),we(n.model,t);const r=ve(n.regions,t);return Me(n.annotations,r,t),t}function ye(e,t){for(const n of ie(e))t.push(d(n.path,`Disallowed key "${n.key}" at ${n.path} (prototype-pollution vector).`))}function be(e,t){(typeof e!="string"||!ge.test(e))&&t.push(d("specVersion",`specVersion must be a semver string (e.g. "0.1.0"), got ${h(e)}.`))}function $e(e,t){e!==void 0&&e!=="gltf"&&e!=="blender"&&t.push(d("coordinateSpace",`coordinateSpace must be "gltf" or "blender" when present, got ${h(e)}.`))}function we(e,t){if(!x(e)){t.push(d("model",`model is required and must be an object, got ${h(e)}.`));return}const n=e,r=n.title;w(r)||t.push(d("model.title",`model.title is required and must be a non-empty string, got ${h(r)}.`));const o=n.altText;w(o)?o.length>300&&t.push(d("model.altText",`model.altText exceeds 300 characters (${o.length} chars).`)):t.push(d("model.altText",`model.altText is required and must be a non-empty string, got ${h(o)}.`));const i=n.language;i!==void 0&&(typeof i!="string"||!pe.test(i))&&t.push($("model.language",`model.language should be an ISO 639-1 two-letter code (e.g. "en"), got ${h(i)}.`)),xe(n,t),Ae(n.source,t),Se(n,t),w(n.longDescription)||t.push($("model.longDescription","model.longDescription is recommended."))}function xe(e,t){const n=["CC0","CC BY","CC BY-SA","All Rights Reserved"],r=e.license;r!==void 0&&(typeof r!="string"||!n.includes(r))&&t.push(d("model.license",`model.license must be one of ${n.map(o=>`"${o}"`).join(", ")}, got ${h(r)}.`)),(r==="CC BY"||r==="CC BY-SA")&&!w(e.attribution)&&t.push($("model.attribution",'model.attribution is recommended when license is "CC BY" or "CC BY-SA".'))}function Ae(e,t){if(e===void 0)return;const n=["scan","photogrammetry","manual","other"];if(typeof e!="string"||!n.includes(e)){const r=n.map(o=>`"${o}"`).join(", ");t.push(d("model.source",`model.source must be one of ${r}, got ${h(e)}.`))}}function Se(e,t){for(const n of["created","modified"]){const r=e[n];r!==void 0&&(typeof r!="string"||!he.test(r))&&t.push($(`model.${n}`,`model.${n} should be ISO 8601 UTC (YYYY-MM-DDTHH:MM:SSZ), got ${h(r)}.`))}}function ve(e,t){const n=new Set;if(!Array.isArray(e)||e.length===0)return t.push(d("regions","At least one region is required.")),n;for(let r=0;r<e.length;r++){const o=e[r],i=`regions[${r}]`;if(!x(o)){t.push(d(i,`${i} must be an object, got ${h(o)}.`));continue}Ie(o,i,n,t)}return n}function Ie(e,t,n,r){const o=e.id;typeof o!="string"||!me.test(o)?r.push(d(`${t}.id`,`${t}.id must be a non-empty snake_case string (lowercase letters, digits, underscores; must start with a letter), got ${h(o)}.`)):n.has(o)?r.push(d(`${t}.id`,`Duplicate region id "${o}" at ${t}.`)):n.add(o);const i=e.visibilityDefault;i!==void 0&&i!=="shown"&&i!=="hidden"&&r.push(d(`${t}.visibilityDefault`,`${t}.visibilityDefault must be "shown" or "hidden", got ${h(i)}.`));const s=e.order;s!==void 0&&!F(s)&&r.push($(`${t}.order`,`${t}.order should be a non-negative integer, got ${h(s)}.`))}function Me(e,t,n){if(!Array.isArray(e)){n.push(d("annotations","annotations must be an array."));return}e.length<3&&n.push($("annotations",`Only ${e.length} annotation(s) defined (3–8 recommended).`)),e.length>8&&n.push($("annotations",`${e.length} annotations defined (3–8 recommended).`));const r={seenRegionIds:t,seenAnnotationIds:new Set,seenReadingOrders:new Set};for(let o=0;o<e.length;o++){const i=e[o],s=`annotations[${o}]`;if(!x(i)){n.push(d(s,`${s} must be an object, got ${h(i)}.`));continue}Oe(i,s,r,n)}Be(r.seenReadingOrders,n)}function Oe(e,t,n,r){const o=e.id;w(o)?n.seenAnnotationIds.has(o)?r.push(d(`${t}.id`,`Duplicate annotation id "${o}" at ${t}.`)):n.seenAnnotationIds.add(o):r.push(d(`${t}.id`,`${t}.id is required and must be a non-empty string, got ${h(o)}.`));const i=e.regionId;i!==void 0&&i!==""&&(typeof i!="string"||!n.seenRegionIds.has(i))&&r.push(d(`${t}.regionId`,`${t}.regionId ${h(i)} does not match any region id.`)),Te(e.readingOrder,t,n,r),Ne(e.anchor,t,r),Re(e.camera,t,r)}function Te(e,t,n,r){e!==void 0&&(F(e)?n.seenReadingOrders.has(e)?r.push(d(`${t}.readingOrder`,`Duplicate readingOrder value ${e} at ${t}.`)):n.seenReadingOrders.add(e):r.push(d(`${t}.readingOrder`,`${t}.readingOrder must be a non-negative integer, got ${h(e)}.`)))}function Ne(e,t,n){if(!x(e)){n.push(d(`${t}.anchor`,`${t}.anchor is required and must be an object, got ${h(e)}.`));return}const r=e,o=r.type;if(o==="marker"){const i=r.position;(!Array.isArray(i)||i.length!==3||!i.every(s=>typeof s=="number"))&&n.push(d(`${t}.anchor.position`,`${t}.anchor.position must be an array of exactly 3 numbers for type "marker".`))}else o==="node"||o==="primitive"||o==="aabb"?w(r.ref)||n.push(d(`${t}.anchor.ref`,`${t}.anchor.ref is required for anchor type "${o}".`)):n.push(d(`${t}.anchor.type`,`${t}.anchor.type must be "marker", "node", "primitive", or "aabb", got ${h(o)}.`))}function Re(e,t,n){if(e===void 0)return;if(!x(e)){n.push(d(`${t}.camera`,`${t}.camera must be an object when present, got ${h(e)}.`));return}for(const o of["eye","target"]){const i=e[o];(!Array.isArray(i)||i.length!==3||!i.every(s=>typeof s=="number"&&Number.isFinite(s)))&&n.push(d(`${t}.camera.${o}`,`${t}.camera.${o} must be an array of exactly 3 finite numbers.`))}const r=e.fov;r!==void 0&&(typeof r!="number"||!(r>0))&&n.push(d(`${t}.camera.fov`,`${t}.camera.fov must be a number greater than 0 when present, got ${h(r)}.`))}function Be(e,t){if(e.size===0)return;const n=[...e].sort((r,o)=>r-o);for(let r=0;r<n.length;r++)if(n[r]!==r){t.push($("annotations[*].readingOrder",`readingOrder values are not sequential from 0. Expected 0–${n.length-1} but found gaps or wrong start.`));break}}class k extends Error{constructor(t){super(t),this.name="AdaptError"}}class Ce{adapt(t){var n,r;const o=t.model,i=((n=o==null?void 0:o.title)==null?void 0:n.trim())??"",s=((r=o==null?void 0:o.altText)==null?void 0:r.trim())??"";if(!i)throw new k("Sidecar is missing required field: model.title");if(!s)throw new k("Sidecar is missing required field: model.altText");return t}}const Ee=new Ce;function Le(e){return Ee.adapt(e)}function V(e){return e.filter(t=>t.severity==="error")}function P(e){const[t,n=""]=e.split("?",2);if(!/\.glb$/i.test(t))return null;const r=t.replace(/\.glb$/i,".modelhub.json");return n?`${r}?${n}`:r}function ke(e,t){var i;if(!e||e.startsWith("blob:")||e.startsWith("data:"))return"";const n=t??((i=globalThis.location)==null?void 0:i.href),r=n?new URL(e,n).href:e,o=r.lastIndexOf("/");return o===-1?"":r.slice(0,o+1)}async function z(e,t=fetch){const n=await t(e);if(!n.ok)throw new Error(`Failed to fetch ${e} (HTTP ${n.status}).`);return n.arrayBuffer()}function j(e){const t=fe(e);if(!t)return{payload:null,source:"none",findings:[],problem:"This model carries no EDU_modelhub metadata."};const n=G(t);return{payload:t,source:"embedded",findings:n,problem:null}}async function De(e,t={}){const n=t.fetchImpl??fetch,r=await z(e,n),o=j(r);if(o.payload)return{...o,bytes:r};if(t.sidecar===!1)return{...o,bytes:r};const s=(t.sidecarUrl??P)(e);if(!s)return{...o,bytes:r};try{const c=await n(s);if(c.ok){const l=Le(await c.json());return{payload:l,source:"sidecar",findings:G(l),problem:null,bytes:r}}}catch{}return{payload:null,source:"none",findings:[],problem:"This model has no EDU_modelhub metadata, either embedded in the file or in a sidecar beside it.",bytes:r}}function _e(e,t="EDU_modelhub",n=console){if(!(e!=null&&e.length))return;const r=V(e),o=e.filter(i=>i.severity!=="error");r.length&&n.warn(`${t}: ${r.length} validation error(s)`,r),o.length&&n.info(`${t}: ${o.length} validation warning(s)`,o)}function Ue(e){return(e==null?void 0:e.name)==="GlbParseError"}function T(e){return Array.isArray(e)&&e.length===3&&e.every(t=>typeof t=="number"&&Number.isFinite(t))}function N(e){return e==="blender"}function A(e,t){if(!T(e))return null;const[n,r,o]=e;return N(t)?[n,o,-r]:[n,r,o]}function Fe(e,t){if(!T(e))return null;const[n,r,o]=e;return N(t)?[n,-o,r]:[n,r,o]}function W(e){if(!Array.isArray(e))return[];const t=new Set;return e.map((n,r)=>{const i=(typeof(n==null?void 0:n.id)=="string"?n.id.trim():"")||`annotation-${r+1}`;let s=i,c=2;for(;t.has(s);)s=`${i}-${c++}`;return t.add(s),{...n??{},id:s}})}function I(e){const t=W(e==null?void 0:e.annotations),n=r=>Number.isFinite(r.readingOrder)?r.readingOrder:Number.MAX_SAFE_INTEGER;return t.map((r,o)=>({annotation:r,index:o})).sort((r,o)=>n(r.annotation)-n(o.annotation)||r.index-o.index).map(r=>r.annotation)}function R(e){return(Array.isArray(e==null?void 0:e.regions)?e.regions:[]).map((n,r)=>({region:n,index:r})).sort((n,r)=>(n.region.order??Number.MAX_SAFE_INTEGER)-(r.region.order??Number.MAX_SAFE_INTEGER)||n.index-r.index).map(n=>n.region)}const Ge="Auto-created from object ";function q(e){var n;const t=(n=e==null?void 0:e.description)==null?void 0:n.trim();return t?!t.startsWith(Ge):!1}function Ve(e){return Array.isArray(e)?e.filter(t=>{var n;return q(t)||!((n=t.description)!=null&&n.trim())}):[]}function M(e){var t;return e?((t=e.label)==null?void 0:t.trim())||e.id||"Unnamed region":"Unknown region"}function Pe(e){if(!e)return[];const t=[],n=/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g;let r=0,o;for(;(o=n.exec(e))!==null;)o.index>r&&t.push({text:e.slice(r,o.index)}),t.push({text:o[1],url:o[2]}),r=o.index+o[0].length;return r<e.length&&t.push({text:e.slice(r)}),t.filter(i=>i.text.trim()||i.url)}function ze(e){const t=e==null?void 0:e.coordinateSpace,n=R(e),r=I(e),o=new Map(n.map(a=>[a.id,a])),i=new Map(r.map(a=>[a.id,a])),s=new Map;for(const a of r){const u=a.regionId;if(!u)continue;const f=s.get(u);f?f.push(a):s.set(u,[a])}const c=[];for(const a of r){const u=a.anchor;if(!u||u.type!=="marker")continue;const f=A(u.position,t);if(!f)continue;const m=a.camera,g=m?A(m.eye,t):null,b=m?A(m.target,t):null;c.push({annotation:a,position:f,...g&&b?{camera:{eye:g,target:b,...m!=null&&m.fov?{fov:m.fov}:{}}}:{}})}const l=new Set(n.filter(a=>a.visibilityDefault==="hidden").map(a=>a.id));return{payload:e,regions:n,regionsById:o,annotations:r,annotationsById:i,annotationsByRegion:s,markerAnchors:c,hiddenRegionIds:l}}function je(e){return e.isMesh===!0}function We(e,t,n=[]){var l;const r=new Map,o=new Map,i=new Map,s=new Map;for(const[a,u]of((t==null?void 0:t.meshes)??[]).entries())for(const f of u.primitives??[]){const m=(l=f.extras)==null?void 0:l.modelhubRegionId;typeof m=="string"&&m&&(s.set(a,m),u.name&&o.set(u.name,m))}for(const a of(t==null?void 0:t.nodes)??[]){if(a.mesh===void 0||!a.name)continue;const u=s.get(a.mesh);u&&i.set(a.name,u)}const c=new Set(n.map(a=>a.id));return e.traverse(a=>{var g,b;if(!je(a))return;const u=(b=(g=a.geometry)==null?void 0:g.userData)==null?void 0:b.modelhubRegionId,f=typeof u=="string"&&u||i.get(a.name)||o.get(a.name)||(c.has(a.name)?a.name:void 0);if(!f)return;const m=r.get(f);m?m.push(a):r.set(f,[a]),a.userData.modelhubOriginalMaterial===void 0&&(a.userData.modelhubOriginalMaterial=a.material)}),r}function qe(e){const t={};for(const[n,r]of e)t[n]=r.length;return t}function H(e,t,n){for(const r of e.get(t)??[])r.visible=n}function He(e,t){for(const n of e.keys())H(e,n,!t.has(n))}function Ye(e,t,n){for(const r of e.get(t)??[])r.material=n}function Ke(e,t){for(const n of e.get(t)??[]){const r=n.userData.modelhubOriginalMaterial;r&&(n.material=r)}}const Y="#2196f3",Ze="#ff5722";function B(e,t={}){const{fill:n=Y,text:r="#ffffff",textStroke:o="rgba(0, 0, 0, 0.45)"}=t,i=256,s=document.createElement("canvas");s.width=i,s.height=i;const c=s.getContext("2d");return c&&(c.beginPath(),c.arc(i/2,i/2,i/2-4,0,Math.PI*2),c.fillStyle=n,c.fill(),c.strokeStyle="#ffffff",c.lineWidth=8,c.stroke(),c.fillStyle=r,c.strokeStyle=o,c.lineWidth=10,c.font="bold 132px Arial, sans-serif",c.textAlign="center",c.textBaseline="middle",c.strokeText(String(e),i/2,i/2),c.fillText(String(e),i/2,i/2)),s}function K(e,t=.2,n){const r=new p.SpriteMaterial({map:new p.CanvasTexture(B(e,n)),transparent:!0,depthTest:!1}),o=new p.Sprite(r);return o.scale.set(t,t,1),o.renderOrder=999,o}function Xe(e,t,n){var o;const r=e.material;(o=r.map)==null||o.dispose(),r.map=new p.CanvasTexture(B(t,{fill:n})),r.needsUpdate=!0}function Qe(e,t,n=.2,r){return t.map((o,i)=>{const s=K(i+1,n,r);return s.position.set(...o.position),s.name=`modelhub_marker_${o.annotation.id}`,s.userData.annotationId=o.annotation.id,s.userData.displayNumber=i+1,e.add(s),s})}function Je(e){const{camera:t,model:n,anchors:r,width:o,height:i,isOccluded:s}=e,c=e.referenceDistance??1,l=new p.Vector3,a={};n.updateMatrixWorld();for(const u of r){l.set(...u.position).applyMatrix4(n.matrixWorld);const f=l.distanceTo(t.position),m=s?s(l,f):!1,g=l.clone().project(t);a[u.annotation.id]={x:(g.x*.5+.5)*o,y:(-g.y*.5+.5)*i,scale:Math.min(1.6,Math.max(.55,c/Math.max(f,.001))),zIndex:Math.max(1,Math.round((1-g.z)*1e5))+(m?0:2e5),inView:g.z<1&&g.x>-1.05&&g.x<1.05&&g.y>-1.05&&g.y<1.05,occluded:m}}return a}const Z=.03,et={enabled:!1,isOccluded:()=>!1,dispose:()=>{}};async function tt(e){var i;let t=[],n=[];if(e.traverse(s=>{if(!s.isMesh)return;const c=s.geometry;c!=null&&c.computeBoundsTree&&(t.push(c),n.push(s))}),!t.length)return et;for(const s of t)s.boundsTree||(i=s.computeBoundsTree)==null||i.call(s,{maxLeafTris:8});const r=new p.Raycaster;r.firstHitOnly=!0;const o=new p.Vector3;return{enabled:!0,isOccluded(s,c,l){return o.subVectors(c,s.position).normalize(),r.set(s.position,o),r.near=0,r.far=Math.max(0,l-Z),r.camera=s,r.intersectObjects(n,!1).length>0},dispose(){var s;for(const c of t)(s=c.disposeBoundsTree)==null||s.call(c);t=[],n=[]}}}const v={headlight:{ambient:.7,lights:[{color:[1,1,1],intensity:.9,position:[4,8,6]},{color:[1,1,1],intensity:2.4,position:[0,0,0],attach:!0,spot:!0}]},studio:{ambient:.85,lights:[{color:[.78,.8,1],intensity:1.45,position:[1.35,6.75,-7.26]},{color:[1,.88,.71],intensity:1.5,position:[-5.71,3.5,7.43]},{color:[.42,.74,1],intensity:.25,position:[7.23,6.78,1.29]}]},"dual-cam":{ambient:.6,lights:[{color:[.6,.48,.3],intensity:1.6,position:[-1,0,1],attach:!0},{color:[.42,.42,.6],intensity:1.6,position:[1,0,1],attach:!0}]},"studio-cam":{ambient:.7,lights:[{color:[.78,.8,1],intensity:1.35,position:[-1,.3,1],attach:!0},{color:[1,.88,.71],intensity:1.4,position:[1,.3,1],attach:!0},{color:[.42,.74,1],intensity:.25,position:[7.23,6.78,1.29]}]}},X="headlight";function nt(){return Object.keys(v)}function rt(e){var t;(t=e.parent)==null||t.remove(e)}function ot(e){var c;const{scene:t,camera:n,exposure:r=1}=e;(c=e.previous)==null||c.remove();const o=v[e.preset??""]??v[X],i=[],s=new p.AmbientLight(16777215,o.ambient*r);t.add(s),i.push(s);for(const l of o.lights){let a;if(l.spot){const f=new p.SpotLight(new p.Color(...l.color));f.angle=.5,f.penumbra=.9,f.decay=0,a=f}else a=new p.DirectionalLight(new p.Color(...l.color));a.intensity=l.intensity,a.position.set(...l.position);const u=a.target;l.attach?(u==null||u.position.set(0,0,l.spot?-1:0),n.add(a),u&&n.add(u)):(t.add(a),u&&t.add(u)),i.push(a),u&&i.push(u)}return{lights:i,remove(){for(const l of i)rt(l);i.length=0}}}const S=[{id:"white",label:"White",hex:"#ffffff",light:!0},{id:"light-grey",label:"Light grey",hex:"#d4d4d4",light:!0},{id:"medium-grey",label:"Medium grey",hex:"#808080",light:!1},{id:"dark-grey",label:"Dark grey",hex:"#252525",light:!1},{id:"black",label:"Black",hex:"#000000",light:!1}],Q="dark-grey";function J(e){if(typeof e!="string")return"";const t=e.trim().replace(/^#/,"");return/^[0-9a-f]{6}$/i.test(t)?`#${t.toLowerCase()}`:/^[0-9a-f]{3}$/i.test(t)?`#${[...t].map(n=>n+n).join("").toLowerCase()}`:""}function ee(e){const t=Number.parseInt(e.replace("#",""),16);return Number.isFinite(t)?(.299*(t>>16&255)+.587*(t>>8&255)+.114*(t&255))/255:0}function it(e,t){const n=J(t);return n?{id:"custom",label:"Custom",hex:n,light:ee(n)>.5}:S.find(r=>r.id===e)??S.find(r=>r.id===Q)??S[0]}const te=2;function O(e){e.updateMatrixWorld(!0);const t=new p.Box3,n=new p.Box3;return e.traverse(r=>{const o=r,i=r;o.isSkinnedMesh?(o.computeBoundingBox(),o.boundingBox&&(n.copy(o.boundingBox).applyMatrix4(o.matrixWorld),t.union(n))):i.isMesh&&i.geometry&&(i.geometry.boundingBox||i.geometry.computeBoundingBox(),i.geometry.boundingBox&&(n.copy(i.geometry.boundingBox).applyMatrix4(i.matrixWorld),t.union(n)))}),t.isEmpty()?new p.Box3().setFromObject(e):t}function st(e,t=te){const r=O(e).getSize(new p.Vector3),o=Math.max(r.x,r.y,r.z)||1,i=t/o;e.scale.multiplyScalar(i);const s=O(e);return e.position.sub(s.getCenter(new p.Vector3)),i}function at(e,t=1.3){return t/Math.tan(e*Math.PI/360)}const ne=.5,re=1.5;function ct(e,t,n={}){t.updateMatrixWorld();const r=new p.Vector3(...e.position).applyMatrix4(t.matrixWorld);if(e.camera){const l=new p.Vector3(...e.camera.eye).applyMatrix4(t.matrixWorld),u=new p.Vector3(...e.camera.target).applyMatrix4(t.matrixWorld).clone().sub(l);u.lengthSq()<1e-10&&u.copy(r).sub(l),u.normalize();const f=Math.max(r.distanceTo(l),.2);return{position:l,target:l.clone().addScaledVector(u,f),...e.camera.fov?{fov:e.camera.fov}:{}}}const o=n.outward??!0,i=new p.Vector3(r.x,0,r.z);i.lengthSq()<1e-8&&i.set(0,0,1),i.normalize();const s=o?-(n.outwardDistance??ne):n.inwardDistance??re,c=r.clone().addScaledVector(i,s);return c.y=r.y,{position:c,target:r}}const ut={cancel:()=>{}};function lt(e){const{camera:t,controls:n,to:r,onFrame:o,onDone:i}=e,s=()=>{o==null||o(),i==null||i()};if(e.reducedMotion)return t.position.copy(r.position),n.target.copy(r.target),n.update(),s(),ut;const c=e.durationMs??900,l=t.position.clone(),a=n.target.clone(),u=performance.now();let f=null;const m=g=>{const b=Math.min((g-u)/c,1),C=b*(2-b);t.position.lerpVectors(l,r.position,C),n.target.lerpVectors(a,r.target,C),n.update(),o==null||o(),b<1?f=requestAnimationFrame(m):(f=null,i==null||i())};return f=requestAnimationFrame(m),{cancel(){f!==null&&cancelAnimationFrame(f),f=null}}}function dt(e,t,n){const r=t||n;e.fov!==r&&(e.fov=r,e.updateProjectionMatrix())}const oe=500;function ft(e){var o,i,s;if(!e)return"Model loaded, but it carries no educational metadata.";const t=[];(o=e.model)!=null&&o.title&&t.push(`${e.model.title} loaded`),(i=e.model)!=null&&i.altText&&t.push(e.model.altText);const n=((s=e.regions)==null?void 0:s.length)??0,r=I(e).length;return n&&t.push(`${n} labeled ${n===1?"region":"regions"}`),r&&t.push(`${r} ${r===1?"annotation":"annotations"}`),t.map(c=>c.replace(/\.$/,"")).join(". ")+"."}function mt(e,t,n){return`Annotation ${t+1} of ${n}: ${e||"Untitled"}.`}function ht(e,t=oe,n=()=>Date.now()){let r=-1/0,o=null,i=null;const s=a=>{r=n(),o=null,e(a)};return{announce:a=>{const u=n()-r;if(u>=t){s(a);return}o=a,i||(i=setTimeout(()=>{i=null,o!==null&&s(o)},t-u))},flush:()=>{i&&(clearTimeout(i),i=null),o!==null&&s(o)}}}function D(e,t){return`## ${e}
2
2
  ${t}
3
3
 
4
- `}function Te(e,t={}){const i=e.model??{},n=S(e),r=M(e);let o=`# ${i.title||"Untitled model"}
4
+ `}function pt(e,t={}){const n=e.model??{},r=R(e),o=I(e);let i=`# ${n.title||"Untitled model"}
5
5
 
6
- `;if(i.altText&&(o+=E(t.summaryHeading??"Summary",i.altText)),i.longDescription&&(o+=E("Full description",i.longDescription)),n.length){o+=`## Regions (${n.length})
6
+ `;if(n.altText&&(i+=D(t.summaryHeading??"Summary",n.altText)),n.longDescription&&(i+=D("Full description",n.longDescription)),r.length){i+=`## Regions (${r.length})
7
7
 
8
- `;for(const s of n)o+=`### ${I(s)}
8
+ `;for(const s of r)i+=`### ${M(s)}
9
9
  ${s.description||"No description."}
10
10
 
11
- `}return r.length&&(o+=`## Annotations (${r.length})
11
+ `}return o.length&&(i+=`## Annotations (${o.length})
12
12
 
13
- `,r.forEach((s,c)=>{const u=s.regionId?` (${I(n.find(l=>l.id===s.regionId))})`:"",a=`${c+1}. ${s.title||"Untitled"}${u}`;o+=`### ${a}
13
+ `,o.forEach((s,c)=>{const l=s.regionId?` (${M(r.find(u=>u.id===s.regionId))})`:"",a=`${c+1}. ${s.title||"Untitled"}${l}`;i+=`### ${a}
14
14
  ${s.body||"No description."}
15
15
 
16
- `})),o+=`## Licensing
17
- `,o+=`- **License:** ${i.license||"Not specified"}
18
- `,o+=`- **Attribution:** ${i.attribution||"Not specified"}
19
- `,o+=`- **Creator:** ${i.creator||"Not specified"}
20
- `,o+=`- **Source:** ${i.source||"Not specified"}
21
- `,o}function Ae(e){var i;return`${(((i=e.model)==null?void 0:i.title)||"model").trim().replace(/[^\w\-. ]+/g,"").replace(/\s+/g,"_")||"model"}_transcript.md`}function Se(e){e&&e.traverse(t=>{var o,s;const i=t,n=i.geometry;n&&(n.boundsTree&&n.disposeBoundsTree&&n.disposeBoundsTree(),(o=n.dispose)==null||o.call(n));const r=[].concat(i.material??[]);for(const c of r)if(c){for(const u of Object.values(c)){const a=u;a&&a.isTexture&&a.dispose()}(s=c.dispose)==null||s.call(c)}})}Object.defineProperty(exports,"GlbParseError",{enumerable:!0,get:()=>h.GlbParseError});exports.BACKGROUND_OPTIONS=b;exports.CAMERA_ANNOUNCE_INTERVAL=H;exports.DEFAULT_BACKGROUND=F;exports.DEFAULT_LIGHTING=U;exports.LIGHTING_PRESETS=y;exports.MARKER_FILL=k;exports.MARKER_FILL_ACTIVE=re;exports.NORMALIZED_SIZE=G;exports.OCCLUSION_BIAS=L;exports.TOUR_DISTANCE_INWARD=W;exports.TOUR_DISTANCE_OUTWARD=z;exports.annotationSummary=Ie;exports.annotationsInReadingOrder=M;exports.applyFov=ye;exports.applyLighting=fe;exports.applyVisibilityDefaults=te;exports.buildRegionMeshMap=J;exports.buildTranscript=Te;exports.clearRegionHighlight=ie;exports.computeDisplayBox=w;exports.computeTourPlacement=he;exports.createAnnouncer=we;exports.createMarkerSprite=D;exports.createMarkerSprites=se;exports.createOcclusionIndex=le;exports.disposeSceneGraph=Se;exports.distanceForFov=ge;exports.drawMarkerCanvas=N;exports.errorsOf=R;exports.fetchBytes=$;exports.flyCamera=be;exports.frameModel=pe;exports.fromViewerPosition=Z;exports.highlightRegion=ne;exports.isVec3=T;exports.lightingPresetNames=ue;exports.loadModel=q;exports.logFindings=j;exports.luminance=P;exports.modelSummary=Me;exports.needsConversion=A;exports.normalizeAnnotationIds=C;exports.normalizeHexColor=V;exports.normalizeModel=Q;exports.parseBodySegments=X;exports.projectMarkers=ae;exports.readEmbedded=_;exports.regionBindingCounts=ee;exports.regionLabel=I;exports.regionsInOrder=S;exports.resolveBackground=me;exports.resourcePathFor=K;exports.setMarkerSpriteColor=oe;exports.setRegionVisible=B;exports.sidecarUrlFor=v;exports.toViewerPosition=x;exports.transcriptFileName=Ae;
16
+ `})),i+=`## Licensing
17
+ `,i+=`- **License:** ${n.license||"Not specified"}
18
+ `,i+=`- **Attribution:** ${n.attribution||"Not specified"}
19
+ `,i+=`- **Creator:** ${n.creator||"Not specified"}
20
+ `,i+=`- **Source:** ${n.source||"Not specified"}
21
+ `,i}function gt(e){var n;return`${(((n=e.model)==null?void 0:n.title)||"model").trim().replace(/[^\w\-. ]+/g,"").replace(/\s+/g,"_")||"model"}_transcript.md`}function yt(e){e&&e.traverse(t=>{var i,s;const n=t,r=n.geometry;r&&(r.boundsTree&&r.disposeBoundsTree&&r.disposeBoundsTree(),(i=r.dispose)==null||i.call(r));const o=[].concat(n.material??[]);for(const c of o)if(c){for(const l of Object.values(c)){const a=l;a&&a.isTexture&&a.dispose()}(s=c.dispose)==null||s.call(c)}})}exports.BACKGROUND_OPTIONS=S;exports.CAMERA_ANNOUNCE_INTERVAL=oe;exports.DEFAULT_BACKGROUND=Q;exports.DEFAULT_LIGHTING=X;exports.LIGHTING_PRESETS=v;exports.MARKER_FILL=Y;exports.MARKER_FILL_ACTIVE=Ze;exports.NORMALIZED_SIZE=te;exports.OCCLUSION_BIAS=Z;exports.TOUR_DISTANCE_INWARD=re;exports.TOUR_DISTANCE_OUTWARD=ne;exports.annotationSummary=mt;exports.annotationsInReadingOrder=I;exports.applyFov=dt;exports.applyLighting=ot;exports.applyVisibilityDefaults=He;exports.buildRegionMeshMap=We;exports.buildTranscript=pt;exports.clearRegionHighlight=Ke;exports.computeDisplayBox=O;exports.computeTourPlacement=ct;exports.createAnnouncer=ht;exports.createMarkerSprite=K;exports.createMarkerSprites=Qe;exports.createOcclusionIndex=tt;exports.disposeSceneGraph=yt;exports.distanceForFov=at;exports.drawMarkerCanvas=B;exports.errorsOf=V;exports.fetchBytes=z;exports.flyCamera=lt;exports.frameModel=st;exports.fromViewerPosition=Fe;exports.highlightRegion=Ye;exports.isAuthoredRegion=q;exports.isGlbParseError=Ue;exports.isVec3=T;exports.lightingPresetNames=nt;exports.loadModel=De;exports.logFindings=_e;exports.luminance=ee;exports.modelSummary=ft;exports.needsConversion=N;exports.normalizeAnnotationIds=W;exports.normalizeHexColor=J;exports.normalizeModel=ze;exports.parseBodySegments=Pe;exports.projectMarkers=Je;exports.readEmbedded=j;exports.readableRegions=Ve;exports.regionBindingCounts=qe;exports.regionLabel=M;exports.regionsInOrder=R;exports.resolveBackground=it;exports.resourcePathFor=ke;exports.setMarkerSpriteColor=Xe;exports.setRegionVisible=H;exports.sidecarUrlFor=P;exports.toViewerPosition=A;exports.transcriptFileName=gt;
package/dist/index.d.ts CHANGED
@@ -10,9 +10,10 @@
10
10
  * renderer and its own UI; this package supplies the behaviour underneath so
11
11
  * two viewers of the same model agree about what it says.
12
12
  */
13
- export { GlbParseError, errorsOf, fetchBytes, loadModel, logFindings, readEmbedded, resourcePathFor, sidecarUrlFor, type LoadOptions, type LoadedModel, type MetadataResult, type MetadataSource } from "./metadata.js";
13
+ export { errorsOf, fetchBytes, loadModel, logFindings, isGlbParseError, readEmbedded, resourcePathFor, sidecarUrlFor, type LoadOptions, type LoadedModel, type MetadataResult, type MetadataSource } from "./metadata.js";
14
+ export type { Annotation, AnnotationCamera, EduModelHub, ModelLicense, ModelMetadata, ModelSource, Region, ValidationResult, ValidationSeverity, VisibilityDefault } from "./types.js";
14
15
  export { fromViewerPosition, isVec3, needsConversion, toViewerPosition, type Vec3 } from "./coordinates.js";
15
- export { annotationsInReadingOrder, normalizeAnnotationIds, normalizeModel, parseBodySegments, regionLabel, regionsInOrder, type BodySegment, type IdentifiedAnnotation, type MarkerAnchor, type NormalizedModel } from "./normalize.js";
16
+ export { annotationsInReadingOrder, isAuthoredRegion, normalizeAnnotationIds, normalizeModel, parseBodySegments, readableRegions, regionLabel, regionsInOrder, type BodySegment, type IdentifiedAnnotation, type MarkerAnchor, type NormalizedModel } from "./normalize.js";
16
17
  export { applyVisibilityDefaults, buildRegionMeshMap, clearRegionHighlight, highlightRegion, regionBindingCounts, setRegionVisible, type GltfJsonLike, type RegionMeshMap } from "./regions.js";
17
18
  export { MARKER_FILL, MARKER_FILL_ACTIVE, createMarkerSprite, createMarkerSprites, drawMarkerCanvas, projectMarkers, setMarkerSpriteColor, type MarkerColors, type MarkerScreenPosition, type ProjectMarkersOptions } from "./markers.js";
18
19
  export { OCCLUSION_BIAS, createOcclusionIndex, type OcclusionIndex } from "./occlusion.js";