@liquidcars/atlas 0.1.23 → 0.1.24
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 +67 -3
- package/demo/index.html +2 -2
- package/dist/liquidcars-atlas.js +287 -32
- package/index.html +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -96,7 +96,7 @@ principales también están expuestos como variables CSS (`--lc-bg`,
|
|
|
96
96
|
por lo que una aplicación puede adaptar la apariencia sin modificar el bundle.
|
|
97
97
|
Las propiedades `atlas.theme` y `atlas.styles` son alias prácticos de esta configuración.
|
|
98
98
|
|
|
99
|
-
Eventos disponibles: `lc-ready`, `lc-error`, `lc-select`, `lc-model-change`,
|
|
99
|
+
Eventos disponibles: `lc-ready`, `lc-error`, `lc-warning`, `lc-select`, `lc-model-change`,
|
|
100
100
|
`lc-relation-mode` y `lc-disconnect`. `lc-select` usa un detalle discriminado:
|
|
101
101
|
las entidades incluyen `{ kind: 'entity', id, entity, source }` y las relaciones
|
|
102
102
|
`{ kind: 'relation', id, index, relation, source }`. Los campos históricos `id`
|
|
@@ -127,6 +127,68 @@ como `setClickBehavior()`).
|
|
|
127
127
|
|
|
128
128
|
Geometrías incluidas en esta primera versión: `box`, `cylinder`, `sphere`, `cone`, `frustum-cone`, `frustum-pyramid`, `frustum-triangle`, `prism`, `pyramid-square`, `pyramid-triangle`, `octahedron`, `dodecahedron` e `icosahedron`. Las variantes `frustum-*` son sólidos truncados con dos caras finitas paralelas; las dimensiones `s` se aplican sobre su caja final normalizada.
|
|
129
129
|
|
|
130
|
+
## Geometry packs
|
|
131
|
+
|
|
132
|
+
Las geometrías pueden distribuirse en paquetes independientes. El componente
|
|
133
|
+
expone `registerGeometryPack()`, `unregisterGeometryPack()` y
|
|
134
|
+
`getGeometryCatalog()`; los mismos métodos están disponibles como estáticos en
|
|
135
|
+
`LiquidCarsAtlas`. Los identificadores usan un namespace (`infra.server`,
|
|
136
|
+
`architecture.application`, etc.) y las primitivas integradas también se
|
|
137
|
+
publican como `geo.box`, `geo.sphere`, etc. Los nombres históricos sin
|
|
138
|
+
namespace siguen siendo compatibles.
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
import { registerGeometryPack } from "@liquidcars/atlas";
|
|
142
|
+
|
|
143
|
+
registerGeometryPack({
|
|
144
|
+
namespace: "infra",
|
|
145
|
+
name: "Infrastructure",
|
|
146
|
+
version: "0.1.0",
|
|
147
|
+
palette: { chassis: "#587386", accent: "#72dbc9" },
|
|
148
|
+
geometries: [{
|
|
149
|
+
id: "server",
|
|
150
|
+
name: "Server",
|
|
151
|
+
canonicalSize: [2.7, 1.8, 2.2],
|
|
152
|
+
parameters: {
|
|
153
|
+
units: { type: "number", min: 1, max: 8, default: 3 },
|
|
154
|
+
icon: { type: "icon", default: "⚙" },
|
|
155
|
+
image: { type: "image", default: "" }
|
|
156
|
+
},
|
|
157
|
+
anchors: { input: [-1, 0, 0], output: [1, 0, 0], network: [0, 0, 1] },
|
|
158
|
+
create({ parameters }) {
|
|
159
|
+
return { parts: [
|
|
160
|
+
{ shape: "box", size: [1, 1, 1], color: "chassis" },
|
|
161
|
+
{ shape: "plane", size: [.42, .42, 1], position: [0, 0, .51],
|
|
162
|
+
color: "accent", icon: parameters.icon, image: parameters.image,
|
|
163
|
+
edges: false, doubleSided: true }
|
|
164
|
+
] };
|
|
165
|
+
}
|
|
166
|
+
}]
|
|
167
|
+
});
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
El modelo configura cada instancia sin conocer la implementación del paquete:
|
|
171
|
+
|
|
172
|
+
```json
|
|
173
|
+
{
|
|
174
|
+
"geometry": "infra.server",
|
|
175
|
+
"geometryOptions": { "units": 4, "icon": "DB" },
|
|
176
|
+
"geometryPalette": { "accent": "#ffd166" }
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Cada definición puede declarar parámetros tipados, una paleta local,
|
|
181
|
+
proporciones canónicas y anchors. Las piezas declarativas admiten primitivas,
|
|
182
|
+
posición, rotación, escala, materiales, iconos de texto o emoji e imágenes por
|
|
183
|
+
URL/data URI. El renderer mantiene Three.js encapsulado para evitar que los
|
|
184
|
+
packs dependan de su versión interna. Las relaciones pueden seleccionar anchors
|
|
185
|
+
con `fromAnchor` y `toAnchor`.
|
|
186
|
+
|
|
187
|
+
Si un namespace no está registrado, Atlas conserva el modelo renderizable con
|
|
188
|
+
una caja roja de fallback y emite `lc-warning` con el código
|
|
189
|
+
`GEOMETRY_NOT_REGISTERED`. Si la factoría del pack falla se usa el código
|
|
190
|
+
`GEOMETRY_BUILD_FAILED`.
|
|
191
|
+
|
|
130
192
|
La paleta puede cambiarse después de cargar el modelo mediante `atlas.setPalette({ nombre: color })` o asignando `atlas.palette = { ... }`. El cambio actualiza las piezas, aristas y relaciones sin reconstruir el modelo.
|
|
131
193
|
|
|
132
194
|
Las acciones de navegación y exploración también están disponibles para la página anfitriona: `zoomIn()`, `zoomOut()`, `panBy()`, `panTo()`, `fit()`, `frontView()`, `setShellLabels()`, `toggleShellLabels()`, `openAllContainers()` y `closeAllContainers()`. El usuario puede desplazar la vista con el botón derecho, con `Shift` + arrastre o mediante dos dedos en una pantalla táctil. Así se puede construir una barra de controles propia sin depender de la toolbar interna.
|
|
@@ -151,12 +213,14 @@ Una vez publicado, los consumidores podrán instalarlo con `npm install @liquidc
|
|
|
151
213
|
Para probar el paquete sin publicarlo, basta con instalar el tarball generado:
|
|
152
214
|
|
|
153
215
|
```bash
|
|
154
|
-
npm install ./liquidcars-atlas-0.1.
|
|
216
|
+
npm install ./liquidcars-atlas-0.1.24.tgz
|
|
155
217
|
```
|
|
156
218
|
|
|
157
219
|
## Estado
|
|
158
220
|
|
|
159
|
-
`0.1.
|
|
221
|
+
`0.1.24` incorpora geometry packs independientes, namespaces, parámetros
|
|
222
|
+
personalizados, paletas locales, anchors e imágenes/iconos declarativos. La
|
|
223
|
+
versión `0.1.23` añadió desplazamiento lateral de cámara mediante ratón, teclado
|
|
160
224
|
modificador y gestos táctiles, además de la API pública `panBy()` y `panTo()`.
|
|
161
225
|
Se conserva la selección contextual de relaciones introducida en `0.1.22`: el usuario puede
|
|
162
226
|
seleccionar un tubo directamente, el componente lo realza, muestra sus extremos
|
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.
|
|
17
|
+
<script type="module" src="../dist/liquidcars-atlas.js?v=0.1.24"></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><liquidcars-atlas></code> · v0.1.
|
|
22
|
+
<p>Prueba de distribución del componente <code><liquidcars-atlas></code> · v0.1.24</p>
|
|
23
23
|
</header>
|
|
24
24
|
<main>
|
|
25
25
|
<liquidcars-atlas id="atlas" height="720" relation-mode="selected" shell-labels="on"></liquidcars-atlas>
|
package/dist/liquidcars-atlas.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! LiquidCars Atlas 0.1.
|
|
1
|
+
/*! LiquidCars Atlas 0.1.24 | MIT */
|
|
2
2
|
|
|
3
3
|
// src/three.core.js
|
|
4
4
|
/**
|
|
@@ -29344,6 +29344,86 @@ var DEFAULT_PALETTE = {
|
|
|
29344
29344
|
component: "#8ae4d0",
|
|
29345
29345
|
connection: "#f4edba"
|
|
29346
29346
|
};
|
|
29347
|
+
var GEOMETRY_DEFINITIONS = /* @__PURE__ */ new Map();
|
|
29348
|
+
var GEOMETRY_ALIASES = /* @__PURE__ */ new Map();
|
|
29349
|
+
function publicGeometryDefinition(definition) {
|
|
29350
|
+
const { create, ...metadata } = definition;
|
|
29351
|
+
return {
|
|
29352
|
+
...metadata,
|
|
29353
|
+
canonicalSize: [...definition.canonicalSize],
|
|
29354
|
+
parameters: { ...definition.parameters },
|
|
29355
|
+
palette: { ...definition.palette },
|
|
29356
|
+
anchors: { ...definition.anchors }
|
|
29357
|
+
};
|
|
29358
|
+
}
|
|
29359
|
+
function registerGeometryPack(pack) {
|
|
29360
|
+
if (!pack || typeof pack !== "object") throw new TypeError("A geometry pack must be an object.");
|
|
29361
|
+
const namespace = String(pack.namespace || "").trim();
|
|
29362
|
+
if (!/^[a-z][a-z0-9-]*$/i.test(namespace)) throw new TypeError("A geometry pack requires a valid namespace.");
|
|
29363
|
+
if (!Array.isArray(pack.geometries) || !pack.geometries.length) throw new TypeError("A geometry pack requires at least one geometry.");
|
|
29364
|
+
const registered = [];
|
|
29365
|
+
for (const source of pack.geometries) {
|
|
29366
|
+
if (!source || typeof source !== "object" || !source.id) throw new TypeError(`Geometry pack '${namespace}' contains an invalid geometry.`);
|
|
29367
|
+
const localId = String(source.id).includes(".") ? String(source.id).split(".").pop() : String(source.id);
|
|
29368
|
+
const id = String(source.id).includes(".") ? String(source.id) : `${namespace}.${localId}`;
|
|
29369
|
+
const definition = {
|
|
29370
|
+
...source,
|
|
29371
|
+
id,
|
|
29372
|
+
localId,
|
|
29373
|
+
namespace,
|
|
29374
|
+
pack: pack.name || namespace,
|
|
29375
|
+
packVersion: pack.version || "0.0.0",
|
|
29376
|
+
category: source.category || pack.category || pack.name || namespace,
|
|
29377
|
+
canonicalSize: Array.isArray(source.canonicalSize) && source.canonicalSize.length === 3 ? source.canonicalSize.map(Number) : [2.5, 1.5, 2.5],
|
|
29378
|
+
parameters: source.parameters && typeof source.parameters === "object" ? source.parameters : {},
|
|
29379
|
+
palette: { ...pack.palette || {}, ...source.palette || {} },
|
|
29380
|
+
anchors: { ...pack.anchors || {}, ...source.anchors || {} }
|
|
29381
|
+
};
|
|
29382
|
+
GEOMETRY_DEFINITIONS.set(id, definition);
|
|
29383
|
+
GEOMETRY_ALIASES.set(id, id);
|
|
29384
|
+
for (const alias of source.aliases || []) GEOMETRY_ALIASES.set(String(alias), id);
|
|
29385
|
+
registered.push(id);
|
|
29386
|
+
}
|
|
29387
|
+
if (typeof globalThis.dispatchEvent === "function" && typeof CustomEvent === "function") {
|
|
29388
|
+
globalThis.dispatchEvent(new CustomEvent("lc-geometry-registry-change", { detail: { namespace, geometries: registered } }));
|
|
29389
|
+
}
|
|
29390
|
+
return { namespace, geometries: registered };
|
|
29391
|
+
}
|
|
29392
|
+
function unregisterGeometryPack(namespace) {
|
|
29393
|
+
const removed = [];
|
|
29394
|
+
for (const [id, definition] of GEOMETRY_DEFINITIONS) {
|
|
29395
|
+
if (definition.namespace !== namespace) continue;
|
|
29396
|
+
GEOMETRY_DEFINITIONS.delete(id);
|
|
29397
|
+
removed.push(id);
|
|
29398
|
+
}
|
|
29399
|
+
for (const [alias, id] of GEOMETRY_ALIASES) if (removed.includes(id)) GEOMETRY_ALIASES.delete(alias);
|
|
29400
|
+
return removed;
|
|
29401
|
+
}
|
|
29402
|
+
function getGeometryCatalog() {
|
|
29403
|
+
return [...GEOMETRY_DEFINITIONS.values()].map(publicGeometryDefinition);
|
|
29404
|
+
}
|
|
29405
|
+
function geometryDefinition(kind) {
|
|
29406
|
+
const id = GEOMETRY_ALIASES.get(String(kind || "box")) || String(kind || "box");
|
|
29407
|
+
return GEOMETRY_DEFINITIONS.get(id) || null;
|
|
29408
|
+
}
|
|
29409
|
+
var CORE_GEOMETRIES = [
|
|
29410
|
+
"box", "cylinder", "sphere", "cone", "frustum-cone", "frustum-pyramid", "frustum-triangle",
|
|
29411
|
+
"prism", "pyramid-square", "pyramid-triangle", "octahedron", "dodecahedron", "icosahedron"
|
|
29412
|
+
];
|
|
29413
|
+
registerGeometryPack({
|
|
29414
|
+
namespace: "geo",
|
|
29415
|
+
name: "Geometric primitives",
|
|
29416
|
+
version: "1",
|
|
29417
|
+
category: "Geometric primitives",
|
|
29418
|
+
geometries: CORE_GEOMETRIES.map((id) => ({
|
|
29419
|
+
id,
|
|
29420
|
+
primitive: id,
|
|
29421
|
+
aliases: [id],
|
|
29422
|
+
name: id.split("-").map((word) => word[0].toUpperCase() + word.slice(1)).join(" "),
|
|
29423
|
+
canonicalSize: id === "sphere" || ["octahedron", "dodecahedron", "icosahedron"].includes(id) ? [2.2, 2.2, 2.2] : [2.5, 1.5, 2.5],
|
|
29424
|
+
parameters: id === "box" ? { radius: { type: "number", label: "Radius", min: 0, max: 0.72, step: 0.01, default: 0 } } : {}
|
|
29425
|
+
}))
|
|
29426
|
+
});
|
|
29347
29427
|
var DEFAULT_THEME = {
|
|
29348
29428
|
background: { color: "#0a111a", glow: "#1c2b39", fade: "#0a111a" },
|
|
29349
29429
|
panel: { background: "linear-gradient(140deg,#182633,#0e1925 65%)", border: "#2a3a48", text: "#b5c6d2", title: "#e9f0f5" },
|
|
@@ -29500,6 +29580,112 @@ function normalizeGeometry(geometry) {
|
|
|
29500
29580
|
geometry.computeBoundingBox();
|
|
29501
29581
|
return geometry;
|
|
29502
29582
|
}
|
|
29583
|
+
function resolveGeometryParameters(entity, definition) {
|
|
29584
|
+
const supplied = entity.geometryOptions || entity.geometry_options || entity.parameters || {};
|
|
29585
|
+
const resolved = {};
|
|
29586
|
+
for (const [name, schema] of Object.entries(definition?.parameters || {})) {
|
|
29587
|
+
if (schema && Object.hasOwn(schema, "default")) resolved[name] = schema.default;
|
|
29588
|
+
}
|
|
29589
|
+
return { ...resolved, ...supplied && typeof supplied === "object" ? supplied : {} };
|
|
29590
|
+
}
|
|
29591
|
+
function resolveGeometryColor(value, palette, fallback) {
|
|
29592
|
+
if (typeof value !== "string" || !value) return fallback;
|
|
29593
|
+
return palette[value] || value;
|
|
29594
|
+
}
|
|
29595
|
+
function iconTexture(value, foreground = "#ffffff", background = "transparent") {
|
|
29596
|
+
const bitmap = document.createElement("canvas");
|
|
29597
|
+
bitmap.width = 256;
|
|
29598
|
+
bitmap.height = 256;
|
|
29599
|
+
const context = bitmap.getContext("2d");
|
|
29600
|
+
context.clearRect(0, 0, 256, 256);
|
|
29601
|
+
if (background !== "transparent") {
|
|
29602
|
+
context.fillStyle = background;
|
|
29603
|
+
context.fillRect(0, 0, 256, 256);
|
|
29604
|
+
}
|
|
29605
|
+
context.fillStyle = foreground;
|
|
29606
|
+
context.font = "600 150px system-ui, sans-serif";
|
|
29607
|
+
context.textAlign = "center";
|
|
29608
|
+
context.textBaseline = "middle";
|
|
29609
|
+
context.fillText(String(value || ""), 128, 137, 220);
|
|
29610
|
+
const texture = new CanvasTexture(bitmap);
|
|
29611
|
+
texture.colorSpace = SRGBColorSpace;
|
|
29612
|
+
return texture;
|
|
29613
|
+
}
|
|
29614
|
+
function partGeometry(part) {
|
|
29615
|
+
if (part.shape === "plane") return new PlaneGeometry(1, 1);
|
|
29616
|
+
return makeGeometry(part.shape || part.geometry || "box", part.radius, part.segments, part.size);
|
|
29617
|
+
}
|
|
29618
|
+
function createGeometryContent(definition, entity, modelPalette, invalidate) {
|
|
29619
|
+
const parameters = resolveGeometryParameters(entity, definition);
|
|
29620
|
+
const localPalette = {
|
|
29621
|
+
...modelPalette,
|
|
29622
|
+
...definition.palette || {},
|
|
29623
|
+
...entity.geometryPalette || entity.geometry_palette || {}
|
|
29624
|
+
};
|
|
29625
|
+
let descriptor = typeof definition.create === "function" ? definition.create({ parameters: { ...parameters }, colors: { ...localPalette }, entity: { ...entity } }) : { parts: definition.parts || [] };
|
|
29626
|
+
if (Array.isArray(descriptor)) descriptor = { parts: descriptor };
|
|
29627
|
+
const parts = Array.isArray(descriptor?.parts) ? descriptor.parts : [];
|
|
29628
|
+
if (!parts.length) throw new Error(`Geometry '${definition.id}' did not produce any parts.`);
|
|
29629
|
+
const root = new Group();
|
|
29630
|
+
const colorBindings = [];
|
|
29631
|
+
const meshes = [];
|
|
29632
|
+
for (const part of parts) {
|
|
29633
|
+
const geometry = partGeometry(part);
|
|
29634
|
+
normalizeGeometry(geometry);
|
|
29635
|
+
const colorValue = part.color || "component";
|
|
29636
|
+
const color = resolveGeometryColor(colorValue, localPalette, modelPalette.component || DEFAULT_PALETTE.component);
|
|
29637
|
+
const material = new MeshStandardMaterial({
|
|
29638
|
+
color,
|
|
29639
|
+
roughness: Number.isFinite(Number(part.roughness)) ? Number(part.roughness) : 0.72,
|
|
29640
|
+
metalness: Number.isFinite(Number(part.metalness)) ? Number(part.metalness) : 0.06,
|
|
29641
|
+
transparent: part.transparent === true || Number(part.opacity) < 1,
|
|
29642
|
+
opacity: Number.isFinite(Number(part.opacity)) ? Number(part.opacity) : 1,
|
|
29643
|
+
side: part.doubleSided ? DoubleSide : FrontSide
|
|
29644
|
+
});
|
|
29645
|
+
const icon = part.icon;
|
|
29646
|
+
const image = part.image;
|
|
29647
|
+
if (typeof image === "string" && image) {
|
|
29648
|
+
const bitmap = new Image();
|
|
29649
|
+
bitmap.crossOrigin = "anonymous";
|
|
29650
|
+
material.map = new Texture(bitmap);
|
|
29651
|
+
bitmap.addEventListener("load", () => {
|
|
29652
|
+
material.map.needsUpdate = true;
|
|
29653
|
+
invalidate?.();
|
|
29654
|
+
});
|
|
29655
|
+
bitmap.addEventListener("error", () => invalidate?.());
|
|
29656
|
+
bitmap.src = image;
|
|
29657
|
+
material.map.colorSpace = SRGBColorSpace;
|
|
29658
|
+
material.needsUpdate = true;
|
|
29659
|
+
} else if (icon != null && icon !== "") {
|
|
29660
|
+
material.map = iconTexture(icon, resolveGeometryColor(part.iconColor || "#ffffff", localPalette, "#ffffff"), resolveGeometryColor(part.iconBackground || "transparent", localPalette, "transparent"));
|
|
29661
|
+
material.transparent = true;
|
|
29662
|
+
material.needsUpdate = true;
|
|
29663
|
+
}
|
|
29664
|
+
const mesh = new Mesh(geometry, material);
|
|
29665
|
+
mesh.position.fromArray(Array.isArray(part.position) ? part.position : [0, 0, 0]);
|
|
29666
|
+
mesh.scale.fromArray(Array.isArray(part.size) ? part.size : [1, 1, 1]);
|
|
29667
|
+
if (Array.isArray(part.rotation)) mesh.rotation.set(...part.rotation.map(Number));
|
|
29668
|
+
root.add(mesh);
|
|
29669
|
+
meshes.push(mesh);
|
|
29670
|
+
colorBindings.push({ material, value: colorValue, palette: localPalette });
|
|
29671
|
+
if (part.edges !== false && part.shape !== "plane") {
|
|
29672
|
+
const edgeMaterial = new LineBasicMaterial({ color, transparent: true, opacity: Number.isFinite(Number(part.edgeOpacity)) ? Number(part.edgeOpacity) : 0.55 });
|
|
29673
|
+
const edge = new LineSegments(new EdgesGeometry(geometry, 22), edgeMaterial);
|
|
29674
|
+
edge.position.copy(mesh.position);
|
|
29675
|
+
edge.rotation.copy(mesh.rotation);
|
|
29676
|
+
edge.scale.copy(mesh.scale);
|
|
29677
|
+
root.add(edge);
|
|
29678
|
+
colorBindings.push({ material: edgeMaterial, value: part.edgeColor || colorValue, palette: localPalette });
|
|
29679
|
+
}
|
|
29680
|
+
}
|
|
29681
|
+
root.updateMatrixWorld(true);
|
|
29682
|
+
const bounds = new Box3().setFromObject(root);
|
|
29683
|
+
const contentSize = bounds.getSize(new Vector3());
|
|
29684
|
+
const center = bounds.getCenter(new Vector3());
|
|
29685
|
+
root.children.forEach((child) => child.position.sub(center));
|
|
29686
|
+
root.scale.set(1 / Math.max(contentSize.x, 1e-4), 1 / Math.max(contentSize.y, 1e-4), 1 / Math.max(contentSize.z, 1e-4));
|
|
29687
|
+
return { root, meshes, colorBindings, parameters, descriptor };
|
|
29688
|
+
}
|
|
29503
29689
|
function makeLabel(text, color, dimensions, type = "") {
|
|
29504
29690
|
const bitmap = document.createElement("canvas");
|
|
29505
29691
|
const context = bitmap.getContext("2d");
|
|
@@ -29580,6 +29766,15 @@ var LiquidCarsAtlas = class extends HTMLElement {
|
|
|
29580
29766
|
static get observedAttributes() {
|
|
29581
29767
|
return ["height", "relation-mode", "shell-labels", "toolbar", "selection-mode"];
|
|
29582
29768
|
}
|
|
29769
|
+
static registerGeometryPack(pack) {
|
|
29770
|
+
return registerGeometryPack(pack);
|
|
29771
|
+
}
|
|
29772
|
+
static unregisterGeometryPack(namespace) {
|
|
29773
|
+
return unregisterGeometryPack(namespace);
|
|
29774
|
+
}
|
|
29775
|
+
static getGeometryCatalog() {
|
|
29776
|
+
return getGeometryCatalog();
|
|
29777
|
+
}
|
|
29583
29778
|
constructor() {
|
|
29584
29779
|
super();
|
|
29585
29780
|
this.attachShadow({ mode: "open" });
|
|
@@ -29616,15 +29811,20 @@ var LiquidCarsAtlas = class extends HTMLElement {
|
|
|
29616
29811
|
this.ready = false;
|
|
29617
29812
|
this._toolbarVisible = this.getAttribute("toolbar") !== "off";
|
|
29618
29813
|
this.toolbar = this.shadowRoot.querySelector(".toolbar");
|
|
29814
|
+
this._geometryRegistryListener = () => {
|
|
29815
|
+
if (this.ready && this.modelData) this._build({ preserveSelection: this.selected?.id, preserveRelation: this.selectedRelationIndex });
|
|
29816
|
+
};
|
|
29619
29817
|
this._bindControls();
|
|
29620
29818
|
this.resizeObserver = new ResizeObserver(() => this.resize());
|
|
29621
29819
|
this.resizeObserver.observe(this.canvas);
|
|
29622
29820
|
}
|
|
29623
29821
|
connectedCallback() {
|
|
29822
|
+
globalThis.addEventListener?.("lc-geometry-registry-change", this._geometryRegistryListener);
|
|
29624
29823
|
if (this.hasAttribute("height")) this.style.setProperty("--lc-height", this.getAttribute("height"));
|
|
29625
29824
|
this._loadInitial();
|
|
29626
29825
|
}
|
|
29627
29826
|
disconnectedCallback() {
|
|
29827
|
+
globalThis.removeEventListener?.("lc-geometry-registry-change", this._geometryRegistryListener);
|
|
29628
29828
|
this.resizeObserver.disconnect();
|
|
29629
29829
|
if (this.animationFrame) cancelAnimationFrame(this.animationFrame);
|
|
29630
29830
|
if (this.renderer) this.renderer.dispose();
|
|
@@ -30042,10 +30242,16 @@ var LiquidCarsAtlas = class extends HTMLElement {
|
|
|
30042
30242
|
_clearObjects() {
|
|
30043
30243
|
for (const node of this.nodes.values()) {
|
|
30044
30244
|
node.group?.traverse((object) => {
|
|
30045
|
-
if (!object.isMesh && !object.isSprite) return;
|
|
30245
|
+
if (!object.isMesh && !object.isSprite && !object.isLineSegments) return;
|
|
30046
30246
|
object.geometry?.dispose();
|
|
30047
|
-
if (Array.isArray(object.material)) object.material.forEach((material) =>
|
|
30048
|
-
|
|
30247
|
+
if (Array.isArray(object.material)) object.material.forEach((material) => {
|
|
30248
|
+
material.map?.dispose();
|
|
30249
|
+
material.dispose();
|
|
30250
|
+
});
|
|
30251
|
+
else {
|
|
30252
|
+
object.material?.map?.dispose();
|
|
30253
|
+
object.material?.dispose();
|
|
30254
|
+
}
|
|
30049
30255
|
});
|
|
30050
30256
|
this.scene.remove(node.group);
|
|
30051
30257
|
}
|
|
@@ -30066,6 +30272,10 @@ var LiquidCarsAtlas = class extends HTMLElement {
|
|
|
30066
30272
|
const color = this.modelData.palette[node.c] || node.c || this.modelData.palette.component;
|
|
30067
30273
|
if (node.material) node.material.color.set(color);
|
|
30068
30274
|
if (node.edge?.material) node.edge.material.color.set(color);
|
|
30275
|
+
for (const binding of node.colorBindings || []) {
|
|
30276
|
+
const palette = { ...this.modelData.palette, ...node.geometryPalette || {}, ...node.geometryPaletteOverrides || {} };
|
|
30277
|
+
binding.material.color.set(resolveGeometryColor(binding.value, palette, color));
|
|
30278
|
+
}
|
|
30069
30279
|
}
|
|
30070
30280
|
}
|
|
30071
30281
|
_build(options = {}) {
|
|
@@ -30080,24 +30290,57 @@ var LiquidCarsAtlas = class extends HTMLElement {
|
|
|
30080
30290
|
node.group = new Group();
|
|
30081
30291
|
node.group.position.copy(this._worldPosition(node));
|
|
30082
30292
|
const geometryKind = entity.geometry || "box";
|
|
30083
|
-
const
|
|
30084
|
-
const
|
|
30085
|
-
|
|
30086
|
-
|
|
30087
|
-
|
|
30088
|
-
|
|
30089
|
-
node.
|
|
30090
|
-
|
|
30091
|
-
|
|
30092
|
-
|
|
30093
|
-
}
|
|
30293
|
+
const definition = geometryDefinition(geometryKind);
|
|
30294
|
+
const missingGeometry = !definition && geometryKind.includes(".");
|
|
30295
|
+
const color = missingGeometry ? "#ed8f98" : this.modelData.palette[entity.c] || entity.c || this.modelData.palette.component;
|
|
30296
|
+
if (missingGeometry) this.dispatchEvent(new CustomEvent("lc-warning", { detail: { code: "GEOMETRY_NOT_REGISTERED", geometry: geometryKind, entityId: entity.id } }));
|
|
30297
|
+
const primitiveKind = definition?.primitive || geometryKind;
|
|
30298
|
+
const isCustom = definition && !definition.primitive && entity.type !== "shell";
|
|
30299
|
+
node.geometryDefinition = definition || null;
|
|
30300
|
+
node.geometryMissing = missingGeometry;
|
|
30301
|
+
node.geometryPalette = definition?.palette || {};
|
|
30302
|
+
node.geometryPaletteOverrides = entity.geometryPalette || entity.geometry_palette || {};
|
|
30303
|
+
node.ports = { ...definition?.anchors || {}, ...entity.ports || {} };
|
|
30304
|
+
if (isCustom) {
|
|
30305
|
+
try {
|
|
30306
|
+
const content = createGeometryContent(definition, entity, this.modelData.palette, () => this.invalidate());
|
|
30307
|
+
content.root.scale.multiply(new Vector3().fromArray(entity.s));
|
|
30308
|
+
content.root.traverse((object) => {
|
|
30309
|
+
if (object.isMesh) object.userData.node = node;
|
|
30310
|
+
});
|
|
30311
|
+
node.mesh = content.root;
|
|
30312
|
+
node.colorBindings = content.colorBindings;
|
|
30313
|
+
node.geometryOptions = content.parameters;
|
|
30314
|
+
node.group.add(content.root);
|
|
30315
|
+
} catch (error) {
|
|
30316
|
+
this.dispatchEvent(new CustomEvent("lc-warning", { detail: { code: "GEOMETRY_BUILD_FAILED", geometry: geometryKind, entityId: entity.id, error } }));
|
|
30317
|
+
const fallback = normalizeGeometry(makeGeometry("box"));
|
|
30318
|
+
node.material = new MeshStandardMaterial({ color: "#ed8f98", roughness: 0.72, metalness: 0.06 });
|
|
30319
|
+
node.mesh = new Mesh(fallback, node.material);
|
|
30320
|
+
node.mesh.scale.fromArray(entity.s);
|
|
30321
|
+
node.mesh.userData.node = node;
|
|
30322
|
+
node.group.add(node.mesh);
|
|
30323
|
+
}
|
|
30094
30324
|
node.labelSize = new Vector3().fromArray(entity.s);
|
|
30325
|
+
} else {
|
|
30326
|
+
const baked = primitiveKind === "box" && Number(entity.radius) > 0;
|
|
30327
|
+
const geometry = makeGeometry(primitiveKind, entity.radius, entity.segments, entity.s);
|
|
30328
|
+
if (!baked) normalizeGeometry(geometry);
|
|
30329
|
+
node.material = new MeshStandardMaterial({ color, roughness: 0.72, metalness: 0.06, transparent: true, opacity: entity.type === "shell" ? 0.96 : 1 });
|
|
30330
|
+
node.mesh = new Mesh(geometry, node.material);
|
|
30331
|
+
node.mesh.scale.fromArray(baked ? [1, 1, 1] : entity.s);
|
|
30332
|
+
if (baked) {
|
|
30333
|
+
geometry.computeBoundingBox();
|
|
30334
|
+
node.labelSize = geometry.boundingBox.getSize(new Vector3());
|
|
30335
|
+
} else {
|
|
30336
|
+
node.labelSize = new Vector3().fromArray(entity.s);
|
|
30337
|
+
}
|
|
30338
|
+
node.mesh.userData.node = node;
|
|
30339
|
+
node.group.add(node.mesh);
|
|
30340
|
+
node.edge = new LineSegments(new EdgesGeometry(geometry, 22), new LineBasicMaterial({ color, transparent: true, opacity: 0.7 }));
|
|
30341
|
+
node.edge.scale.fromArray(baked ? [1, 1, 1] : entity.s);
|
|
30342
|
+
node.group.add(node.edge);
|
|
30095
30343
|
}
|
|
30096
|
-
node.mesh.userData.node = node;
|
|
30097
|
-
node.group.add(node.mesh);
|
|
30098
|
-
node.edge = new LineSegments(new EdgesGeometry(geometry, 22), new LineBasicMaterial({ color, transparent: true, opacity: 0.7 }));
|
|
30099
|
-
node.edge.scale.fromArray(baked ? [1, 1, 1] : entity.s);
|
|
30100
|
-
node.group.add(node.edge);
|
|
30101
30344
|
node.label = makeLabel(entity.name, color, node.labelSize, entity.type);
|
|
30102
30345
|
node.label.userData.node = node;
|
|
30103
30346
|
node.label.traverse((labelFace) => {
|
|
@@ -30123,18 +30366,19 @@ var LiquidCarsAtlas = class extends HTMLElement {
|
|
|
30123
30366
|
}
|
|
30124
30367
|
return true;
|
|
30125
30368
|
}
|
|
30126
|
-
_endpoint(node, other, isSource = false) {
|
|
30369
|
+
_endpoint(node, other, isSource = false, relation = null) {
|
|
30127
30370
|
const center = this._worldPosition(node);
|
|
30128
30371
|
const mode = node.anchor || "center";
|
|
30129
|
-
|
|
30130
|
-
if (mode === "port") {
|
|
30372
|
+
const requestedAnchor = relation?.[isSource ? "fromAnchor" : "toAnchor"] || relation?.[isSource ? "from_anchor" : "to_anchor"];
|
|
30373
|
+
if (mode === "port" || requestedAnchor || node.geometryDefinition?.anchors) {
|
|
30131
30374
|
const ports = node.ports || {};
|
|
30132
|
-
const port = ports[isSource ? "output" : "input"] || ports.default;
|
|
30375
|
+
const port = ports[requestedAnchor] || ports[isSource ? "output" : "input"] || ports.default;
|
|
30133
30376
|
if (Array.isArray(port) && port.length >= 3) {
|
|
30134
30377
|
const half2 = new Vector3(node.s[0] / 2, node.s[1] / 2, node.s[2] / 2);
|
|
30135
30378
|
return center.add(new Vector3(port[0] * half2.x, port[1] * half2.y, port[2] * half2.z));
|
|
30136
30379
|
}
|
|
30137
30380
|
}
|
|
30381
|
+
if (mode === "center") return center;
|
|
30138
30382
|
const direction = this._worldPosition(other).sub(center);
|
|
30139
30383
|
if (direction.lengthSq() < 1e-8) return center;
|
|
30140
30384
|
const half = new Vector3(node.s[0] / 2, node.s[1] / 2, node.s[2] / 2);
|
|
@@ -30158,8 +30402,8 @@ var LiquidCarsAtlas = class extends HTMLElement {
|
|
|
30158
30402
|
const from = this.nodes.get(relation.from);
|
|
30159
30403
|
const to = this.nodes.get(relation.to);
|
|
30160
30404
|
if (!from || !to) continue;
|
|
30161
|
-
const start = this._endpoint(from, to, true);
|
|
30162
|
-
const end = this._endpoint(to, from, false);
|
|
30405
|
+
const start = this._endpoint(from, to, true, relation);
|
|
30406
|
+
const end = this._endpoint(to, from, false, relation);
|
|
30163
30407
|
const delta = end.clone().sub(start);
|
|
30164
30408
|
if (delta.lengthSq() < 0.01) continue;
|
|
30165
30409
|
const visualMode = relationVisualMode(relation);
|
|
@@ -30380,13 +30624,21 @@ var LiquidCarsAtlas = class extends HTMLElement {
|
|
|
30380
30624
|
const visible = this._isVisible(node);
|
|
30381
30625
|
node.group.visible = visible;
|
|
30382
30626
|
const alpha = node.type === "shell" ? this.open.has(node.id) ? 0.12 : 1 : 1;
|
|
30383
|
-
node.material.opacity = alpha;
|
|
30384
30627
|
const transparent = alpha < 0.999;
|
|
30385
|
-
node.material.depthWrite = !transparent;
|
|
30386
30628
|
node.mesh.renderOrder = transparent ? 10 - (node.parent ? 1 : 0) : 0;
|
|
30387
|
-
|
|
30388
|
-
|
|
30389
|
-
|
|
30629
|
+
if (node.material) {
|
|
30630
|
+
node.material.opacity = alpha;
|
|
30631
|
+
node.material.depthWrite = !transparent;
|
|
30632
|
+
node.material.emissive?.set(node === this.selected ? node.material.color : 0);
|
|
30633
|
+
node.material.emissiveIntensity = node === this.selected ? 0.15 : 0;
|
|
30634
|
+
} else {
|
|
30635
|
+
node.mesh.traverse((object) => {
|
|
30636
|
+
if (!object.isMesh || !object.material) return;
|
|
30637
|
+
object.material.emissive?.set(node === this.selected ? object.material.color : 0);
|
|
30638
|
+
object.material.emissiveIntensity = node === this.selected ? 0.15 : 0;
|
|
30639
|
+
});
|
|
30640
|
+
}
|
|
30641
|
+
if (node.edge?.material) node.edge.material.opacity = node === this.selected ? 1 : 0.65;
|
|
30390
30642
|
const opened = node.type === "shell" && this.open.has(node.id);
|
|
30391
30643
|
const transparentLabel = node.type === "shell" && (opened || alpha < 0.65);
|
|
30392
30644
|
const displayTransparentLabel = transparentLabel && this.showShellLabels;
|
|
@@ -30465,7 +30717,7 @@ var LiquidCarsAtlas = class extends HTMLElement {
|
|
|
30465
30717
|
if (!(node.type === "shell" && this.open.has(node.id))) meshes.push(node.mesh);
|
|
30466
30718
|
if (node.label?.visible) meshes.push(...node.label.children.filter((face) => face.visible));
|
|
30467
30719
|
}
|
|
30468
|
-
const nodeHit = this.raycaster.intersectObjects(meshes,
|
|
30720
|
+
const nodeHit = this.raycaster.intersectObjects(meshes, true)[0] || null;
|
|
30469
30721
|
const relationTargets = this.relationGroup.children.filter((object) => object.userData.relationPick);
|
|
30470
30722
|
const relationHit = this.raycaster.intersectObjects(relationTargets, false)[0] || null;
|
|
30471
30723
|
if (nodeHit && (!relationHit || nodeHit.distance <= relationHit.distance + 0.12)) return { kind: "entity", node: nodeHit.object.userData.node };
|
|
@@ -30571,5 +30823,8 @@ var LiquidCarsAtlas = class extends HTMLElement {
|
|
|
30571
30823
|
if (!customElements.get("liquidcars-atlas")) customElements.define("liquidcars-atlas", LiquidCarsAtlas);
|
|
30572
30824
|
export {
|
|
30573
30825
|
LiquidCarsAtlas,
|
|
30826
|
+
getGeometryCatalog,
|
|
30827
|
+
registerGeometryPack,
|
|
30828
|
+
unregisterGeometryPack,
|
|
30574
30829
|
normalizeModel
|
|
30575
30830
|
};
|
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.
|
|
17
|
+
<script type="module" src="./dist/liquidcars-atlas.js?v=0.1.24"></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><liquidcars-atlas></code> · v0.1.
|
|
22
|
+
<p>Prueba de distribución del componente <code><liquidcars-atlas></code> · v0.1.24</p>
|
|
23
23
|
</header>
|
|
24
24
|
<main>
|
|
25
25
|
<liquidcars-atlas id="atlas" height="720" relation-mode="selected" shell-labels="on"></liquidcars-atlas>
|