@harpern/edu-modelhub-viewer-core 0.1.0-beta.5 → 0.1.0-beta.6
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 +9 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +7 -1
- package/dist/types.d.ts +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [0.1.0-beta.6] - 2026-10-05
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- `ai_generated` as a `model.source` value (spec 0.3.0). `ModelSource`
|
|
16
|
+
includes it, and the extractor compiled into this package accepts it, so
|
|
17
|
+
loading a model whose source is `ai_generated` no longer produces a
|
|
18
|
+
validation error. `0.1.0-beta.5` reports that value as invalid.
|
|
19
|
+
|
|
11
20
|
## [0.1.0-beta.5] - 2026-09-23
|
|
12
21
|
|
|
13
22
|
### Added
|
package/dist/index.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const h=require("three"),V=new Set(["__proto__","constructor","prototype"]);function z(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 de(e){const t=[];return z(e,(n,r,o)=>{for(const i of Object.keys(n)){const s=r===""?i:`${r}.${i}`;V.has(i)?t.push({path:s,key:i}):o(n[i],s)}}),t}function fe(e){return z(e,(t,n,r)=>{for(const o of Object.keys(t))V.has(o)?delete t[o]:r(t[o],n)}),e}const B=1179937895,D=2,me=1313821514,pe=21;class y extends Error{constructor(t){super(t),this.name="GlbParseError"}}function he(e){return e instanceof ArrayBuffer?new DataView(e):new DataView(e.buffer,e.byteOffset,e.byteLength)}function ge(e){return e===null?"null":Array.isArray(e)?"an array":typeof e}function ye(e){const t=he(e);if(t.byteLength<pe)throw new y(`Input too short to be a GLB file (${t.byteLength} bytes).`);const n=t.getUint32(0,!0);if(n!==B)throw new y(`Invalid GLB magic: expected 0x${B.toString(16)}, got 0x${n.toString(16)}.`);const r=t.getUint32(4,!0);if(r!==D)throw new y(`Unsupported GLB version: expected ${D}, 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!==me)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 ${ge(a)}.`);return a}function be(e){const t=ye(e).extensions;if(t===null||typeof t!="object"||Array.isArray(t))return null;const n=t.EDU_modelhub;return n===void 0?null:fe(n)}const $e=/^[a-z][a-z0-9_]*$/,xe=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/,Ae=/^[a-z]{2}$/,we=/^(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 A(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function x(e){return typeof e=="string"&&e.trim()!==""}function P(e){return typeof e=="number"&&Number.isInteger(e)&&e>=0}function p(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(!A(n))return[d("",`Payload must be a JSON object, got ${p(n)}.`)];Se(n,t),Me(n.specVersion,t),Ie(n.coordinateSpace,t),Re(n.model,t);const r=Oe(n.regions,t);return Ne(n.annotations,r,t),t}function Se(e,t){for(const n of de(e))t.push(d(n.path,`Disallowed key "${n.key}" at ${n.path} (prototype-pollution vector).`))}function Me(e,t){(typeof e!="string"||!we.test(e))&&t.push(d("specVersion",`specVersion must be a semver string (e.g. "0.1.0"), got ${p(e)}.`))}function Ie(e,t){e!==void 0&&e!=="gltf"&&e!=="blender"&&t.push(d("coordinateSpace",`coordinateSpace must be "gltf" or "blender" when present, got ${p(e)}.`))}function Re(e,t){if(!A(e)){t.push(d("model",`model is required and must be an object, got ${p(e)}.`));return}const n=e,r=n.title;x(r)||t.push(d("model.title",`model.title is required and must be a non-empty string, got ${p(r)}.`));const o=n.altText;x(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 ${p(o)}.`));const i=n.language;i!==void 0&&(typeof i!="string"||!Ae.test(i))&&t.push($("model.language",`model.language should be an ISO 639-1 two-letter code (e.g. "en"), got ${p(i)}.`)),Te(n,t),Ee(n.source,t),ve(n,t),x(n.longDescription)||t.push($("model.longDescription","model.longDescription is recommended."))}function Te(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 ${p(r)}.`)),(r==="CC BY"||r==="CC BY-SA")&&!x(e.attribution)&&t.push($("model.attribution",'model.attribution is recommended when license is "CC BY" or "CC BY-SA".'))}function Ee(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 ${p(e)}.`))}}function ve(e,t){for(const n of["created","modified"]){const r=e[n];r!==void 0&&(typeof r!="string"||!xe.test(r))&&t.push($(`model.${n}`,`model.${n} should be ISO 8601 UTC (YYYY-MM-DDTHH:MM:SSZ), got ${p(r)}.`))}}function Oe(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(!A(o)){t.push(d(i,`${i} must be an object, got ${p(o)}.`));continue}_e(o,i,n,t)}return n}function _e(e,t,n,r){const o=e.id;typeof o!="string"||!$e.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 ${p(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 ${p(i)}.`));const s=e.order;s!==void 0&&!P(s)&&r.push($(`${t}.order`,`${t}.order should be a non-negative integer, got ${p(s)}.`))}function Ne(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(!A(i)){n.push(d(s,`${s} must be an object, got ${p(i)}.`));continue}Ce(i,s,r,n)}De(r.seenReadingOrders,n)}function Ce(e,t,n,r){const o=e.id;x(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 ${p(o)}.`));const i=e.regionId;i!==void 0&&i!==""&&(typeof i!="string"||!n.seenRegionIds.has(i))&&r.push(d(`${t}.regionId`,`${t}.regionId ${p(i)} does not match any region id.`)),Le(e.readingOrder,t,n,r),ke(e.anchor,t,r),Be(e.camera,t,r)}function Le(e,t,n,r){e!==void 0&&(P(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 ${p(e)}.`)))}function ke(e,t,n){if(!A(e)){n.push(d(`${t}.anchor`,`${t}.anchor is required and must be an object, got ${p(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"?x(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 ${p(o)}.`))}function Be(e,t,n){if(e===void 0)return;if(!A(e)){n.push(d(`${t}.camera`,`${t}.camera must be an object when present, got ${p(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 ${p(r)}.`))}function De(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 U extends Error{constructor(t){super(t),this.name="AdaptError"}}class Ue{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 U("Sidecar is missing required field: model.title");if(!s)throw new U("Sidecar is missing required field: model.altText");return t}}const Fe=new Ue;function Ve(e){return Fe.adapt(e)}function j(e){return e.filter(t=>t.severity==="error")}function W(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 ze(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 H(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 K(e){const t=be(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 Pe(e,t={}){const n=t.fetchImpl??fetch,r=await H(e,n),o=K(r);if(o.payload)return{...o,bytes:r};if(t.sidecar===!1)return{...o,bytes:r};const s=(t.sidecarUrl??W)(e);if(!s)return{...o,bytes:r};try{const c=await n(s);if(c.ok){const l=Ve(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 Ge(e,t="EDU_modelhub",n=console){if(!(e!=null&&e.length))return;const r=j(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 je(e){return(e==null?void 0:e.name)==="GlbParseError"}function _(e){return Array.isArray(e)&&e.length===3&&e.every(t=>typeof t=="number"&&Number.isFinite(t))}function N(e){return e==="blender"}function w(e,t){if(!_(e))return null;const[n,r,o]=e;return N(t)?[n,o,-r]:[n,r,o]}function We(e,t){if(!_(e))return null;const[n,r,o]=e;return N(t)?[n,-o,r]:[n,r,o]}function q(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=q(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 C(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 He="Auto-created from object ";function Y(e){var n;const t=(n=e==null?void 0:e.description)==null?void 0:n.trim();return t?!t.startsWith(He):!1}function Ke(e){return Array.isArray(e)?e.filter(t=>{var n;return Y(t)||!((n=t.description)!=null&&n.trim())}):[]}function E(e){var t;return e?((t=e.label)==null?void 0:t.trim())||e.id||"Unnamed region":"Unknown region"}function qe(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 Ye(e){const t=e==null?void 0:e.coordinateSpace,n=C(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=w(u.position,t);if(!f)continue;const m=a.camera,g=m?w(m.eye,t):null,b=m?w(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 Ze(e){return e.isMesh===!0}function Xe(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(!Ze(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 Z(e,t,n){for(const r of e.get(t)??[])r.visible=n}function Je(e,t){for(const n of e.keys())Z(e,n,!t.has(n))}function et(e,t,n){for(const r of e.get(t)??[])r.material=n}function tt(e,t){for(const n of e.get(t)??[]){const r=n.userData.modelhubOriginalMaterial;r&&(n.material=r)}}const X="#2196f3",nt="#ff5722";function L(e,t={}){const{fill:n=X,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 Q(e,t=R,n){const r=new h.SpriteMaterial({map:new h.CanvasTexture(L(e,n)),transparent:!0,depthTest:!1}),o=new h.Sprite(r);return o.scale.set(t,t,1),o.renderOrder=999,o}function rt(e,t,n){var o;const r=e.material;(o=r.map)==null||o.dispose(),r.map=new h.CanvasTexture(L(t,{fill:n})),r.needsUpdate=!0}function ot(e,t,n=R,r){return t.map((o,i)=>{const s=Q(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})}const v=Object.freeze({min:.5,max:2,step:.1,default:1}),R=.2,J=.3;function T(e){let t=Number.NaN;if(typeof e=="number"?t=e:typeof e=="string"&&e.trim()!==""&&(t=Number(e)),!Number.isFinite(t))return v.default;const{min:n,max:r,step:o}=v,i=Number((Math.round(t/o)*o).toFixed(6));return Math.min(r,Math.max(n,i))}function it(e){return`${T(e).toFixed(1)} times`}function st(e,t,n={}){const r=T(t),{base:o=R,activeBase:i=J,activeId:s=null}=n;for(const c of e){const a=(s!==null&&c.userData.annotationId===s?i:o)*r;c.scale.set(a,a,1)}}function at(e){const{camera:t,model:n,anchors:r,width:o,height:i,isOccluded:s}=e,c=e.referenceDistance??1,l=new h.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 ee=1.65,te=.8;function ct(e,t,n={}){const r=T(t)*e;return{diameter:(n.diameter??ee)*r,fontSize:(n.fontSize??te)*r}}const ne=.03,ut={enabled:!1,isOccluded:()=>!1,dispose:()=>{}};async function lt(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 ut;for(const s of t)s.boundsTree||(i=s.computeBoundsTree)==null||i.call(s);const r=new h.Raycaster;r.firstHitOnly=!0;const o=new h.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-ne),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 M={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]}]}},re="headlight";function dt(){return Object.keys(M)}function ft(e){var t;(t=e.parent)==null||t.remove(e)}function mt(e){var c;const{scene:t,camera:n,exposure:r=1}=e;(c=e.previous)==null||c.remove();const o=M[e.preset??""]??M[re],i=[],s=new h.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 h.SpotLight(new h.Color(...l.color));f.angle=.5,f.penumbra=.9,f.decay=0,a=f}else a=new h.DirectionalLight(new h.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)ft(l);i.length=0}}}const S=[{id:"white",label:"White",hex:"#ffffff",light:!0},{id:"light-grey",label:"Light gray",hex:"#d4d4d4",light:!0},{id:"medium-grey",label:"Medium gray",hex:"#808080",light:!1},{id:"dark-grey",label:"Dark gray",hex:"#252525",light:!1},{id:"black",label:"Black",hex:"#000000",light:!1}],oe="dark-grey";function ie(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 se(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 pt(e,t){const n=ie(t);return n?{id:"custom",label:"Custom",hex:n,light:se(n)>.5}:S.find(r=>r.id===e)??S.find(r=>r.id===oe)??S[0]}const ae=2;function O(e){e.updateMatrixWorld(!0);const t=new h.Box3,n=new h.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 h.Box3().setFromObject(e):t}function ht(e,t=ae){const r=O(e).getSize(new h.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 h.Vector3)),i}function gt(e,t=1.3){return t/Math.tan(e*Math.PI/360)}const ce=.5,ue=1.5;function yt(e,t,n={}){t.updateMatrixWorld();const r=new h.Vector3(...e.position).applyMatrix4(t.matrixWorld);if(e.camera){const l=new h.Vector3(...e.camera.eye).applyMatrix4(t.matrixWorld),u=new h.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 h.Vector3(r.x,0,r.z);i.lengthSq()<1e-8&&i.set(0,0,1),i.normalize();const s=o?-(n.outwardDistance??ce):n.inwardDistance??ue,c=r.clone().addScaledVector(i,s);return c.y=r.y,{position:c,target:r}}const bt={cancel:()=>{}};function $t(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(),bt;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),k=b*(2-b);t.position.lerpVectors(l,r.position,k),n.target.lerpVectors(a,r.target,k),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 xt(e,t,n){const r=t||n;e.fov!==r&&(e.fov=r,e.updateProjectionMatrix())}const le=500;function At(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 wt(e,t,n){return`Annotation ${t+1} of ${n}: ${e||"Untitled"}.`}function St(e,t=le,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 F(e,t){return`## ${e}
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const h=require("three"),V=new Set(["__proto__","constructor","prototype"]);function z(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 de(e){const t=[];return z(e,(n,r,o)=>{for(const i of Object.keys(n)){const s=r===""?i:`${r}.${i}`;V.has(i)?t.push({path:s,key:i}):o(n[i],s)}}),t}function fe(e){return z(e,(t,n,r)=>{for(const o of Object.keys(t))V.has(o)?delete t[o]:r(t[o],n)}),e}const B=1179937895,D=2,me=1313821514,pe=21;class y extends Error{constructor(t){super(t),this.name="GlbParseError"}}function he(e){return e instanceof ArrayBuffer?new DataView(e):new DataView(e.buffer,e.byteOffset,e.byteLength)}function ge(e){return e===null?"null":Array.isArray(e)?"an array":typeof e}function ye(e){const t=he(e);if(t.byteLength<pe)throw new y(`Input too short to be a GLB file (${t.byteLength} bytes).`);const n=t.getUint32(0,!0);if(n!==B)throw new y(`Invalid GLB magic: expected 0x${B.toString(16)}, got 0x${n.toString(16)}.`);const r=t.getUint32(4,!0);if(r!==D)throw new y(`Unsupported GLB version: expected ${D}, 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!==me)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 ${ge(a)}.`);return a}function be(e){const t=ye(e).extensions;if(t===null||typeof t!="object"||Array.isArray(t))return null;const n=t.EDU_modelhub;return n===void 0?null:fe(n)}const $e=/^[a-z][a-z0-9_]*$/,xe=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/,Ae=/^[a-z]{2}$/,we=/^(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 A(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function x(e){return typeof e=="string"&&e.trim()!==""}function P(e){return typeof e=="number"&&Number.isInteger(e)&&e>=0}function p(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(!A(n))return[d("",`Payload must be a JSON object, got ${p(n)}.`)];Se(n,t),Me(n.specVersion,t),Ie(n.coordinateSpace,t),Re(n.model,t);const r=Oe(n.regions,t);return Ne(n.annotations,r,t),t}function Se(e,t){for(const n of de(e))t.push(d(n.path,`Disallowed key "${n.key}" at ${n.path} (prototype-pollution vector).`))}function Me(e,t){(typeof e!="string"||!we.test(e))&&t.push(d("specVersion",`specVersion must be a semver string (e.g. "0.1.0"), got ${p(e)}.`))}function Ie(e,t){e!==void 0&&e!=="gltf"&&e!=="blender"&&t.push(d("coordinateSpace",`coordinateSpace must be "gltf" or "blender" when present, got ${p(e)}.`))}function Re(e,t){if(!A(e)){t.push(d("model",`model is required and must be an object, got ${p(e)}.`));return}const n=e,r=n.title;x(r)||t.push(d("model.title",`model.title is required and must be a non-empty string, got ${p(r)}.`));const o=n.altText;x(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 ${p(o)}.`));const i=n.language;i!==void 0&&(typeof i!="string"||!Ae.test(i))&&t.push($("model.language",`model.language should be an ISO 639-1 two-letter code (e.g. "en"), got ${p(i)}.`)),Te(n,t),Ee(n.source,t),ve(n,t),x(n.longDescription)||t.push($("model.longDescription","model.longDescription is recommended."))}function Te(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 ${p(r)}.`)),(r==="CC BY"||r==="CC BY-SA")&&!x(e.attribution)&&t.push($("model.attribution",'model.attribution is recommended when license is "CC BY" or "CC BY-SA".'))}function Ee(e,t){if(e===void 0)return;const n=["scan","photogrammetry","manual","ai_generated","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 ${p(e)}.`))}}function ve(e,t){for(const n of["created","modified"]){const r=e[n];r!==void 0&&(typeof r!="string"||!xe.test(r))&&t.push($(`model.${n}`,`model.${n} should be ISO 8601 UTC (YYYY-MM-DDTHH:MM:SSZ), got ${p(r)}.`))}}function Oe(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(!A(o)){t.push(d(i,`${i} must be an object, got ${p(o)}.`));continue}_e(o,i,n,t)}return n}function _e(e,t,n,r){const o=e.id;typeof o!="string"||!$e.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 ${p(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 ${p(i)}.`));const s=e.order;s!==void 0&&!P(s)&&r.push($(`${t}.order`,`${t}.order should be a non-negative integer, got ${p(s)}.`))}function Ne(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(!A(i)){n.push(d(s,`${s} must be an object, got ${p(i)}.`));continue}Ce(i,s,r,n)}De(r.seenReadingOrders,n)}function Ce(e,t,n,r){const o=e.id;x(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 ${p(o)}.`));const i=e.regionId;i!==void 0&&i!==""&&(typeof i!="string"||!n.seenRegionIds.has(i))&&r.push(d(`${t}.regionId`,`${t}.regionId ${p(i)} does not match any region id.`)),Le(e.readingOrder,t,n,r),ke(e.anchor,t,r),Be(e.camera,t,r)}function Le(e,t,n,r){e!==void 0&&(P(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 ${p(e)}.`)))}function ke(e,t,n){if(!A(e)){n.push(d(`${t}.anchor`,`${t}.anchor is required and must be an object, got ${p(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"?x(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 ${p(o)}.`))}function Be(e,t,n){if(e===void 0)return;if(!A(e)){n.push(d(`${t}.camera`,`${t}.camera must be an object when present, got ${p(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 ${p(r)}.`))}function De(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 U extends Error{constructor(t){super(t),this.name="AdaptError"}}class Ue{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 U("Sidecar is missing required field: model.title");if(!s)throw new U("Sidecar is missing required field: model.altText");return t}}const Fe=new Ue;function Ve(e){return Fe.adapt(e)}function j(e){return e.filter(t=>t.severity==="error")}function W(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 ze(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 H(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 K(e){const t=be(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 Pe(e,t={}){const n=t.fetchImpl??fetch,r=await H(e,n),o=K(r);if(o.payload)return{...o,bytes:r};if(t.sidecar===!1)return{...o,bytes:r};const s=(t.sidecarUrl??W)(e);if(!s)return{...o,bytes:r};try{const c=await n(s);if(c.ok){const l=Ve(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 Ge(e,t="EDU_modelhub",n=console){if(!(e!=null&&e.length))return;const r=j(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 je(e){return(e==null?void 0:e.name)==="GlbParseError"}function _(e){return Array.isArray(e)&&e.length===3&&e.every(t=>typeof t=="number"&&Number.isFinite(t))}function N(e){return e==="blender"}function w(e,t){if(!_(e))return null;const[n,r,o]=e;return N(t)?[n,o,-r]:[n,r,o]}function We(e,t){if(!_(e))return null;const[n,r,o]=e;return N(t)?[n,-o,r]:[n,r,o]}function q(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=q(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 C(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 He="Auto-created from object ";function Y(e){var n;const t=(n=e==null?void 0:e.description)==null?void 0:n.trim();return t?!t.startsWith(He):!1}function Ke(e){return Array.isArray(e)?e.filter(t=>{var n;return Y(t)||!((n=t.description)!=null&&n.trim())}):[]}function E(e){var t;return e?((t=e.label)==null?void 0:t.trim())||e.id||"Unnamed region":"Unknown region"}function qe(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 Ye(e){const t=e==null?void 0:e.coordinateSpace,n=C(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=w(u.position,t);if(!f)continue;const m=a.camera,g=m?w(m.eye,t):null,b=m?w(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 Ze(e){return e.isMesh===!0}function Xe(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(!Ze(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 Z(e,t,n){for(const r of e.get(t)??[])r.visible=n}function Je(e,t){for(const n of e.keys())Z(e,n,!t.has(n))}function et(e,t,n){for(const r of e.get(t)??[])r.material=n}function tt(e,t){for(const n of e.get(t)??[]){const r=n.userData.modelhubOriginalMaterial;r&&(n.material=r)}}const X="#2196f3",nt="#ff5722";function L(e,t={}){const{fill:n=X,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 Q(e,t=R,n){const r=new h.SpriteMaterial({map:new h.CanvasTexture(L(e,n)),transparent:!0,depthTest:!1}),o=new h.Sprite(r);return o.scale.set(t,t,1),o.renderOrder=999,o}function rt(e,t,n){var o;const r=e.material;(o=r.map)==null||o.dispose(),r.map=new h.CanvasTexture(L(t,{fill:n})),r.needsUpdate=!0}function ot(e,t,n=R,r){return t.map((o,i)=>{const s=Q(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})}const v=Object.freeze({min:.5,max:2,step:.1,default:1}),R=.2,J=.3;function T(e){let t=Number.NaN;if(typeof e=="number"?t=e:typeof e=="string"&&e.trim()!==""&&(t=Number(e)),!Number.isFinite(t))return v.default;const{min:n,max:r,step:o}=v,i=Number((Math.round(t/o)*o).toFixed(6));return Math.min(r,Math.max(n,i))}function it(e){return`${T(e).toFixed(1)} times`}function st(e,t,n={}){const r=T(t),{base:o=R,activeBase:i=J,activeId:s=null}=n;for(const c of e){const a=(s!==null&&c.userData.annotationId===s?i:o)*r;c.scale.set(a,a,1)}}function at(e){const{camera:t,model:n,anchors:r,width:o,height:i,isOccluded:s}=e,c=e.referenceDistance??1,l=new h.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 ee=1.65,te=.8;function ct(e,t,n={}){const r=T(t)*e;return{diameter:(n.diameter??ee)*r,fontSize:(n.fontSize??te)*r}}const ne=.03,ut={enabled:!1,isOccluded:()=>!1,dispose:()=>{}};async function lt(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 ut;for(const s of t)s.boundsTree||(i=s.computeBoundsTree)==null||i.call(s);const r=new h.Raycaster;r.firstHitOnly=!0;const o=new h.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-ne),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 M={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]}]}},re="headlight";function dt(){return Object.keys(M)}function ft(e){var t;(t=e.parent)==null||t.remove(e)}function mt(e){var c;const{scene:t,camera:n,exposure:r=1}=e;(c=e.previous)==null||c.remove();const o=M[e.preset??""]??M[re],i=[],s=new h.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 h.SpotLight(new h.Color(...l.color));f.angle=.5,f.penumbra=.9,f.decay=0,a=f}else a=new h.DirectionalLight(new h.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)ft(l);i.length=0}}}const S=[{id:"white",label:"White",hex:"#ffffff",light:!0},{id:"light-grey",label:"Light gray",hex:"#d4d4d4",light:!0},{id:"medium-grey",label:"Medium gray",hex:"#808080",light:!1},{id:"dark-grey",label:"Dark gray",hex:"#252525",light:!1},{id:"black",label:"Black",hex:"#000000",light:!1}],oe="dark-grey";function ie(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 se(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 pt(e,t){const n=ie(t);return n?{id:"custom",label:"Custom",hex:n,light:se(n)>.5}:S.find(r=>r.id===e)??S.find(r=>r.id===oe)??S[0]}const ae=2;function O(e){e.updateMatrixWorld(!0);const t=new h.Box3,n=new h.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 h.Box3().setFromObject(e):t}function ht(e,t=ae){const r=O(e).getSize(new h.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 h.Vector3)),i}function gt(e,t=1.3){return t/Math.tan(e*Math.PI/360)}const ce=.5,ue=1.5;function yt(e,t,n={}){t.updateMatrixWorld();const r=new h.Vector3(...e.position).applyMatrix4(t.matrixWorld);if(e.camera){const l=new h.Vector3(...e.camera.eye).applyMatrix4(t.matrixWorld),u=new h.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 h.Vector3(r.x,0,r.z);i.lengthSq()<1e-8&&i.set(0,0,1),i.normalize();const s=o?-(n.outwardDistance??ce):n.inwardDistance??ue,c=r.clone().addScaledVector(i,s);return c.y=r.y,{position:c,target:r}}const bt={cancel:()=>{}};function $t(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(),bt;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),k=b*(2-b);t.position.lerpVectors(l,r.position,k),n.target.lerpVectors(a,r.target,k),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 xt(e,t,n){const r=t||n;e.fov!==r&&(e.fov=r,e.updateProjectionMatrix())}const le=500;function At(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 wt(e,t,n){return`Annotation ${t+1} of ${n}: ${e||"Untitled"}.`}function St(e,t=le,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 F(e,t){return`## ${e}
|
|
2
2
|
${t}
|
|
3
3
|
|
|
4
4
|
`}function Mt(e,t={}){const n=e.model??{},r=C(e),o=I(e);let i=`# ${n.title||"Untitled model"}
|
package/dist/index.js
CHANGED
|
@@ -228,7 +228,13 @@ function he(e, t) {
|
|
|
228
228
|
}
|
|
229
229
|
function ge(e, t) {
|
|
230
230
|
if (e === void 0) return;
|
|
231
|
-
const n = [
|
|
231
|
+
const n = [
|
|
232
|
+
"scan",
|
|
233
|
+
"photogrammetry",
|
|
234
|
+
"manual",
|
|
235
|
+
"ai_generated",
|
|
236
|
+
"other"
|
|
237
|
+
];
|
|
232
238
|
if (typeof e != "string" || !n.includes(e)) {
|
|
233
239
|
const o = n.map((r) => `"${r}"`).join(", ");
|
|
234
240
|
t.push(
|
package/dist/types.d.ts
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
/** Usage license for the model. */
|
|
18
18
|
export type ModelLicense = "CC0" | "CC BY" | "CC BY-SA" | "All Rights Reserved";
|
|
19
19
|
/** How the model was created. */
|
|
20
|
-
export type ModelSource = "scan" | "photogrammetry" | "manual" | "other";
|
|
20
|
+
export type ModelSource = "scan" | "photogrammetry" | "manual" | "ai_generated" | "other";
|
|
21
21
|
/** Whether a region is visible when the model first loads. */
|
|
22
22
|
export type VisibilityDefault = "shown" | "hidden";
|
|
23
23
|
/** Anchor type for an annotation. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@harpern/edu-modelhub-viewer-core",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.6",
|
|
4
4
|
"description": "Framework-agnostic viewer core for EDU_modelhub models: metadata loading, normalization, region binding, annotation markers, and camera behavior shared by every conforming viewer",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Oregon State University Ecampus",
|