Referencia técnica
Autoría de paquetes completos Locus Package v2
En esta página
- Empieza con un paquete completo funcional
- Estructura del archivo
- locusplace.json
- Referencia de campos de la envoltura
- Metadatos de Package v2 dentro del ZIP
- View: destination.json
- Room: space.json
- Experience: experience.json
- Capacidades opcionales y etiquetas traducidas
- Procedencia obligatoria
- Seguridad y límites de recursos
- Obtención desde Archivos y URL
- Validación de paquetes y ejemplos
Estado: contrato canónico de publicación, almacenamiento, composición e importación con todas las funciones. Locus 1.2.1 y posteriores aceptan este archivo desde Archivos o una URL HTTPS pública para importar tanto Views como Rooms. El antiguo formato plano descrito en
locus-asset-format.md sigue siendo una entrada de compatibilidad.
Un .zip o .locusplace es un archivo ZIP normal que contiene una o más Destinations de Package v2 (nombre del producto: Views), Spaces (Rooms) y Experiences. Es la envoltura segura de publicación e integridad que utilizan el propio Locus y los paquetes portátiles con todas las funciones. La raíz del ZIP contiene locusplace.json y
catalog/ directamente; no los coloques dentro de otra carpeta superior.
La validación se basa en los bytes y manifiestos del archivo, nunca en la extensión del nombre. Un paquete completo conserva su packageID, los ID de contenido,
contentVersion como flujo de actualización y las Experiences compatibles; importar el mismo hash de contenido es una operación idempotente que no produce cambios. Las herramientas de empaquetado y validación mantenidas producen e inspeccionan este formato exacto.
Esta envoltura contiene datos puros, no extensiones ejecutables. Para una misma compilación de Locus, todas las capacidades de Package v2 utilizadas en ejecución tienen la misma semántica de contenido, tanto si el archivo está integrado como si se descarga de Library o se importa desde Archivos/URL. El origen puede cambiar los derechos de acceso, la cuota de importaciones personalizadas, la autoridad de actualización, el almacenamiento, la eliminación y las etiquetas de interfaz, pero nunca habilita otro esquema Room/View ni otras funciones de renderizado. Los scripts, plugins, bibliotecas dinámicas y miembros ejecutables quedan fuera del formato. Todos los orígenes conservan las mismas comprobaciones de confinamiento de rutas, integridad, límites de recursos, decodificación estructural, cierre de referencias y versión mínima.
Utiliza esta guía para la envoltura completa y los manifiestos base. La referencia de campos documenta las capacidades opcionales de View y Room, sus recursos, intervalos y versiones mínimas de la app. El esquema de la envoltura y esquema de procedencia describen la estructura JSON; las herramientas de empaquetado y validación también comprueban los bytes y las reglas entre campos. Un esquema JSON por sí solo no puede validar un archivo ni demostrar que resulte cómodo en Apple Vision Pro.
Empieza con un paquete completo funcional
Clona o descarga el repositorio público de herramientas. Ejecuta estos comandos desde su raíz en macOS, con Python 3.9 o posterior y una instalación de Xcode que proporcione
xcrun usdchecker y xcrun usdcat. La validación básica utiliza la biblioteca estándar de Python,
/usr/bin/sips para decodificar las imágenes por completo, y las herramientas de Xcode usdchecker y usdcat
para los modelos. Las comprobaciones avanzadas de nubes, reflexión/contacto del agua y cielo sin sol también requieren NumPy y Pillow: python3 -m pip install numpy pillow.
python3 examples/locusplace/build_examples.py /absolute/path/to/locusplace-examples
python3 tools/validate_locusplace.py --current-app-version 1.4.4 /absolute/path/to/locusplace-examples/view-only.locusplace
python3 tools/validate_locusplace.py --current-app-version 1.4.4 /absolute/path/to/locusplace-examples/room-only.locusplace
python3 tools/validate_locusplace.py --current-app-version 1.4.4 /absolute/path/to/locusplace-examples/combined.locusplace
La carpeta de destino debe estar vacía o no existir. Estos pequeños ejemplos sintéticos sirven para probar el formato; el degradado y el cubo no son paisajes de calidad de producción ni un Room terminado. Su código fuente y el contenido generado tienen licencia Apache-2.0. Consulta las instrucciones del ejemplo.
--current-app-version es la versión de Locus que se utiliza como destino de la validación, es decir, la versión objetivo; no actualiza la app instalada ni añade compatibilidad con ninguna capacidad. Las herramientas utilizan de forma conservadora el valor predeterminado 1.0.0; indica explícitamente la versión prevista. Las importaciones completas requieren Locus 1.2.1 o posterior, y los campos más recientes tienen sus propios requisitos de versión mínima. Los controles de terreno y plano medio del Room requieren una compilación compatible de la versión 1.4.4 o posterior; esta guía no afirma que dicha compilación esté disponible actualmente en el App Store.
Para crear tu propio paquete, descomprime un ejemplo en una carpeta editable o crea la estructura que se muestra a continuación. Sustituye sus recursos y procedencia, utiliza tus propios ID estables y empaqueta la salida fuera de esa carpeta:
python3 tools/pack_locusplace.py --current-app-version 1.4.4 MyEditablePlace MyPlace.zip
python3 tools/validate_locusplace.py --current-app-version 1.4.4 --json MyPlace.zip
La herramienta de empaquetado recalcula files y contentHash, valida el archivo y solo entonces lo publica. Se niega a sobrescribir una salida existente. No modifica la fuente editable. Importa el resultado mediante Places → Importar → Importar un View o Importar un Room → Archivos, eligiendo una opción de importación que corresponda al tipo de contenido incluido. Un paquete combinado instala todo su contenido junto. También sirve una URL HTTPS pública directa al ZIP. Comprueba cada asiento, efecto, luz y sonido creado en Apple Vision Pro antes de compartir un lugar terminado.
Estructura del archivo
example.locusplace
├── locusplace.json
└── catalog/
├── destinations/<destination-id>/
│ ├── destination.json
│ ├── provenance.json
│ └── ...
├── spaces/<space-id>/
│ ├── space.json
│ ├── provenance.json
│ └── ...
└── experiences/<experience-id>/
└── experience.json
Cada miembro que no sea una carpeta, salvo el archivo de la raíz locusplace.json debe declararse en files. Todos los archivos declarados deben existir. El archivo solo puede contener las tres colecciones conocidas del catálogo, y cada archivo debe estar dentro de un ID de paquete declarado por contents. Los manifiestos de Package v2 mantienen la compatibilidad futura con campos desconocidos, pero se rechazan los campos desconocidos de la envoltura y las versiones de formato, colecciones y modos de contenido no admitidos. La procedencia debe seguir su esquema publicado.
Un archivo exclusivo de View o de Room instala contenido reutilizable del catálogo. Una vez resuelto el catálogo, Locus deriva las combinaciones predeterminadas: cada View con Your Room, y cada View con cada Room que tenga una abertura hacia el destino. Una Experience explícita sustituye únicamente la combinación que identifica y puede referirse a contenido del mismo archivo o a un ID estable ya presente en el catálogo integrado o importado.
locusplace.json
{
"formatVersion": 1,
"packageID": "place.example-combined",
"contentVersion": "1.0.0",
"minimumAppVersion": "1.0.0",
"contents": {
"destinationIDs": ["destination.example-view"],
"spaceIDs": ["space.example-room"],
"experienceIDs": ["experience.example-combined"]
},
"files": [
{
"path": "catalog/destinations/destination.example-view/destination.json",
"byteCount": 214,
"sha256": "<64 lowercase hex characters>"
}
],
"contentHash": "<64 lowercase hex characters>"
}
packageID y todos los ID de contenido son identificadores ASCII estables de hasta 128 bytes. Comienzan por una letra o un dígito y después solo utilizan letras, dígitos,
., _, y -. Las versiones tienen exactamente tres componentes numéricos. Una actualización que cambie los bytes debe conservar los mismos ID de contenido declarados y utilizar un valor mayor de contentVersion. Importar el mismo contentHash es una operación idempotente que no produce cambios.
contentHash es el SHA-256 de todos los registros de archivo ordenados por ruta Unicode. Cada registro aporta estos bytes UTF-8:
path NUL decimal-byteCount NUL lowercase-sha256 NUL
Este hash es independiente de los espacios en blanco del JSON y del orden de los miembros del ZIP. El validador también lee en flujo y verifica el número real de bytes, el CRC de ZIP y el SHA-256 de cada archivo declarado.
Referencia de campos de la envoltura
| Campo | Obligatorio | Significado |
|---|---|---|
formatVersion |
sí | Entero 1; se rechazan las versiones de envoltura desconocidas. |
packageID |
sí | Identidad estable de este flujo de instalación/actualización; no es un nombre de archivo. |
contentVersion |
sí | Tres componentes numéricos, como 1.2.0; auméntala cuando cambien los bytes bajo los mismos ID. |
minimumAppVersion |
sí | Versión más antigua de Locus que puede importar el archivo, también con tres componentes numéricos. |
contents.destinationIDs |
sí | ID únicos de View pertenecientes al ZIP; utiliza [] cuando no haya ninguno. |
contents.spaceIDs |
sí | ID únicos de Room pertenecientes al ZIP; utiliza [] cuando no haya ninguno. |
contents.experienceIDs |
sí | ID únicos de Experience pertenecientes al ZIP; utiliza [] cuando no haya ninguno. |
files |
sí | Inventario completo generado de todos los archivos de contenido, excepto locusplace.json. |
files[].path |
sí | Ruta canónica relativa al archivo: catalog/... . |
files[].byteCount |
sí | Número exacto de bytes descomprimidos. |
files[].sha256 |
sí | SHA-256 de los bytes descomprimidos, 64 caracteres hexadecimales en minúscula. |
contentHash |
sí | Identidad SHA-256 del inventario canónico de contenido descrito anteriormente. |
Metadatos de Package v2 dentro del ZIP
La envoltura completa utiliza formatVersion: 1; cada destination.json,
space.json y experience.json utiliza formatVersion: 2. Los números de Room v1–v10 de la referencia del formato plano de compatibilidad
pertenecen al formato plano independiente space.json de entrada. No cambies un paquete completo
space.json a la versión 10 para usar capas de iluminación precalculadas. En los paquetes completos, las capacidades opcionales se activan mediante sus campos con tipos definidos y están protegidas por
minimumAppVersion.
Todas las rutas de recursos del paquete son relativas a la carpeta que contiene su manifiesto, no a la raíz del ZIP. Cada referencia debe resolverse a un archivo normal dentro de ese paquete. Los manifiestos base de Package v2 permiten metadatos con compatibilidad futura; cada perfil de capacidad puede utilizar conjuntos cerrados de campos. No des por hecho que un campo desconocido se renderizará. La envoltura externa rechaza los campos desconocidos, y la procedencia debe seguir su esquema publicado.
Las coordenadas usan un sistema de mano derecha, en metros, con +Y hacia arriba y −Z hacia delante. Los cuaterniones se expresan como
[x, y, z, w]. Una transformación puede contener translationMeters (tres números finitos), orientationXYZW (cuatro números finitos que forman un vector de longitud no nula), y
scale (tres números finitos positivos).
View: destination.json
{
"formatVersion": 2,
"id": "destination.example-view",
"title": "Example View",
"panorama": {
"path": "panorama.jpg",
"projection": "equirectangular",
"width": 8192,
"height": 4096,
"initialYawDegrees": 0
},
"provenance": "provenance.json"
}
| Campo | Obligatorio | Significado y validación |
|---|---|---|
formatVersion |
sí | Entero 2. |
id |
sí | ID estable del View; coincide con su carpeta y con el ID de la envoltura. |
title |
sí | Nombre del View visible para el usuario y no vacío. |
panorama.path |
sí | JPEG o PNG final con ruta relativa al paquete; los bytes decodificados, y no solo la extensión, determinan el tipo. |
panorama.projection |
sí | Exactamente equirectangular. |
panorama.width, height |
sí | Dimensiones decodificadas positivas, declaradas con exactitud, con width == 2 * height. |
panorama.initialYawDegrees |
no | Rotación finita dentro del sistema de referencia canónico del panorama; valor predeterminado 0. |
sourceImage |
no | Imagen de auditoría JPEG/PNG opcional con ruta relativa al paquete. No se renderiza, pero los paquetes completos pueden conservarla como datos de tipo definido, decodificados y sujetos a límites de recursos. Los archivos fuente editables/DCC permanecen fuera del ZIP de ejecución. |
thumbnail |
no | Imagen de catálogo JPEG/PNG con ruta relativa al paquete. |
caption |
no | Descripción legible para personas. |
generation.generator |
no | Nombre veraz del generador o herramienta. |
generation.model |
no | Versión opcional del modelo/flujo de trabajo. |
generation.generatedAt |
no | Marca de tiempo opcional; se recomienda RFC 3339 UTC. |
depthLayers[] |
no en Package v2; no admitido en importaciones ZIP públicas | Recursos de profundidad ordenados reservados. La importación pública rechaza el campo hasta que un motor de producto y un validador a nivel de bytes utilicen su codificación. |
midground.path |
no | USDZ de plano medio renderizado durante la ejecución en ambos modos de experiencia; se aplica la misma lista de miembros permitidos, estructura y límites de recursos a los paquetes integrados, de Library e importados. Consulta la referencia del plano medio. |
midground.transform |
no | Transformación finita opcional para midground.path. |
midground.terrainEntities |
no | Nombres únicos y no vacíos de unidades de terreno en midground.path; el motor de ejecución los utiliza para nivelar el terreno bajo la huella del Room. |
midground.water |
no | Declaración de agua renderizada por la app, que incluye máscara/mapa de normales validados y los datos opcionales de orilla descritos en la referencia de campos. |
sky |
no | Cielo nocturno procedural renderizado por la app (estrellas, Vía Láctea y aurora opcional), visible mediante una imagen en escala de grises 2:1 validada de sky.mask; requiere minimumAppVersion >= 1.2.4. Consulta la referencia del cielo. |
gpuTexture, gpuTextures |
no | Archivos auxiliares ASTC KTX1 validados, disponibles tanto para ZIP de usuarios como para paquetes de Library. El archivo auxiliar de panorama v1 requiere 1.2.3; el array de imágenes fuente de v2 requiere 1.3.0. Los hashes de origen, las dimensiones y los niveles mip deben coincidir. |
water, clouds, fireflies |
no | Declaraciones de agua en ejecución (incluido el resplandor), movimiento de nubes pintadas y luciérnagas; requieren minimumAppVersion >= 1.3.0. Los recursos referenciados y sus límites siguen el contrato común de Package v2. |
environment.sunPath |
no | Cielo creado sin sol y disco solar enmascarado opcional; requiere 1.3.0 y su declaración de autoría. |
celestial |
no | Cuerpo celeste animado y texturas referenciadas; requiere 1.2.4, o la versión mínima más reciente que exijan sus funciones opcionales (el movimiento/evolución de la capa de nubes requiere 1.3.0). |
nearOrbit |
no | Perfiles de órbita cercana, recursos de textura y prueba de alternativa vinculada al contenido. v1 y v2 requieren 1.3.0; v2 incluye el manifiesto de teselas, las imágenes de las teselas y sus archivos auxiliares GPU. |
splat.variants, splat.ply |
no en Package v2; no admitido en importaciones ZIP públicas | Referencias reservadas para recursos futuros. Se ha eliminado el motor splat, por lo que la importación pública rechaza el campo completo. |
splat.metricScaleFactor |
no en Package v2; no admitido en importaciones ZIP públicas | Escala positiva y finita en el contrato reservado. |
splat.groundPlaneOffset |
no en Package v2; no admitido en importaciones ZIP públicas | Desplazamiento finito del terreno definido por el autor en el contrato reservado. |
splat.placement |
no en Package v2; no admitido en importaciones ZIP públicas | Transformación compartida opcional del contrato reservado. |
environment |
no | Brillo del View visible, iluminación SDR pequeña y opcional del Room y valores predeterminados de sol directo que figuran a continuación. |
audio.path |
no en Package v2; no admitido en importaciones ZIP públicas | Archivo de audio de ejecución reservado. La importación pública rechaza el campo hasta que el producto admita la validación completa de los bytes de audio y su reproducción. |
audio.loops |
con audio de Package v2; no admitido en importaciones ZIP públicas | Booleano que controla la repetición en el contrato reservado. |
provenance |
sí para importaciones | JSON de procedencia con ruta relativa al paquete. |
Los ZIP completos aceptan los campos que esta compilación realmente utiliza, además de los metadatos conservados de forma explícita, como sourceImage, cuando su contenido pueda validarse como datos de tipo definido sujetos a límites de recursos. midground forma parte del contrato común de ejecución y requiere minimumAppVersion >= 1.2.1.
depthLayers, splat, y el formato heredado audio siguen reservados o retirados sin un consumidor actual en ejecución, por lo que esta compilación los rechaza cualquiera que sea su origen, en lugar de aceptar una ruta sin comprobar su tipo. Cuando una compilación posterior implemente una de esas capacidades, deberá añadir al contrato común las referencias a recursos del campo, las comprobaciones de tipo y límites, el consumidor en ejecución y la versión mínima de la app; no deberá añadir un interruptor separado para la importación pública.
Se recomienda 12.288×6144 (12K), pero no es obligatorio. Las resoluciones inferiores, como 8192×4096, 6144×3072 o 4096×2048, son válidas si tienen una proporción estricta de 2:1 y se declaran con sus dimensiones reales decodificadas. No amplíes una fuente pequeña solo para afirmar que es 12K: no crea detalle y desperdicia memoria.
Las coordenadas del panorama son fijas: u = 0.5 es yaw 0 y la dirección frontal inicial/−Z mundial; u aumenta hacia el este, hacia +X; la costura está en u = 0/1, yaw ±180°; y la fila superior de la imagen es el cenit. initialYawDegrees gira la imagen dentro de este sistema de referencia sin redefinirlo.
Iluminación opcional del View:
{
"environment": {
"imageBasedLight": "thumbnail.jpg",
"skyGainEV": 0,
"exposureEV": 0,
"horizonPitchDegrees": 0,
"colorGrade": {"contrast": 1.15, "saturation": 1.2},
"directSun": {"enabled": false, "illuminanceLux": 5000}
}
}
| Campo | Significado |
|---|---|
imageBasedLight |
JPEG/PNG 2:1 separado que se utiliza solo para iluminar la geometría del Room y los reflejos. No debe reutilizar panorama.path; se recomienda 1024×512. Si se omite, el renderizador utiliza una alternativa neutra. |
skyGainEV |
Ganancia EV finita aplicada solo al panorama visible. |
exposureEV |
Ganancia EV finita aplicada solo a la IBL. |
horizonPitchDegrees |
Corrección finita del horizonte definida por el autor. |
colorGrade.contrast |
Contraste opcional del panorama SDR visible desde 0.5 hasta 2.0; 1.0 es neutro. |
colorGrade.saturation |
Saturación opcional del panorama SDR visible desde 0.0 hasta 2.0; 1.0 es neutro. |
directSun.enabled |
Indica si se ha definido una luz direccional independiente. |
directSun.azimuthDegrees, elevationDegrees |
Ángulos de dirección finitos; ambos son obligatorios cuando está activado. |
directSun.illuminanceLux |
Iluminancia finita no negativa. |
La gradación de color solo afecta al panorama SDR mostrado. Reutiliza esa textura y no convierte el View a HDR ni añade detalles en las altas luces que no existan en la fuente. Un sol desactivado puede conservar 5000 lux, por lo que Añadir luz solar comienza con el valor predeterminado de la app sin cambiar el estado desactivado definido en el paquete.
visibleSkyHDR y hdrManifest están retirados y se rechazan. Un EXR externo al USDZ no puede utilizarse como View visible ni como mapa de iluminación del Room. OpenEXR sigue siendo un formato de textura USDZ permitido para los recursos del Room y se valida dentro del modelo.
Room: space.json
{
"formatVersion": 2,
"id": "space.example-room",
"title": "Example Room",
"scene": "scene.usdz",
"thumbnail": "thumbnail.jpg",
"caption": "A quiet house arranged around a sunlit courtyard.",
"previewCamera": {
"yawDegrees": -28,
"pitchDegrees": -10,
"zoom": 1.0
},
"seatedOrigin": {
"translationMeters": [0, 0, 0],
"orientationXYZW": [0, 0, 0, 1]
},
"safeHeadVolume": {
"centerMeters": [0, 1.2, 0],
"sizeMeters": [1.2, 0.6, 1.2]
},
"destinationOpenings": [
{
"id": "front",
"transform": {
"translationMeters": [0, 1.2, -2],
"orientationXYZW": [0, 0, 0, 1]
},
"widthMeters": 4,
"heightMeters": 2.5
}
],
"collision": {"mode": "embedded"},
"teleportCatalog": {"path": "teleport-points.json", "houseID": "example-room"},
"quality": {
"status": "unverified",
"triangleCount": 0,
"materialCount": 0,
"entityCount": 0,
"maxTextureDimension": 0
},
"provenance": "provenance.json"
}
| Campo | Obligatorio | Significado y validación |
|---|---|---|
formatVersion |
sí | Entero 2. |
id |
sí | ID estable del Room; coincide con su carpeta y con el ID de la envoltura. |
title |
sí | Nombre del Room visible para el usuario y no vacío. |
scene |
sí | USDZ autónomo con ruta relativa al paquete. .blend y otros archivos de autoría no son sustitutos y no pueden incluirse en otra ubicación. |
thumbnail |
no | JPEG o PNG decodificado con ruta relativa al paquete que aparece en la tarjeta del Room. Si se declara, debe existir y decodificarse por completo; comparte los límites de imágenes del archivo. Omítelo para usar el símbolo predeterminado. |
caption |
no | Texto no vacío para el usuario de Acerca de este Room, hasta 2000 bytes UTF-8. |
previewCamera.yawDegrees, pitchDegrees |
no | Ángulos iniciales finitos para la vista previa 3D acotada. No afectan a la colocación inmersiva. |
previewCamera.zoom |
con la cámara | Escala inicial finita y positiva para la vista previa 3D acotada. Restablecer vuelve a esta pose definida por el autor; el visor la limita al intervalo de interacción admitido. |
seatedOrigin.translationMeters |
sí | Tres valores finitos en metros que sitúan la referencia del suelo del asiento definida por el autor. |
seatedOrigin.orientationXYZW |
sí | Cuatro componentes de cuaternión normalizados y finitos; el sistema de referencia del asiento debe permanecer vertical, sin inclinación ni alabeo. |
safeHeadVolume.centerMeters |
sí | Tres valores finitos en metros en coordenadas del Room. |
safeHeadVolume.sizeMeters |
sí | Tres dimensiones finitas y estrictamente positivas. |
destinationOpenings |
sí | Array de aberturas rectangulares. Una importación independiente de solo Room necesita exactamente una; con cero o varias aberturas se requiere una virtual-space Experience en el mismo archivo. |
destinationOpenings[].id |
por entrada | ID único de paquete utilizado para la composición de Experiences. |
destinationOpenings[].transform |
por entrada | Pose obligatoria de posición/cuaternión. |
destinationOpenings[].widthMeters, heightMeters |
por entrada | Dimensiones finitas y estrictamente positivas. |
collision.mode |
sí | embedded o separate. |
collision.path |
condicional | Prohibido para embedded; para separate, un USDZ autónomo obligatorio con una ruta relativa al paquete, validado con las mismas reglas de miembros, texturas y geometría que scene. Su geometría y la de la escena comparten los límites del Room. No se admiten archivos fuente de autoría con el nombre cambiado. |
quality.status |
sí | unverified o verified. |
quality.triangleCount, materialCount, entityCount, maxTextureDimension |
sí | Mediciones no negativas del autor. Cero significa sin medir; el USD real se comprueba de forma independiente. |
teleportCatalog.path |
sí | JSON de teletransporte con ruta relativa al paquete. La casa seleccionada debe contener al menos un punto; de lo contrario, el Room no puede entrar en Virtual Space. Su límite JSON de 1 MiB se aplica según la función del campo, incluso si el nombre de archivo no termina en .json; los alias del sistema de archivos por mayúsculas/minúsculas o Unicode no eluden esta regla. |
teleportCatalog.houseID |
sí | ID que selecciona un elemento no vacío de houses array de ese JSON. |
spatialAdaptation.wallEntities |
no | Nombres únicos y no vacíos de entidades USD para paredes; estos metadatos no las ocultan. |
spatialAdaptation.roofEntities |
no | Nombres únicos y no vacíos, distintos de los de las paredes. |
spatialAdaptation.deskEntitiesByTeleportID |
no | Mapa del ID de teletransporte declarado al nombre no vacío de la entidad de escritorio definida por el autor. |
provenance |
sí | JSON de procedencia con ruta relativa al paquete. |
El USDZ usa metros y un sistema de mano derecha, con +Y hacia arriba, y solo contiene miembros USD, de textura y de audio admitidos en ejecución. Los miembros del USDZ deben almacenarse sin compresión. El validador carga y aplana la escena real y cuenta su geometría; los valores pequeños o nulos de quality no pueden ocultar un modelo que supere los límites.
JSON de teletransporte obligatorio (la casa seleccionada debe contener al menos un punto):
{
"houses": {
"example-room": [
{
"id": "work-desk",
"title": "Work desk",
"anchorXZ": [0.5, 0.5],
"sourceFloorOffset": 0,
"eyeHeight": 1.15,
"yawRadians": 0
}
]
}
}
anchorXZ son dos valores finitos normalizados en 0...1; sourceFloorOffset es finito y no negativo, en unidades del modelo creado; eyeHeight es positivo, finito y se mide en metros; yawRadians es una rotación finita del modelo alrededor de +Y en el sistema de referencia mundial. Los ID de punto son únicos y los títulos no están vacíos.
Experience: experience.json
{
"formatVersion": 2,
"id": "experience.example-combined",
"title": "Example Room · Example View",
"destinationID": "destination.example-view",
"spaceID": "space.example-room",
"supportedModes": ["virtual-space"],
"composition": {
"destinationOpeningID": "front",
"destinationYawDegrees": 0,
"destinationPitchDegrees": 0,
"destinationExposureOffsetEV": 0
},
"compatibility": {
"minimumViewerVersion": "1.0.0",
"requiredCapabilities": ["world-tracking"]
}
}
| Campo | Obligatorio | Significado y validación |
|---|---|---|
formatVersion |
sí | Entero 2. |
id |
sí | ID estable de Experience. |
title |
sí | Nombre de tesela visible para el usuario y no vacío. |
destinationID |
sí | ID estable del View en este ZIP o ya instalado. |
spaceID |
condicional | ID estable del Room; obligatorio para virtual-space, que suele omitirse para room-portal. |
supportedModes |
sí | Array no vacío y sin duplicados que contenga solo virtual-space o room-portal. |
composition.destinationOpeningID |
condicional | Obligatorio para virtual-space; identifica una abertura del Room referenciado. |
composition.destinationYawDegrees |
no | Desplazamiento finito de alineación del cielo para esta combinación. |
composition.destinationPitchDegrees |
no | Desplazamiento finito de inclinación del cielo para esta combinación. |
composition.destinationExposureOffsetEV |
no | Desplazamiento de exposición relativo finito para esta combinación. |
compatibility.minimumViewerVersion |
no | Cadena declarativa de versión mínima del visor. |
compatibility.requiredCapabilities |
no | Cadenas declarativas de capacidades. |
Los ZIP de solo View y de solo Room instalan contenido reutilizable y se incorporan a la matriz de composición predeterminada. Solo hace falta una Experience explícita para sustituir la abertura, orientación, exposición, título, modo o compatibilidad de una combinación. Las referencias pueden resolverse en el mismo archivo o mediante ID estables integrados o ya instalados; una referencia sin resolver hace fallar toda la importación.
Capacidades opcionales y etiquetas traducidas
Añade los campos opcionales al manifiesto del View o Room al que pertenecen e incluye junto a él todos los recursos referenciados. La referencia de campos
abarca medios y máscaras, sonido y música del Room, plano medio y viento, agua, cielo, nubes, trayectoria solar, cuerpos celestes, órbita cercana, agujeros negros, luces puntuales, archivos auxiliares de texturas GPU, asientos y grupos, luces, animaciones, superficies de vídeo, partículas y capas de iluminación precalculadas. También define localizations,
provenanceDetail, y el valor del Room de viewGroundFloorOffsetMeters,
viewGroundKeepOut, y viewMidgroundVisibility controles.
Por ejemplo, estos campos opcionales pertenecen a un manifiesto de Room completo y requieren en la envoltura minimumAppVersion: "1.4.4" o posterior:
{
"viewGroundFloorOffsetMeters": 0.2,
"viewGroundKeepOut": false,
"viewMidgroundVisibility": "terrainOnly"
}
Conservan su significado para contenido importado, integrado y de Library. Los nombres de capacidades de Library en la referencia de campos pertenecen a las ofertas de Library; no añadas un valor inventado de requiredCapabilities campo a locusplace.json. La ruta de importación de paquetes completos comprueba la versión mínima de la app y el propio contenido con su tipo definido.
Procedencia obligatoria
Cada Destination y Space importado apunta a un documento JSON de procedencia. Registra el creador/agencia, exactamente una licencia o una declaración de reserva de todos los derechos, el crédito solicitado, las notas de modificación, si se utilizó IA y, cuando corresponda, el proveedor de IA. Las URL de los recursos fuente/originales son opcionales, pero deben ser HTTPS y no incluir credenciales ni fragmentos. La ausencia de procedencia, su tamaño excesivo o un formato incorrecto hacen fallar toda la importación. Consulta la referencia de procedencia.
{
"formatVersion": 1,
"creatorOrAgency": "Your name or studio",
"sourcePageURL": "https://example.com/project",
"originalAssetURL": "https://example.com/original",
"license": {
"identifier": "YOUR-LICENSE-ID",
"name": "Your license name",
"url": "https://example.com/license"
},
"requestedCredit": "Credit line requested by the creator",
"modificationNotes": "Converted and packaged for Locus.",
"aiGenerated": false
}
| Campo | Obligatorio | Significado y restricciones |
|---|---|---|
formatVersion |
sí | Entero 1. |
creatorOrAgency |
sí | Nombre no vacío del creador/estudio, de hasta 200 caracteres. |
sourcePageURL |
no | Página pública HTTPS del proyecto, sin credenciales ni fragmento. |
originalAssetURL |
no | Página pública HTTPS de la fuente; registra el origen sin incluir el archivo original. |
license.identifier |
condicional | Identificador no vacío, de hasta 100 caracteres; obligatorio con license. Locus no elige la licencia del recurso. |
license.name |
condicional | Nombre no vacío, de hasta 200 caracteres; obligatorio con license. |
license.url |
condicional | URL HTTPS pública de la licencia; obligatoria con license. |
rights.statement |
condicional | Declaración de derechos no vacía, de hasta 1000 caracteres; úsala junto con rights.url en lugar de license. |
rights.url |
condicional | URL HTTPS pública de la declaración de derechos; exactamente una de license o rights es obligatorio. |
requestedCredit |
sí | Línea de crédito no vacía, de hasta 1000 caracteres. |
modificationNotes |
sí | Notas de preparación no vacías, de hasta 2000 caracteres. |
aiGenerated |
sí | Booleano. |
aiProvider |
condicional | Obligatorio y no vacío cuando aiGenerated es true; está prohibido cuando es false; como máximo, 200 caracteres. |
importSource |
no | Reservado para un View que Locus crea directamente a partir de una imagen: photos, files, o url. Los paquetes de terceros deben omitirlo. |
El documento está limitado a 64 KiB. No copies la procedencia de una plantilla después de sustituir sus recursos. La licencia del repositorio y la de los recursos del usuario son declaraciones independientes.
El ZIP es una entrega para ejecución, no una copia de seguridad de autoría. Todos los archivos de contenido deben ser accesibles desde un campo de recurso reconocido de Package v2; los archivos sin referencias se rechazan aunque aparezcan en locusplace.json.files. .blend, .blend1,
.blend2, .fbx, .obj, .mtl, .gltf, y .glb se rechazan aunque se declaren. Mantén esos archivos fuente fuera del ZIP. Un Room distribuye el USDZ final; un View empaquetado distribuye el panorama 2:1 final y, de forma opcional, una imagen JPEG/PNG 2:1 pequeña para iluminar el Room.
Seguridad y límites de recursos
La validación examina los bytes, no las extensiones. Rechaza los recorridos fuera de la carpeta, las rutas absolutas o no canónicas, las barras invertidas, los caracteres de control, las colisiones por mayúsculas/minúsculas o Unicode, los enlaces simbólicos, las entradas duplicadas, el contenido cifrado, los archivos sin declarar, los hashes incorrectos, los manifiestos no admitidos, las imágenes malformadas, las texturas EXR inválidas dentro del USDZ, las estructuras USDZ malformadas, los miembros USDZ comprimidos y los tipos de recursos USDZ no admitidos. Los límites del Room se comprueban en la escena USD que el cargador del sistema realmente analiza; el bloque de calidad autodeclarado es un metadato de autoría y no puede reducir esos recuentos.
La lista de imágenes permitidas a nivel de bytes es exacta: el panorama importado, sourceImage,
thumbnail, y imageBasedLight de imagen ráster deben ser JPEG o PNG; las texturas USDZ pueden ser JPEG, PNG u OpenEXR. Se rechazan otros formatos que ImageIO pueda decodificar, en lugar de aceptarlos implícitamente. También se rechazan los formatos de autoría 3D externos al USDZ. Un USDZ solo puede contener miembros USD/USDA/USDC, JPEG, PNG, OpenEXR, M4A, WAV y MP3.
| Límite | límite de v1 |
|---|---|
| Descarga o archivo local | 1 GiB |
| Entradas del ZIP | 4,096 |
| Manifiesto raíz | 1 MiB |
| Cualquier archivo de contenido | 768 MiB |
| Contenido descomprimido total | 2 GiB |
| Relación de compresión | 200:1 |
| Cualquier archivo JSON importado | 1 MiB |
| JSON de procedencia | 64 KiB |
| Dimensiones de imagen | 32.768 px |
| Una imagen | 150 millones de píxeles |
| Todas las imágenes del View | 500 millones de píxeles |
| Entradas de USDZ | 4,096 |
| Archivo USDZ / bytes descomprimidos | 768 MiB / 1,5 GiB |
| Dimensiones de textura USDZ / píxeles totales | 16.384 px / 500 millones |
| Geometría del Room cargada | 5 millones de triángulos, 1024 materiales, 100.000 entidades |
Obtención desde Archivos y URL
Para un View, la página Importaciones acepta una imagen JPG, PNG o HEIC desde Fotos, Archivos o una URL HTTPS pública directa. Locus normaliza la imagen en un contenido Package v2 exclusivo de Destination y pasa ese paquete transitorio por el mismo validador e instalador atómico. Archivos y URL también aceptan ZIP completos de Package v2 para Views y Rooms, incluidos los paquetes combinados que contengan el tipo de contenido seleccionado por el visitante. Los ZIP planos existentes de View/Room siguen aceptándose como entradas de compatibilidad. Un archivo malformado que contenga locusplace.json se rechaza como paquete completo inválido; nunca se recurre al analizador plano. Las importaciones por URL rechazan credenciales, fragmentos, localhost, .local, y resultados DNS no públicos. El nombre de host se resuelve una vez por solicitud; todas las respuestas deben ser públicas y el socket TLS se conecta a esa dirección IP numérica exacta mientras autentica el nombre de host original. Cada redirección repite este paso de resolución y fijación de IP antes de abrir otra conexión. Se siguen como máximo cinco redirecciones y se limitan tanto el tamaño de descarga declarado como el recibido.
La validación y la resolución del catálogo se completan en una carpeta privada de preparación en el mismo volumen. Un bloqueo consultivo para toda la biblioteca serializa el inventario, las comprobaciones de cuota y la confirmación. La primera instalación utiliza un renombrado atómico exclusivo; las actualizaciones utilizan un intercambio atómico de carpetas. Cualquier fallo elimina la preparación y deja intacto el paquete instalado.
Validación de paquetes y ejemplos
Los comandos siguientes mantienen la envoltura completa de Package v2 utilizada para la publicación y las importaciones con todas las funciones. Las herramientas planas heredadas siguen documentadas en
locus-asset-format.md.
Valida cualquiera de los nombres de archivo sin instalarlo:
python3 tools/validate_locusplace.py --current-app-version 1.4.4 MyPlace.locusplace
python3 tools/validate_locusplace.py --current-app-version 1.4.4 MyPlace.zip
python3 tools/validate_locusplace.py --current-app-version 1.4.4 --json MyPlace.locusplace
El código Python utiliza la biblioteca estándar, ImageIO de macOS mediante /usr/bin/sips, y las herramientas USD usdchecker y usdcat (incluidos con Xcode/Command Line Tools). ImageIO decodifica todos los píxeles después de validar la estructura JPEG o PNG; la validación de EXR se limita a las texturas dentro del USDZ, y las herramientas USD cargan, aplanan y miden la escena real del Room. Si se produce un error, termina con un código distinto de cero. Comprueba el archivo, la integridad, Package v2, la procedencia, las imágenes/EXR y el USDZ. El importador de visionOS sigue siendo la autoridad para la instalación y la resolución entre catálogos.
Empaqueta una carpeta editable en un ZIP normal, recalculando todos los registros de integridad y rechazando la publicación salvo que este validador acepte el resultado:
python3 tools/pack_locusplace.py --current-app-version 1.4.4 MyEditablePlace MyPlace.zip
Genera archivos de ejemplo reales de solo View, solo Room y combinados en una carpeta temporal o ignorada, y después valídalos:
python3 examples/locusplace/build_examples.py /tmp/locusplace-examples
for package in /tmp/locusplace-examples/*.locusplace; do
python3 tools/validate_locusplace.py --current-app-version 1.4.4 "$package"
done
Los ejemplos se generan a partir del código fuente sin descargar recursos privados ni llamar a un servicio de pago. La herramienta de creación y las estructuras esperadas se documentan en el README del ejemplo.