@liquidcars/atlas 0.1.26 → 0.1.28

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
@@ -180,8 +180,8 @@ El modelo configura cada instancia sin conocer la implementación del paquete:
180
180
  Cada definición puede declarar parámetros tipados, una paleta local,
181
181
  proporciones canónicas y anchors. Las piezas declarativas admiten primitivas,
182
182
  posición, rotación, escala, materiales, iconos de texto o emoji e imágenes por
183
- URL/data URI. Para planos con imagen, `imageFit` admite `cover`, `contain` y
184
- `stretch`. El renderer mantiene Three.js encapsulado para evitar que los packs
183
+ URL/data URI; los SVG también pueden proporcionarse inline. Para planos con
184
+ imagen, `imageFit` admite `cover`, `contain` y `stretch`. El renderer mantiene Three.js encapsulado para evitar que los packs
185
185
  dependan de su versión interna. Las relaciones parten del centro por defecto;
186
186
  `fromAnchor` y `toAnchor` quedan como overrides explícitos para casos
187
187
  excepcionales.
@@ -222,12 +222,15 @@ Una vez publicado, los consumidores podrán instalarlo con `npm install @liquidc
222
222
  Para probar el paquete sin publicarlo, basta con instalar el tarball generado:
223
223
 
224
224
  ```bash
225
- npm install ./liquidcars-atlas-0.1.26.tgz
225
+ npm install ./liquidcars-atlas-0.1.28.tgz
226
226
  ```
227
227
 
228
228
  ## Estado
229
229
 
230
- `0.1.26` mantiene el centro como anclaje implícito de toda relación y utiliza
230
+ `0.1.28` rasteriza explícitamente los SVG sobre un canvas antes de subirlos a
231
+ WebGL y emite diagnósticos cuando falla la carga o la conversión. `0.1.27` añade carga automática de imágenes SVG remotas, SVG mediante data URI
232
+ y SVG inline, manteniendo las mismas opciones de cara y ajuste que las imágenes
233
+ raster. `0.1.26` mantiene el centro como anclaje implícito de toda relación y utiliza
231
234
  los puertos de geometrías externas únicamente cuando el modelo los solicita.
232
235
  También evita recorrer los materiales de geometrías compuestas en cada frame,
233
236
  refuerza la liberación de recursos y expone `getRenderStats()`.
package/demo/index.html CHANGED
@@ -14,12 +14,12 @@
14
14
  liquidcars-atlas { display: block; width: 100%; max-width: 1480px; margin: 0 auto; }
15
15
  code { color: #85e5d3; }
16
16
  </style>
17
- <script type="module" src="../dist/liquidcars-atlas.js?v=0.1.26"></script>
17
+ <script type="module" src="../dist/liquidcars-atlas.js?v=0.1.28"></script>
18
18
  </head>
19
19
  <body>
20
20
  <header>
21
21
  <h1>LiquidCars Atlas</h1>
22
- <p>Prueba de distribución del componente <code>&lt;liquidcars-atlas&gt;</code> · v0.1.26</p>
22
+ <p>Prueba de distribución del componente <code>&lt;liquidcars-atlas&gt;</code> · v0.1.28</p>
23
23
  </header>
24
24
  <main>
25
25
  <liquidcars-atlas id="atlas" height="720" relation-mode="selected" shell-labels="on"></liquidcars-atlas>
@@ -1,4 +1,4 @@
1
- /*! LiquidCars Atlas 0.1.26 | MIT */
1
+ /*! LiquidCars Atlas 0.1.28 | MIT */
2
2
 
3
3
  // src/three.core.js
4
4
  /**
@@ -29615,7 +29615,7 @@ function partGeometry(part) {
29615
29615
  if (part.shape === "plane") return new PlaneGeometry(1, 1);
29616
29616
  return makeGeometry(part.shape || part.geometry || "box", part.radius, part.segments, part.size);
29617
29617
  }
29618
- function createGeometryContent(definition, entity, modelPalette, invalidate) {
29618
+ function createGeometryContent(definition, entity, modelPalette, invalidate, reportWarning) {
29619
29619
  const parameters = resolveGeometryParameters(entity, definition);
29620
29620
  const localPalette = {
29621
29621
  ...modelPalette,
@@ -29650,13 +29650,41 @@ function createGeometryContent(definition, entity, modelPalette, invalidate) {
29650
29650
  bitmap.crossOrigin = "anonymous";
29651
29651
  material.map = new Texture(bitmap);
29652
29652
  let cancelled = false;
29653
+ let objectUrl = null;
29654
+ let svgSource = /^\s*<svg(?:\s|>)/i.test(image) || /^data:image\/svg\+xml/i.test(image) || /\.svg(?:[?#]|$)/i.test(image);
29655
+ const abortController = new AbortController();
29656
+ const releaseObjectUrl = () => {
29657
+ if (!objectUrl) return;
29658
+ URL.revokeObjectURL(objectUrl);
29659
+ objectUrl = null;
29660
+ };
29653
29661
  const removeListeners = () => {
29654
29662
  bitmap.removeEventListener("load", onLoad);
29655
29663
  bitmap.removeEventListener("error", onError);
29656
29664
  };
29657
29665
  const onLoad = () => {
29658
29666
  removeListeners();
29659
- if (cancelled || !material.map) return;
29667
+ if (cancelled || !material.map) {
29668
+ releaseObjectUrl();
29669
+ return;
29670
+ }
29671
+ if (svgSource) {
29672
+ try {
29673
+ const aspect = (bitmap.naturalWidth || 1) / Math.max(1, bitmap.naturalHeight || 1);
29674
+ const longestSide = 1024;
29675
+ const canvas = document.createElement("canvas");
29676
+ canvas.width = aspect >= 1 ? longestSide : Math.max(1, Math.round(longestSide * aspect));
29677
+ canvas.height = aspect >= 1 ? Math.max(1, Math.round(longestSide / aspect)) : longestSide;
29678
+ const context = canvas.getContext("2d");
29679
+ if (!context) throw new Error("Canvas 2D is unavailable");
29680
+ context.clearRect(0, 0, canvas.width, canvas.height);
29681
+ context.drawImage(bitmap, 0, 0, canvas.width, canvas.height);
29682
+ material.map.image = canvas;
29683
+ } catch (error) {
29684
+ reportWarning?.({ code: "SVG_RASTERIZE_FAILED", geometry: definition.id, entityId: entity.id, image, error });
29685
+ }
29686
+ }
29687
+ releaseObjectUrl();
29660
29688
  const fit = part.imageFit || part.fit || "stretch";
29661
29689
  const imageAspect = bitmap.naturalWidth / Math.max(1, bitmap.naturalHeight);
29662
29690
  const baseWidth = Math.abs(Number(part.size?.[0])) || 1;
@@ -29679,16 +29707,49 @@ function createGeometryContent(definition, entity, modelPalette, invalidate) {
29679
29707
  };
29680
29708
  const onError = () => {
29681
29709
  removeListeners();
29682
- if (!cancelled) invalidate?.();
29710
+ releaseObjectUrl();
29711
+ if (!cancelled) {
29712
+ reportWarning?.({ code: "IMAGE_LOAD_FAILED", geometry: definition.id, entityId: entity.id, image });
29713
+ invalidate?.();
29714
+ }
29683
29715
  };
29684
29716
  material.userData.atlasImageCleanup = () => {
29685
29717
  cancelled = true;
29718
+ abortController.abort();
29686
29719
  removeListeners();
29720
+ releaseObjectUrl();
29687
29721
  bitmap.src = "";
29688
29722
  };
29689
29723
  bitmap.addEventListener("load", onLoad);
29690
29724
  bitmap.addEventListener("error", onError);
29691
- bitmap.src = image;
29725
+ const assignImageSource = async () => {
29726
+ const inlineSvg = /^\s*<svg(?:\s|>)/i.test(image);
29727
+ if (inlineSvg) {
29728
+ objectUrl = URL.createObjectURL(new Blob([image], { type: "image/svg+xml" }));
29729
+ bitmap.src = objectUrl;
29730
+ return;
29731
+ }
29732
+ if (/^(?:data|blob):/i.test(image)) {
29733
+ bitmap.src = image;
29734
+ return;
29735
+ }
29736
+ try {
29737
+ const response = await fetch(image, { mode: "cors", signal: abortController.signal });
29738
+ if (!response.ok) throw new Error(`Image request failed with ${response.status}`);
29739
+ let blob = await response.blob();
29740
+ const contentType = response.headers.get("content-type") || blob.type;
29741
+ const looksLikeSvg = /image\/svg\+xml/i.test(contentType) || /\.svg(?:[?#]|$)/i.test(image);
29742
+ svgSource = svgSource || looksLikeSvg;
29743
+ if (looksLikeSvg && blob.type !== "image/svg+xml") blob = new Blob([await blob.text()], { type: "image/svg+xml" });
29744
+ if (cancelled) return;
29745
+ objectUrl = URL.createObjectURL(blob);
29746
+ bitmap.src = objectUrl;
29747
+ } catch (error) {
29748
+ if (cancelled || error?.name === "AbortError") return;
29749
+ bitmap.src = image;
29750
+ }
29751
+ };
29752
+ assignImageSource();
29692
29753
  material.map.colorSpace = SRGBColorSpace;
29693
29754
  material.needsUpdate = true;
29694
29755
  } else if (icon != null && icon !== "") {
@@ -30376,7 +30437,7 @@ var LiquidCarsAtlas = class extends HTMLElement {
30376
30437
  node.ports = { ...definition?.anchors || {}, ...entity.ports || {} };
30377
30438
  if (isCustom) {
30378
30439
  try {
30379
- const content = createGeometryContent(definition, entity, this.modelData.palette, () => this.invalidate());
30440
+ const content = createGeometryContent(definition, entity, this.modelData.palette, () => this.invalidate(), (detail) => this.dispatchEvent(new CustomEvent("lc-warning", { detail })));
30380
30441
  content.root.scale.multiply(new Vector3().fromArray(entity.s));
30381
30442
  node.appearanceMaterials = [];
30382
30443
  content.root.traverse((object) => {
package/index.html CHANGED
@@ -14,12 +14,12 @@
14
14
  liquidcars-atlas { display: block; width: 100%; max-width: 1480px; margin: 0 auto; }
15
15
  code { color: #85e5d3; }
16
16
  </style>
17
- <script type="module" src="./dist/liquidcars-atlas.js?v=0.1.26"></script>
17
+ <script type="module" src="./dist/liquidcars-atlas.js?v=0.1.28"></script>
18
18
  </head>
19
19
  <body>
20
20
  <header>
21
21
  <h1>LiquidCars Atlas</h1>
22
- <p>Prueba de distribución del componente <code>&lt;liquidcars-atlas&gt;</code> · v0.1.26</p>
22
+ <p>Prueba de distribución del componente <code>&lt;liquidcars-atlas&gt;</code> · v0.1.28</p>
23
23
  </header>
24
24
  <main>
25
25
  <liquidcars-atlas id="atlas" height="720" relation-mode="selected" shell-labels="on"></liquidcars-atlas>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liquidcars/atlas",
3
- "version": "0.1.26",
3
+ "version": "0.1.28",
4
4
  "description": "Reusable 3D conceptual atlas Web Component for LiquidCars-style models",
5
5
  "type": "module",
6
6
  "main": "./dist/liquidcars-atlas.js",