@popmelt.com/core 0.17.0 → 0.18.1

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/README.md CHANGED
@@ -243,6 +243,71 @@ If the toolbar opens but cannot reach an AI, check that:
243
243
  - The framework plugin or config wrapper is installed
244
244
  - The development server was restarted after installation
245
245
 
246
+ ## React Three Fiber
247
+
248
+ Popmelt can target objects rendered by React Three Fiber while Core continues to
249
+ own the familiar hover, annotation, and thread presentation. Install the R3F
250
+ peers alongside Core:
251
+
252
+ ```bash
253
+ npm install @popmelt.com/core @react-three/fiber three
254
+ ```
255
+
256
+ Keep `PopmeltProvider` outside the canvas and mount `PopmeltThreeAdapter` inside
257
+ it. The adapter renders nothing; it registers the current scene, camera, and
258
+ renderer with the nearest provider.
259
+
260
+ ```tsx
261
+ import { Canvas } from '@react-three/fiber';
262
+ import { PopmeltProvider } from '@popmelt.com/core';
263
+ import { PopmeltThreeAdapter } from '@popmelt.com/core/react-three-fiber';
264
+
265
+ export function ProductPreview() {
266
+ return (
267
+ <PopmeltProvider>
268
+ <Canvas>
269
+ <PopmeltThreeAdapter />
270
+ <ProductScene />
271
+ </Canvas>
272
+ </PopmeltProvider>
273
+ );
274
+ }
275
+ ```
276
+
277
+ Named objects receive readable labels automatically. Add stable application
278
+ identity when object names or scene ordering may change:
279
+
280
+ ```tsx
281
+ <mesh
282
+ name="Bottle"
283
+ userData={{ popmeltId: 'hero-bottle', popmeltLabel: 'Hero bottle' }}
284
+ >
285
+ {/* geometry and material */}
286
+ </mesh>
287
+ ```
288
+
289
+ Use a distinct `id` for each adapter when a page owns multiple canvases. Pass
290
+ `root` to target only one scene subtree, `captureFrame={false}` to omit renderer
291
+ screenshots, or `select`, `key`, `label`, and `describe` to adapt an application's
292
+ scene semantics.
293
+
294
+ For integrations that need the registered adapter instance, use the hook inside
295
+ the canvas instead of the component:
296
+
297
+ ```tsx
298
+ import { usePopmeltThreeAdapter } from '@popmelt.com/core/react-three-fiber';
299
+
300
+ function ProductPopmeltAdapter() {
301
+ const adapter = usePopmeltThreeAdapter({ id: 'product-preview' });
302
+ // Optional application-specific coordination with adapter.
303
+ void adapter;
304
+ return null;
305
+ }
306
+ ```
307
+
308
+ The component is the recommended default; the hook exposes the same lifecycle
309
+ for advanced composition.
310
+
246
311
  ## Media annotation
247
312
 
248
313
  Manual image and video annotation are enabled by default in `PopmeltProvider`,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "popmelt-core",
3
- "version": "0.17.0",
3
+ "version": "0.18.1",
4
4
  "description": "Ambient project memory and shared understanding for Codex",
5
5
  "author": {
6
6
  "name": "Popmelt",
@@ -0,0 +1,36 @@
1
+ import { Camera, Object3D, Raycaster } from 'three';
2
+ import { P as PopmeltSubjectAdapter } from './subjects-CeqtFged.mjs';
3
+
4
+ type ThreeSubjectAdapterOptions = {
5
+ /** Unique adapter identity when a page owns more than one Three.js surface. */
6
+ id?: string;
7
+ canvas: HTMLCanvasElement;
8
+ camera: Camera;
9
+ scene: Object3D;
10
+ raycaster?: Raycaster;
11
+ /** Select a meaningful parent from the raw intersected render object. */
12
+ select?: (object: Object3D) => Object3D | null;
13
+ /** Override durable identity. Explicit application IDs are preferred. */
14
+ key?: (object: Object3D) => string | null | undefined;
15
+ label?: (object: Object3D) => string;
16
+ /** Add application-specific provenance or source hints to the model context. */
17
+ describe?: (object: Object3D) => Record<string, string | undefined>;
18
+ /** Capture the renderer's current frame for DOM-cloned screenshots. */
19
+ captureFrame?: () => string | null | Promise<string | null>;
20
+ };
21
+
22
+ type PopmeltThreeAdapterOptions = Omit<ThreeSubjectAdapterOptions, 'canvas' | 'camera' | 'scene' | 'captureFrame'> & {
23
+ /** Scene root Popmelt should target. Defaults to the complete R3F scene. */
24
+ root?: Object3D;
25
+ /** Override frame capture, or set false to omit renderer screenshots. */
26
+ captureFrame?: ThreeSubjectAdapterOptions['captureFrame'] | false;
27
+ };
28
+ /**
29
+ * Register an R3F-owned Three.js scene with the nearest PopmeltProvider.
30
+ * Must run inside Canvas so React Three Fiber's renderer state is available.
31
+ */
32
+ declare function usePopmeltThreeAdapter(options?: PopmeltThreeAdapterOptions): PopmeltSubjectAdapter;
33
+ /** Declarative, zero-render integration for use inside an R3F Canvas. */
34
+ declare function PopmeltThreeAdapter(props: PopmeltThreeAdapterOptions): null;
35
+
36
+ export { PopmeltThreeAdapter, type PopmeltThreeAdapterOptions, usePopmeltThreeAdapter };
@@ -0,0 +1,2 @@
1
+ "use client";
2
+ "use client";import{b as g}from"./chunk-J3UIBBU2.mjs";import{useThree as M}from"@react-three/fiber";import{useEffect as C,useMemo as E}from"react";import{Box3 as x,Raycaster as D,Vector2 as T,Vector3 as P}from"three";function h(e){return[e.x,e.y,e.z].map(r=>Number(r.toFixed(4))).join(", ")}function y(e){let r=e.material;return r&&(Array.isArray(r)?r:[r]).map(n=>n.name?.trim()||n.type).filter(Boolean).join(", ")||void 0}function j(e,r,s){let c=e.domElement.width,n=e.domElement.height;if(c<=0||n<=0)return null;let l=e.getRenderTarget(),t=e.xr.enabled;try{return e.xr.enabled=!1,e.setRenderTarget(null),e.render(r,s),e.domElement.toDataURL("image/png")}catch{return null}finally{e.setRenderTarget(l),e.xr.enabled=t}}function A(e){let c=(e.parent?.children??[]).filter(t=>t.type===e.type&&t.name===e.name),n=Math.max(0,c.indexOf(e)),l=e.name.trim()||e.type;return`${encodeURIComponent(l)}[${n}]`}function f(e){let r=[],s=e;for(;s?.parent;)r.push(A(s)),s=s.parent;return r.reverse().join("/")}function v(e){let r=e.userData.popmeltId;return typeof r=="string"&&r.trim()?r.trim():f(e)}function S(e){let r=e.userData.popmeltLabel;return typeof r=="string"&&r.trim()?r.trim():e.name.trim()||e.type}function F(e){let r=e;for(;r.parent;){let s=r.name.trim(),c=s.match(/^(.*)_(?:alpha|normal|material)(?:_\d+)+$/i),n=r.parent;for(;n.parent&&!n.name.trim();)n=n.parent;let l=n.name.trim();if(!l||!s.startsWith(`${l}_`)&&!c)break;r=n}return r}function R(e,r,s){let c=new x().setFromObject(e,!0);if(c.isEmpty())return null;let n=s.getBoundingClientRect(),l=[];for(let p of[c.min.x,c.max.x])for(let d of[c.min.y,c.max.y])for(let b of[c.min.z,c.max.z])l.push(new P(p,d,b).project(r));let t=l.map(p=>n.left+(p.x+1)/2*n.width),a=l.map(p=>n.top+(1-p.y)/2*n.height),i=Math.max(n.left,Math.min(...t)),o=Math.min(n.right,Math.max(...t)),m=Math.max(n.top,Math.min(...a)),u=Math.min(n.bottom,Math.max(...a));return o<=i||u<=m?null:{left:i,top:m,width:o-i,height:u-m}}function O(e){let r=e.raycaster??new D,s=new T,c=t=>e.key?.(t)??v(t),n=(t,a)=>{let i=c(t);if(!i)return null;let o=R(t,e.camera,e.canvas);return o?{key:i,label:e.label?.(t)??S(t),sourceElement:e.canvas,bounds:o,context:f(t),data:{renderer:"Three.js",objectType:t.type,...t.name?{objectName:t.name}:{},scenePath:f(t),...a&&a!==t?{renderObjectName:a.name.trim()||a.type,renderObjectType:a.type,renderScenePath:f(a)}:{},position:h(t.position),rotation:h(t.rotation),scale:h(t.scale),visible:String(t.visible),...y(t)?{material:y(t)}:{},...Object.fromEntries(Object.entries(e.describe?.(t)??{}).filter(m=>typeof m[1]=="string"&&m[1].length>0))}}:null},l=t=>{let a=null;return e.scene.traverse(i=>{!a&&c(i)===t&&(a=i)}),a};return{id:e.id?.trim()||"three",hitTest({x:t,y:a}){let i=e.canvas.getBoundingClientRect();if(t<i.left||t>i.right||a<i.top||a>i.bottom)return null;s.set((t-i.left)/i.width*2-1,-((a-i.top)/i.height)*2+1),r.setFromCamera(s,e.camera);for(let o of r.intersectObject(e.scene,!0)){let m=e.select?e.select(o.object):F(o.object);if(!m||!m.visible)continue;let u=n(m,o.object);if(u)return u}return null},resolve(t){let a=l(t);return a?n(a):null},...e.captureFrame?{async captureSurfaces(){let t=await e.captureFrame?.();return t?[{element:e.canvas,dataUrl:t}]:[]}}:{}}}function w(e={}){let{registerSubjectAdapter:r}=g(),{gl:s,camera:c,scene:n}=M(),{root:l=n,captureFrame:t,describe:a,id:i,key:o,label:m,raycaster:u,select:p}=e,d=E(()=>O({id:i,canvas:s.domElement,camera:c,scene:l,raycaster:u,select:p,key:o,label:m,describe:b=>({framework:"@react-three/fiber",...a?.(b)}),...t===!1?{}:{captureFrame:t??(()=>j(s,n,c))}}),[c,t,a,s,i,o,m,u,l,n,p]);return C(()=>r(d),[d,r]),d}function U(e){return w(e),null}export{U as PopmeltThreeAdapter,w as usePopmeltThreeAdapter};
@@ -152,6 +152,8 @@ type PopmeltSubjectAdapter = {
152
152
  };
153
153
 
154
154
  type ThreeSubjectAdapterOptions = {
155
+ /** Unique adapter identity when a page owns more than one Three.js surface. */
156
+ id?: string;
155
157
  canvas: HTMLCanvasElement;
156
158
  camera: Camera;
157
159
  scene: Object3D;
@@ -1 +1 @@
1
- import{Box3 as y,Raycaster as x,Vector2 as O,Vector3 as j}from"three";function b(e){return[e.x,e.y,e.z].map(n=>Number(n.toFixed(4))).join(", ")}function h(e){let n=e.material;return n&&(Array.isArray(n)?n:[n]).map(r=>r.name?.trim()||r.type).filter(Boolean).join(", ")||void 0}function M(e,n,c){let s=e.domElement.width,r=e.domElement.height;if(s<=0||r<=0)return null;let l=e.getRenderTarget(),t=e.xr.enabled;try{return e.xr.enabled=!1,e.setRenderTarget(null),e.render(n,c),e.domElement.toDataURL("image/png")}catch{return null}finally{e.setRenderTarget(l),e.xr.enabled=t}}function D(e){let s=(e.parent?.children??[]).filter(t=>t.type===e.type&&t.name===e.name),r=Math.max(0,s.indexOf(e)),l=e.name.trim()||e.type;return`${encodeURIComponent(l)}[${r}]`}function f(e){let n=[],c=e;for(;c?.parent;)n.push(D(c)),c=c.parent;return n.reverse().join("/")}function v(e){let n=e.userData.popmeltId;return typeof n=="string"&&n.trim()?n.trim():f(e)}function T(e){let n=e.userData.popmeltLabel;return typeof n=="string"&&n.trim()?n.trim():e.name.trim()||e.type}function R(e){let n=e;for(;n.parent;){let c=n.name.trim(),s=c.match(/^(.*)_(?:alpha|normal|material)(?:_\d+)+$/i),r=n.parent;for(;r.parent&&!r.name.trim();)r=r.parent;let l=r.name.trim();if(!l||!c.startsWith(`${l}_`)&&!s)break;n=r}return n}function P(e,n,c){let s=new y().setFromObject(e,!0);if(s.isEmpty())return null;let r=c.getBoundingClientRect(),l=[];for(let p of[s.min.x,s.max.x])for(let d of[s.min.y,s.max.y])for(let g of[s.min.z,s.max.z])l.push(new j(p,d,g).project(n));let t=l.map(p=>r.left+(p.x+1)/2*r.width),a=l.map(p=>r.top+(1-p.y)/2*r.height),i=Math.max(r.left,Math.min(...t)),o=Math.min(r.right,Math.max(...t)),m=Math.max(r.top,Math.min(...a)),u=Math.min(r.bottom,Math.max(...a));return o<=i||u<=m?null:{left:i,top:m,width:o-i,height:u-m}}function C(e){let n=e.raycaster??new x,c=new O,s=t=>e.key?.(t)??v(t),r=(t,a)=>{let i=s(t);if(!i)return null;let o=P(t,e.camera,e.canvas);return o?{key:i,label:e.label?.(t)??T(t),bounds:o,context:f(t),data:{renderer:"Three.js",objectType:t.type,...t.name?{objectName:t.name}:{},scenePath:f(t),...a&&a!==t?{renderObjectName:a.name.trim()||a.type,renderObjectType:a.type,renderScenePath:f(a)}:{},position:b(t.position),rotation:b(t.rotation),scale:b(t.scale),visible:String(t.visible),...h(t)?{material:h(t)}:{},...Object.fromEntries(Object.entries(e.describe?.(t)??{}).filter(m=>typeof m[1]=="string"&&m[1].length>0))}}:null},l=t=>{let a=null;return e.scene.traverse(i=>{!a&&s(i)===t&&(a=i)}),a};return{id:"three",hitTest({x:t,y:a}){let i=e.canvas.getBoundingClientRect();if(t<i.left||t>i.right||a<i.top||a>i.bottom)return null;c.set((t-i.left)/i.width*2-1,-((a-i.top)/i.height)*2+1),n.setFromCamera(c,e.camera);for(let o of n.intersectObject(e.scene,!0)){let m=e.select?e.select(o.object):R(o.object);if(!m||!m.visible)continue;let u=r(m,o.object);if(u)return u}return null},resolve(t){let a=l(t);return a?r(a):null},...e.captureFrame?{async captureSurfaces(){let t=await e.captureFrame?.();return t?[{element:e.canvas,dataUrl:t}]:[]}}:{}}}export{M as captureThreeRendererFrame,C as createThreeSubjectAdapter,R as selectThreeNamedPrimitiveParent,f as threeObjectPath};
1
+ import{Box3 as y,Raycaster as x,Vector2 as O,Vector3 as j}from"three";function b(e){return[e.x,e.y,e.z].map(n=>Number(n.toFixed(4))).join(", ")}function d(e){let n=e.material;return n&&(Array.isArray(n)?n:[n]).map(r=>r.name?.trim()||r.type).filter(Boolean).join(", ")||void 0}function M(e,n,c){let s=e.domElement.width,r=e.domElement.height;if(s<=0||r<=0)return null;let l=e.getRenderTarget(),t=e.xr.enabled;try{return e.xr.enabled=!1,e.setRenderTarget(null),e.render(n,c),e.domElement.toDataURL("image/png")}catch{return null}finally{e.setRenderTarget(l),e.xr.enabled=t}}function D(e){let s=(e.parent?.children??[]).filter(t=>t.type===e.type&&t.name===e.name),r=Math.max(0,s.indexOf(e)),l=e.name.trim()||e.type;return`${encodeURIComponent(l)}[${r}]`}function f(e){let n=[],c=e;for(;c?.parent;)n.push(D(c)),c=c.parent;return n.reverse().join("/")}function v(e){let n=e.userData.popmeltId;return typeof n=="string"&&n.trim()?n.trim():f(e)}function T(e){let n=e.userData.popmeltLabel;return typeof n=="string"&&n.trim()?n.trim():e.name.trim()||e.type}function R(e){let n=e;for(;n.parent;){let c=n.name.trim(),s=c.match(/^(.*)_(?:alpha|normal|material)(?:_\d+)+$/i),r=n.parent;for(;r.parent&&!r.name.trim();)r=r.parent;let l=r.name.trim();if(!l||!c.startsWith(`${l}_`)&&!s)break;n=r}return n}function P(e,n,c){let s=new y().setFromObject(e,!0);if(s.isEmpty())return null;let r=c.getBoundingClientRect(),l=[];for(let p of[s.min.x,s.max.x])for(let h of[s.min.y,s.max.y])for(let g of[s.min.z,s.max.z])l.push(new j(p,h,g).project(n));let t=l.map(p=>r.left+(p.x+1)/2*r.width),a=l.map(p=>r.top+(1-p.y)/2*r.height),i=Math.max(r.left,Math.min(...t)),u=Math.min(r.right,Math.max(...t)),m=Math.max(r.top,Math.min(...a)),o=Math.min(r.bottom,Math.max(...a));return u<=i||o<=m?null:{left:i,top:m,width:u-i,height:o-m}}function C(e){let n=e.raycaster??new x,c=new O,s=t=>e.key?.(t)??v(t),r=(t,a)=>{let i=s(t);if(!i)return null;let u=P(t,e.camera,e.canvas);return u?{key:i,label:e.label?.(t)??T(t),sourceElement:e.canvas,bounds:u,context:f(t),data:{renderer:"Three.js",objectType:t.type,...t.name?{objectName:t.name}:{},scenePath:f(t),...a&&a!==t?{renderObjectName:a.name.trim()||a.type,renderObjectType:a.type,renderScenePath:f(a)}:{},position:b(t.position),rotation:b(t.rotation),scale:b(t.scale),visible:String(t.visible),...d(t)?{material:d(t)}:{},...Object.fromEntries(Object.entries(e.describe?.(t)??{}).filter(m=>typeof m[1]=="string"&&m[1].length>0))}}:null},l=t=>{let a=null;return e.scene.traverse(i=>{!a&&s(i)===t&&(a=i)}),a};return{id:e.id?.trim()||"three",hitTest({x:t,y:a}){let i=e.canvas.getBoundingClientRect();if(t<i.left||t>i.right||a<i.top||a>i.bottom)return null;c.set((t-i.left)/i.width*2-1,-((a-i.top)/i.height)*2+1),n.setFromCamera(c,e.camera);for(let u of n.intersectObject(e.scene,!0)){let m=e.select?e.select(u.object):R(u.object);if(!m||!m.visible)continue;let o=r(m,u.object);if(o)return o}return null},resolve(t){let a=l(t);return a?r(a):null},...e.captureFrame?{async captureSurfaces(){let t=await e.captureFrame?.();return t?[{element:e.canvas,dataUrl:t}]:[]}}:{}}}export{M as captureThreeRendererFrame,C as createThreeSubjectAdapter,R as selectThreeNamedPrimitiveParent,f as threeObjectPath};