IFC Viewer SDK v1.18.0 Abrir el visor
Español

IFC Viewer SDK · v1.18.0

Integra el visor IFC en tu propio producto

Inserta el mismo visor del navegador en tu CDE, gemelo digital o herramientas internas de proyecto. Carga datos IFC desde tu app mientras cada modelo se parsea en el cliente.

Tres líneas hasta el primer render

JavaScript
const viewer = new IfcViewer("#viewer");

await viewer.add("project.ifc", ifcBytes);
// → rendered, validated, all on the client

Puesta en marcha

Inicio rápido

Elige tu stack. Copia, pega y renderiza — sin paso de build, sin API key.

HTML
<div id="viewer" style="height:520px"></div>
<script type="module">
  import { IfcViewer } from "https://www.ifcvieweronline.eu/sdk/ifc-viewer.es.js";

  const viewer = new IfcViewer("#viewer");

  // Load IFC bytes from your app — nothing is uploaded
  const bytes = await fetch("/models/project.ifc").then(r => r.arrayBuffer());
  await viewer.add("project.ifc", bytes);
</script>

Alojado por nosotros o por ti

Los ejemplos importan desde nuestra CDN, que funciona en cualquier origen. Para alojarlo tú, sirve ifc-viewer.es.js desde tu propio dominio: el SDK descubre la URL de la app relativa al script. ¿Sirves la app en otro sitio? Usa la opción baseUrl.

Todavía no hay paquete npm: importa el módulo por URL (arriba) o incorpora ifc-viewer.es.js a tu app.

Demostración

Demo en vivo

Esta página está ejecutando el SDK. Pulsa un control para descargar un IFC de ejemplo y manejar el visor — cada botón muestra la llamada que hace.

inactivo
Última llamada —

Arquitectura

Cómo funciona

Un wrapper mínimo en tu página, el visor completo en un iframe aislado, tus bytes cruzando entre ambos — y procesados donde ya estaban.

Navegador del visitante — no se sube nadaTu aplicaciónSDK · ~6 KBiframe aisladothree.js · web-ifc · WASMBytes IFCpostMessage

Tu aplicación

Un módulo ES de ~6 KB que crea un iframe y una API JS tipada. Nada del peso de 3D ni de parseo toca tu bundle.

Puente postMessage

Los bytes IFC se transfieren al iframe como ArrayBuffer (sin copia). Los comandos y las consultas son mensajes correlacionados de petición/respuesta.

El visor

three.js + web-ifc compilado a WebAssembly parsean y renderizan el modelo dentro del iframe — en la máquina del visitante.

Por qué es privado

Los modelos nunca llegan a un servidor. Ninguna cadena de subida que proteger, ninguna duda sobre residencia de datos, ninguna cola por tamaño de archivo.

Por qué escala

El cómputo corre en el dispositivo de cada visitante. Servir un modelo de 200 MB cuesta lo mismo que uno de 2 MB: es un archivo estático.

Conceptos

Conceptos clave

Cuatro cosas que conviene saber antes de construir.

Montaje y readiness

new IfcViewer(target) monta de inmediato y encola los comandos hasta que el visor está listo. await IfcViewer.create(target) (o whenReady()) te da una instancia que puedes usar directamente.

JavaScript
// queues commands until ready
const viewer = new IfcViewer("#viewer");

// …or await an instance that is ready to use
const viewer = await IfcViewer.create("#viewer");

Bytes o URL

add(name, bytes) transmite bytes que ya tienes — sin CORS y sin subir nada; el buffer se transfiere (queda detached), así que pasa una copia si aún lo necesitas. addFromUrl(url) descarga una URL pública con CORS habilitado.

JavaScript
// you already have the bytes — no CORS, nothing uploaded
await viewer.add("a.ifc", bytes);

// …or load a public, CORS-enabled URL
await viewer.addFromUrl("https://host/b.ifc");

Eventos

Suscríbete con on(event, cb), que devuelve una función para cancelar la suscripción. Dispones de ready, model-progress, model-loaded, validation-completed, model-error y element-selected.

JavaScript
const off = viewer.on("model-loaded", (m) => {
  console.log(m.fileName, m.elementCount);
});
// later: off();

Consultas

Extrae datos bajo demanda: getModels, getElement, getValidation, getStats, getIssues, checkIds, checkEir, screenshot. Cada una es petición/respuesta y se rechaza tras un timeout (30 s; IDS / EIR hasta 120 s).

JavaScript
const models = await viewer.getModels();
const { qualityScore } = await viewer.getValidation();
const png = await viewer.screenshot();

Referencia

Referencia de la API

Todos los métodos, agrupados — con firmas y tipos de retorno. Después vienen las opciones del constructor, los eventos y el web component <ifc-viewer>.

Constructor

new IfcViewer(target, options?)IfcViewer

Monta en un selector CSS o elemento. Consulta las opciones abajo.

IfcViewer.create(target, options?)Promise<IfcViewer>

Crea un visor y resuelve cuando está listo para aceptar comandos.

Carga de modelos

add(name, bytes)Promise<ModelLoadedEvent>

Carga bytes IFC (ArrayBuffer/Uint8Array) desde tu app. El buffer se transfiere para un traspaso sin copia.

addFromUrl(url, name?)Promise<ModelLoadedEvent>

Carga una URL IFC pública con CORS habilitado.

removeModel(modelId)void

Descarga un modelo por id.

clear()void

Elimina todos los modelos cargados.

Cámara y selección

select(expressId | globalId, modelId?)void

Selecciona y encuadra un elemento — por su expressID IFC o por su GlobalId (que además encuentra su modelo).

setView(view)void

Vuela a una vista con nombre de la escena — iso, front, back, left, right, top, bottom — con toda ella en el encuadre. Sin respuesta (entre 1.14 y 1.17 el visor la ignoraba).

frame({ view?, fill?, azimuth?, elevation?, animate? })Promise<{ scope }>

Encuadre para presentar: el modelo ocupa `fill` del marco (0,2–0,98; por defecto 0,85), ajustado a sus esquinas y no a su esfera envolvente. azimuth / elevation miran desde cualquier ángulo.

frame(elementId, modelId?, options?)Promise<{ scope: 'element' }>

Encuadra un elemento (expressID): desde el ángulo actual salvo que se indique una vista. Rechaza si no está en el modelo.

fit() · reset()void

Encuadra el modelo activo desde el ángulo actual, con margen / reinicia la cámara.

setCamera(position, direction)void

Coloca la cámara en una posición mirando en una dirección.

getCamera()Promise<CameraState | null>

Dónde está la cámara y hacia dónde mira: guárdalo y restáuralo con lookAt.

lookAt(position, target, animate?)Promise<void>

Lleva la cámara a una posición mirando a un objetivo (metros de escena, Y arriba).

setWalkMode(enabled, { speed? })Promise<WalkState>

Modo paseo en primera persona (WASD, arrastrar para mirar, Esc para salir). Emite walk-changed.

getWalkState()Promise<WalkState>

Si el modo paseo está activo y a qué velocidad.

Visibilidad

isolate(ifcType?)void

Aísla una categoría (p. ej. "IfcWall"); omítelo para limpiar.

showAll()void

Restaura toda la visibilidad (quita ocultos y aislamiento).

hideElements(ids, modelId?) · showElements(ids, modelId?)void

Oculta / muestra un conjunto de elementos por expressID.

setModelVisible(modelId, visible)Promise<void>

Muestra u oculta un modelo de una escena federada sin descargarlo.

setModelOpacity(opacity, modelId?)Promise<void>

Hace translúcido un modelo (0,05–1) — p. ej. la arquitectura alrededor de las instalaciones.

isolateModel(modelId | null)Promise<void>

Muestra solo este modelo; null vuelve a mostrarlos todos.

Aspecto y presentación

setBackground(background)Promise<BackgroundState>

Cambia el fondo de la escena: un preset ('white', 'paper', 'blueprint', 'sky', 'studio'), '#rrggbb', '#arriba,#abajo' o { top, bottom }. No se guarda como preferencia del visitante.

getBackground()Promise<BackgroundState>

El fondo actual de la escena.

setAccent(color)Promise<void>

Cambia el color de acento de la interfaz en tiempo de ejecución (#rrggbb).

setClientMode(enabled)Promise<void>

Activa o desactiva la vista cliente — la de presentación a stakeholders, igual que ui: 'client'.

setRenderQuality(quality)Promise<void>

'quality' activa el renderizado más pesado para una imagen destacada; 'standard' para el día a día.

Estudio solar

setSolar(opts?)Promise<SolarState>

Sombras reales en una fecha y hora locales del emplazamiento: { date: '06-21', time: '18:00' }. El lugar sale de la georreferencia del IFC; pasa location si el modelo no la tiene — nunca una ciudad por defecto en silencio.

getSolar()Promise<SolarState>

Fecha, hora y zona horaria del estudio, y dónde está el emplazamiento.

Modo mapa

setSiteContext(opts?)Promise<SiteContextState>

Coloca el modelo en el mapa con terreno y entorno de OpenStreetMap. Llamarlo es tu página declarando el consentimiento de teselas por sus visitantes; muestra las atribuciones.

getSiteContext()Promise<SiteContextState>

Estado del modo mapa, la ubicación y las atribuciones que debes mostrar.

Escenas

openScene(url | SceneDocument)Promise<void>

Abre otra escena: la URL de un .scene.json o un documento de escena (viaja empaquetado en la dirección del marco, así que debe caber en un enlace). El visor se recarga con ella; las llamadas pendientes se rechazan y ready vuelve a dispararse.

exportScene({ title?, description?, camera? })Promise<SceneExport>

La escena en pantalla como documento de escena —modelos por URL, capas, gemelo, mapa, cámara— con las fuentes que hay que citar y un enlace que la contiene.

packScene(doc)Promise<string | null>

Empaqueta tú mismo un documento de escena para un enlace #scene= (null si no cabe en un enlace).

Capas de datos y gemelo

getLayerPresets()Promise<LayerPreset[]>

El catálogo de fuentes públicas en vivo, con nombre en el idioma del visor, lo que necesita cada una en un navegador (una clave, un proxy) y si tiene datos cerca del emplazamiento.

addLayer({ preset } | { url, live? } | { geojson })Promise<DataLayerInfo>

Añade una capa: una fuente del catálogo ({ preset }), GeoJSON o un feed en vivo en una URL ({ url, live }) o GeoJSON que ya tienes ({ geojson }). Solo para la sesión: nunca se guarda entre las capas del visitante.

getLayers()Promise<DataLayerInfo[]>

Todas las capas de datos: estado, recuento de elementos, extensión, refresco y atribución.

setLayerVisible(id, visible)Promise<DataLayerInfo>

Muestra u oculta una capa.

frameLayer(id)Promise<void>

Lleva la cámara a los elementos de una capa.

removeLayer(id)Promise<void>

Quita una capa.

getTwin()Promise<TwinState>

El gemelo operativo ahora: fuentes de dispositivos (estado, última lectura) y, por vínculo, la regla que se cumple, su color, el valor de la etiqueta y si está en alerta.

Secciones

addSection({ axis?, offset?, level?, flip? })Promise<SectionsState & { id }>

Añade un plano de sección. { level: 'Planta 1' } es una planta a esa altura; { axis: 'x', offset: 4.5 } una sección a 4,5 m.

updateSection(id, { offset?, enabled?, flipped? })Promise<SectionsState>

Mueve, activa/desactiva o invierte un plano.

removeSection(id?)Promise<SectionsState>

Quita un plano, o todos los cortes si no pasas id.

setSectionBox('model' | 'selection' | false)Promise<SectionsState>

Una caja de sección alrededor del modelo o de la selección; false la quita.

getSections()Promise<SectionsState>

Todos los planos, la caja y las plantas disponibles para cortes por nivel.

Tours

startTour(template?, { autoplay?, title? })Promise<TourState>

Inicia un tour predefinido: 'social', 'client-walkthrough' o 'technical-review' (recorre las incidencias de validación). Lo reproduce la propia barra de tour del visor.

playTour({ title?, steps }, { startAt?, autoplay? })Promise<TourState>

Reproduce un tour tuyo: paradas de cámara con textos, resaltados y clases aisladas. autoplay: true o ms por parada.

nextTourStep() · prevTourStep() · goToTourStep(i)Promise<TourState>

Moverse entre paradas.

setTourAutoplay(autoplay)Promise<TourState>

Activa o desactiva el avance automático del tour en curso.

stopTour()Promise<TourState>

Detiene el tour y devuelve la cámara.

getTour()Promise<TourState>

El tour y sus paradas, con la forma que acepta playTour(): guárdalo y reprodúcelo más tarde.

Director de presentaciones

getPresentationRecipes()Promise<PresentationRecipe[]>

Las recetas del director (demo para reunión, reel, teaser de LinkedIn, lanzamiento, looks editoriales…).

createPresentation(recipe?, options?)Promise<PresentationState>

Convierte el modelo en un vídeo editado a partir de una receta: tomas planificadas desde el IFC, textos, música. Abre Clip Studio con el resultado. Sigue presentation-progress.

exportPresentation({ resolution?, music? })Promise<PresentationVideo>

Codifica la presentación en el navegador del visitante y recibe los bytes del MP4: súbelos, adjúntalos o reprodúcelos en un <video>.

closePresentation()Promise<void>

Cierra Clip Studio.

Estudio de portadas

getCoverOptions()Promise<CoverCatalog>

Las recetas, plantillas, formatos y paletas que ofrece el estudio de portadas.

createCover({ recipe?, template?, format?, palette?, text? })Promise<CoverState>

Crea una portada: ejecuta una receta (captura las vistas que necesita) y luego fija plantilla, formato, paleta y textos. Abre el estudio, que sigue siendo editable.

getCover()Promise<CoverState | null>

La portada tal como está, o null si el estudio está cerrado.

exportCover({ type?, slide? })Promise<CoverFile>

Expórtala: png / jpeg (una página), pdf / pptx (todo el documento), zip (todas las páginas). Devuelve los bytes.

closeCover()Promise<void>

Cierra el estudio de portadas.

Kit de artículo

activate()void

Arranca ya un visor diferido (lo que hace el botón del póster). Las llamadas anteriores quedaron en cola.

bindSteps(steps, { rootMargin? })() => void

Scrollytelling: mientras cada párrafo cruza el centro de la pantalla, encuadra, aísla, mueve el sol o ejecuta tu código. Devuelve una función para desvincular.

setTurntable(enabled | degPerSec)Promise<{ active, speed }>

Una rotación lenta en reposo (grados por segundo; false la para). Se detiene al primer toque y nunca con movimiento reducido.

toggleFullscreen()Promise<void>

Amplía la figura a pantalla completa, o vuelve.

setPaused(paused)Promise<{ paused }>

Detiene o reanuda el pintado de fotogramas. pauseOffscreen lo hace por ti.

Grupos de escena

getGroups()Promise<SceneGroupsState>

Todos los grupos de la escena: deducidos del IFC (proyecto, emplazamiento, ubicación) y creados por usuarios.

createGroup(name, modelIds?)Promise<string>

Crea un grupo, opcionalmente con modelos dentro. Devuelve su id.

renameGroup(id, name) · deleteGroup(id)Promise<void>

Renombra o borra un grupo de usuario. Los automáticos no se editan.

assignToGroup(itemId, groupId | null | 'loose')Promise<void>

Mueve un modelo o nube de puntos a un grupo; null lo devuelve al agrupado automático y 'loose' lo deja suelto.

setGroupVisible(id, visible) · isolateGroup(id | null)Promise<void>

Muestra u oculta un grupo entero, o muestra solo uno.

frameGroup(id)Promise<void>

Encuadra la cámara en un grupo, incluidos los miembros ocultos.

Mediciones

setMeasureTool(tool)Promise<void>

Arma una herramienta de medición para el visitante (abre el panel Medir), o 'none'. Los resultados llegan en measurements-changed.

getMeasurements()Promise<MeasurementsState>

Todas las mediciones en pantalla. Los valores van en SI — metros, m², grados — se muestre lo que se muestre.

clearMeasurements(id?)Promise<MeasurementsState>

Quita una medición, o todas.

Consultas

getModels()Promise<ModelSummary[]>

Lista los modelos cargados. Devuelve una promesa.

findElements({ ifcClass?, globalId?, name?, modelId?, limit? })Promise<ElementSummary[]>

Busca elementos por clase IFC, GlobalId y/o nombre en los modelos cargados. Cada resultado trae lo que piden getElement(), select() y frame().

getElement(expressId | globalId, modelId?)Promise<IfcElementData | null>

Por expressID o por GlobalId. Los datos IFC de un elemento: atributos con el GlobalId, sus conjuntos de propiedades y cantidades, y los de su tipo — typeName, typeProperties — con unidades. effectivePropertySets combina ambos y gana el valor propio del elemento.

validate(modelId?, { force? })Promise<ValidationRunResult>

Valida un modelo ahora (por defecto, el activo) y resuelve con su Health Score y el recuento de incidencias. Rechaza con el motivo.

getValidation()Promise<ValidationSummary | null>

El resultado de validación en pantalla, o null antes de que una ejecución haya producido uno. Ver «Ciclo de la validación».

getValidationStatus()Promise<ValidationStatus>

En qué punto está la validación: idle, running (con progreso), done o error.

getStats()Promise<StatsResult>

Conteo de elementos por categoría y modelo — para gráficos de dashboard. Devuelve una promesa.

getIssues({ severity?, limit?, modelId? })Promise<IssuesResult>

Incidencias de validación para una tabla (filtra por severidad / límite). Devuelve una promesa.

screenshot()Promise<string>

Captura la vista 3D actual como data URL PNG. Devuelve una promesa.

compare({ base, head, baseLabel?, headLabel? })Promise<{ changes }>

Compara dos entregas por URL (por GlobalId) y abre el espacio de comparación con el resultado.

Paneles

openPanel(panel | null) · closePanel()void

Abre un panel de herramienta, o pasa null para cerrar el que esté abierto. Un panel no disponible — porque el chrome lo oculta o no hay nada cargado sobre lo que actuar — no hace nada, y no es un error.

getPanels()Promise<PanelsResult>

Qué panel está abierto y cuáles se ofrecen ahora mismo. Una herramienta que no aparece en `available` no se puede abrir; no está simplemente deshabilitada.

setPanels(panels)void

Limita el raíl a estos paneles, en tiempo de ejecución. Mismo vocabulario que el parámetro de URL `panels=` y tiene prioridad sobre él. Restringe lo que el visor ofrece y nunca añade; un array vacío significa sin raíl.

Nubes de puntos

addPointCloud(fileName, bytes)Promise<string>

Carga un escaneo desde bytes — LAS, LAZ, COPC, PLY, PCD o texto. El búfer se transfiere, no se copia. Resuelve con el id de la nube.

addPointCloudFromUrl(url, fileName?)Promise<string>

Carga un escaneo que descarga el propio visor. La URL debe permitir CORS.

listPointClouds()Promise<PointCloudInfo[]>

Todos los escaneos cargados. `pointCount` es lo residente; `declaredCount` es lo que contiene el archivo — difieren cuando el presupuesto truncó el análisis.

removePointCloud(cloudId) · clearPointClouds()Promise<void>

Descarga un escaneo y libera sus búferes de GPU, o descárgalos todos.

setPointCloudVisible(cloudId, visible)Promise<void>

Muestra u oculta un escaneo sin descargarlo.

fitPointCloud(cloudId?)Promise<void>

Encuadra la cámara en un escaneo, o en el primero cargado.

setPointCloudDisplay(display, renderBudget?)Promise<void>

Tamaño de punto, opacidad, modo de color, densidad, umbral de confianza — compartidos por todos los escaneos. Cada uno es un uniform del shader, así que es instantáneo con 20 millones de puntos.

inspectPointCloud(enabled?)Promise<void>

Arma el clic para leer. Mientras está armado, clicar un punto emite `pointcloud-picked` con las coordenadas DEL ARCHIVO junto a las de la escena.

setPointCloudPlacement(placement, cloudId?)Promise<void>

Ajusta un escaneo a mano: posición, giro, nivelación, escala. Parcial y acotado; se aplica sobre la alineación deducida en vez de sustituirla.

setPointCloudUpAxis(axis, cloudId?)Promise<void>

Corrige qué eje considera vertical el escaneo. PLY, PCD y texto no declaran orientación, así que el visor la deduce — comprueba `upAxisSource` antes de fiarte.

Imported 3D models

addMesh(files)Promise<string>

Importa un modelo desde bytes. Toma una LISTA: un .gltf necesita su .bin y sus texturas, un .obj su .mtl — si mandas solo el archivo de entrada obtienes geometría gris.

addMeshFromUrl(urls)Promise<string>

Importa un modelo que descarga el visor. Pasa todas las URL que necesita; se descargan en paralelo y todas deben permitir CORS.

listMeshes()Promise<MeshInfo[]>

Todos los modelos importados, con sus recuentos de triángulos y texturas. Lee `unitSource` y `upAxisSource` antes de fiarte de los valores contiguos.

removeMesh(meshId?) · clearMeshes()Promise<void>

Elimina una importación y libera su geometría, materiales y texturas, o elimínalas todas.

setMeshVisible(visible, meshId?)Promise<void>

Muestra u oculta una importación sin descargarla.

fitMesh(meshId?)Promise<void>

Encuadra la cámara en una importación, o en todas.

setMeshPlacement(placement, meshId?)Promise<void>

Coloca una importación a mano: posición, giro, nivelación, escala. Parcial y acotado. Las importaciones empiezan centradas sobre el IFC y apoyadas en su suelo.

setMeshUpAxis(axis, meshId?)Promise<void>

Corrige el eje vertical del origen. Solo tiene sentido en OBJ — la especificación de glTF obliga Y arriba, así que informa `upAxisSource: 'declared'`.

setMeshUnit(unitScale, meshId?)Promise<void>

Corrige la unidad de origen: 1 metros, 0,01 centímetros, 0,001 milímetros, 0,3048 pies. Ninguno de estos formatos la registra, así que el visor la deduce del tamaño del modelo.

Conformidad IDS y EIR

checkIds(idsXml)Promise<IdsResult>

Comprueba el modelo contra un IDS de buildingSMART (.ids XML). Se ejecuta en un worker. Devuelve pass/fail por spec.

checkEir(profile)Promise<IdsResult>

Comprueba el modelo contra un perfil de validación EIR / BIM — un objeto, su JSON (incl. el formato compacto) o el id de un perfil incorporado como 'builtin-en14351-1'. Compila a IDS y usa el mismo motor: mismo IdsResult que checkIds.

Idiomas

setLanguage(lang)void

Cambia el idioma de la interfaz en tiempo de ejecución.

getLanguages()string[]

Lista los códigos de idioma soportados (ver IfcViewer.LANGUAGES para etiquetas).

Ciclo de vida y eventos

whenReady()Promise<void>

Resuelve cuando el visor está listo para aceptar comandos.

isReadyboolean

Getter — true en cuanto el visor ha señalado que está listo.

on(event, cb)() => void

Suscríbete a un evento; devuelve una función para desuscribir.

off(event, cb)void

Elimina un listener de eventos añadido previamente.

dispose()void

Desmonta el visor y elimina el iframe.

Las llamadas concurrentes a add() / addFromUrl() se serializan internamente y cada promesa se correlaciona con su propia carga — una carga iniciada por la app (parámetro de URL, subida dentro del iframe) nunca resuelve tu promesa de add().

Opciones del constructor

OpciónTipoPor defectoDescripción
ui'minimal' | 'full' | 'kiosk' | 'client' | 'article' | 'embed''minimal'Preset de la interfaz. 'embed': para una app anfitriona que aporta el archivo — 3D, propiedades al seleccionar y una barra compacta con Validar y Medir; sin Abrir, Compartir, cuenta ni idioma.
toolbarbooleanpresetMuestra la barra de herramientas (por defecto, la del preset). false deja el visor 3D y los paneles.
tools('validate' | 'measure')[]['validate', 'measure']Herramientas de la barra compacta de ui: 'embed' — 'validate', 'measure'. [] para ninguna.
autoFramebooleantrueEncuadra toda la escena en iso, con margen, cada vez que termina una carga. false deja la cámara en tus manos.
validatebooleantrueEjecuta la validación al cargar (alimenta el Health Score).
panelbooleanfalseAbre automáticamente el panel de validación.
panelsPanelName[]—Limita el raíl de herramientas a estos paneles, desde el primer fotograma — `['scene','map']`, o `[]` para no tener raíl. Mismos nombres que setPanels y que el parámetro de URL `panels=`. Es preferible a llamar a setPanels tras la carga, que muestra el raíl completo y luego quita herramientas.
langstringautoFuerza el idioma de la interfaz (p. ej. en, es, ja).
accent#rrggbbbrandTiñe el visor para que encaje con tu dashboard.
modelstring—Carga automáticamente esta URL pública de IFC al estar listo.
backgroundBackgroundSpec—Fondo de escena desde el primer fotograma — un preset, '#rrggbb' o '#arriba,#abajo'.
mapboolean | ('terrain' | 'buildings' | 'showcase')[]—Coloca el modelo en el mapa al cargar: true, o capas ['terrain','buildings','showcase'].
solar · moon'MM-DDTHH:MM' · boolean—Abre el estudio solar a una hora local del emplazamiento: '06-21T18:00'.
scansstring[]—URLs de nubes de puntos que cargar junto al modelo.
layersstring—Configuración de capas de datos que abrir: la URL de un JSON exportado desde el panel Capas de datos (fuentes en vivo, estilos, alertas). Desde 1.16.
scenestring—Escena que abrir: la URL de un documento de escena (modelos, capas de datos, dispositivos en vivo, mapa, cámara). Desde 1.17.
view · fillCameraView · number—Cuando todos los modelos han cargado, encuádralos desde esta vista con un ajuste ceñido. ui: 'article' implica 'iso'.
wheel'always' | 'ctrl''always''ctrl': la rueda desplaza tu página y solo hace zoom con Ctrl/⌘ pulsado, como un mapa incrustado. ui: 'article' lo implica.
lazyboolean | 'visible'falseNo carga nada hasta que hace falta: true muestra un póster con botón; 'visible' arranca al acercarse la figura a la pantalla.
poster · posterTitle · posterText · launchLabelstring—Imagen, título, texto y etiqueta del botón del póster, visibles hasta que el visor está listo.
aspectRatiostring ('16/10')—Tamaño por proporción ('16/10'): póster y visor comparten la caja, así que nada se mueve en la página.
turntableboolean | numberfalseRotación lenta en reposo tras cargar (true = 6°/s, o grados por segundo).
pauseOffscreenbooleanui === 'article'Deja de renderizar fuera de pantalla. Por defecto true con ui: 'article'.
fullscreenButtonbooleanfalseUn pequeño botón de ampliar en la esquina.
height · widthnumber | string'100%'Tamaño del iframe (número → px).
baseUrlstringautoURL base de la app. Se deduce de la URL del script.
loadTimeoutnumber120000Rechaza add()/addFromUrl() tras N ms (0 lo desactiva).
className · titlestring—Clase extra / title del iframe.
on* callbacksfunction—Callbacks de conveniencia — equivalen a .on(...).

Eventos

EventoPayloadDescripción
ready{ languages }Visor montado y listo.
model-progress{ percent, phase }Progreso de carga (descarga → parseo → render).
model-loaded{ modelId, fileName, elementCount, fromCache }Un modelo terminó de cargar.
validation-started{ modelId }Empezó una validación sobre un modelo — tras una carga (validate: true) o desde validate().
validation-completed{ modelId, qualityScore, errors, warnings, info, total }Validación terminada: el Health Score y los recuentos, y el modelo al que se refieren (null para el agregado federado).
validation-failed{ modelId, message }Una validación no pudo ejecutarse o no terminó — con el motivo.
model-error{ message, url?, name? }Falló una carga.
element-selected{ expressId, modelId, ifcType, name, globalId }El usuario eligió un elemento — con su GlobalId, para cruzarlo con tus propios registros.
pointcloud-picked{ cloudId, position, sourcePosition, classification, intensity, distance }Se leyó un punto de un escaneo. Lleva las coordenadas del archivo, no las de la escena.
map-feature-picked{ id, name?, label?, featureKind, heightM?, heightEstimated }Se pulsó un edificio del entorno de OpenStreetMap. Contexto, no modelo.
walk-changed{ active, speed }El modo paseo se activó o desactivó — por el visitante o por el host.
measurements-changed{ tool, units, items }Se añadió, quitó o renombró una medición. Lleva la lista completa.
tour-started{ title, total, template }Empezó un tour.
tour-step{ index, total, caption }El tour pasó a otra parada.
tour-ended{ completed }El tour se detuvo; completed si llegó a la última parada.
presentation-progress{ stage, label?, progress }El director está generando o exportando una presentación.
layer-feature-picked{ layerId, layer, featureId, geometry, lonLat, properties }Se pulsó un elemento de una capa de datos: su capa, sus propiedades y un punto para marcarlo.
alert{ kind, from, id, name, rule, count, sample }Una regla de una capa de datos o del gemelo empezó o dejó de alertar.

Estáticos

IfcViewer.LANGUAGES ({ code, label }[], nombres nativos) e IfcViewer.SUPPORTED_LANGUAGES (códigos) — para construir un selector de idioma antes de que el visor esté listo.

Web component

Cero JavaScript — coloca la etiqueta en cualquier página o dashboard y define atributos.

Atributos: modeluilangaccentvalidatepanelbase-urltoolbartoolsauto-frame

Los eventos se reemiten como CustomEvents del DOM llamados ifcviewer:<type> (detail = payload). El IfcViewer subyacente está en .viewer del elemento; getStats / getIssues / screenshot se delegan a él.

JavaScript
const el = document.querySelector("ifc-viewer");
el.addEventListener("ifcviewer:validation-completed", (e) => {
  updateScore(e.detail.qualityScore);
});
const stats = await el.getStats();

Guías

Recetas

Patrones para copiar y pegar, de lo que los equipos construyen de verdad.

Inspector de elementos

Haz clic en el 3D y vuelca los property sets IFC en tu propio panel.

JavaScript
viewer.on("element-selected", async (e) => {
  const data = await viewer.getElement(e.expressId, e.modelId);
  renderPanel(data.name, data.globalId, data.propertySets);
});

Widget de Health Score

Muestra la puntuación de calidad del modelo y el recuento de incidencias en tu dashboard.

JavaScript
viewer.on("validation-completed", (v) => {
  setScore(v.qualityScore);                 // 0–100, or null
  setCounts(v.errors, v.warnings, v.info);
});

Comprobación IDS

Valida contra un IDS de buildingSMART y lista el resultado por especificación.

JavaScript
const ids = await fetch("/specs/project.ids").then(r => r.text());
const res = await viewer.checkIds(ids);     // runs in a worker

res.specs.forEach((spec) => {
  row(spec.name, spec.status, spec.passedCount + "/" + spec.applicableCount);
  spec.failures.forEach((f) => onClick(() => viewer.select(f.expressId, f.modelId)));
});

Validación EIR / BIM

Ejecuta un perfil EIR editable (estilo ISO 19650) — sin necesidad de un archivo .ids. Los fallos incluyen el GlobalId del elemento.

JavaScript
// An editable EIR profile — object, JSON, or the compact shorthand below.
const profile = {
  name: "Door QA",
  rules: [
    { type: "requiredProperty", entity: "IfcDoor", property: "FireRating", severity: "error" },
    { type: "allowedValues", entity: "IfcDoor", property: "FireRating",
      values: ["EI30", "EI60", "EI90"], severity: "warning" },
  ],
};
const res = await viewer.checkEir(profile);   // same IdsResult as checkIds
res.specs.forEach((spec) => {
  spec.failures.forEach((f) => row(spec.name, f.globalId, f.reasons.join(" · ")));
});

Una escena para un post de blog

Fondo blanco, el modelo en su emplazamiento y la sombra de la tarde del solsticio de verano — declarado de antemano, sin clics.

JavaScript
// Declared up front: the iframe boots with all of it, no clicks, no flash.
const viewer = await IfcViewer.create("#viewer", {
  model: "https://example.com/house.ifc",
  ui: "client",
  background: "white",
  map: ["terrain", "buildings"],   // your page declares tile consent
  solar: "06-21T19:30",            // summer solstice, site-local
});

// …or change the story as the reader scrolls
await viewer.setSolar({ date: "12-21", time: "09:30" });
const { attributions } = await viewer.getSiteContext(); // show these

Planta y mediciones

Corta una planta en un nivel por nombre, deja medir al lector y lee los resultados en unidades SI.

JavaScript
const { levels } = await viewer.getSections();
await viewer.addSection({ level: levels[0].name });   // floor plan, 1.2 m up
viewer.setView("top");

await viewer.setMeasureTool("distance");
viewer.on("measurements-changed", ({ items }) => {
  // value is SI — metres, m², degrees — whatever the viewer displays
  render(items.map((m) => m.kind + ": " + m.value?.toFixed(2)));
});

Un tour guiado en un artículo

Define las paradas una vez con getCamera() y deja que el tour avance solo junto a tu texto.

JavaScript
// 1. Once, while writing the post: frame each view and copy the camera.
console.log(JSON.stringify(await viewer.getCamera()));

// 2. In the page: play the stops, self-running, next to your text.
await viewer.playTour({
  title: "Casa Poblenou",
  steps: [
    { position: { x: 52, y: 29, z: 24 }, target: { x: 18, y: 7, z: -11 }, caption: "The site" },
    { position: { x: 18, y: 40, z: 11 }, target: { x: 18, y: 0, z: 10 },
      caption: "Structure", isolate: ["IfcColumn", "IfcSlab"] },
  ],
}, { autoplay: 6000 });
viewer.on("tour-step", ({ index, caption }) => highlightParagraph(index));

Un vídeo del modelo, desde tu página

Genera una presentación con una receta y recibe el MP4: sin grabar pantalla, sin subir nada.

JavaScript
viewer.on("presentation-progress", ({ stage, progress }) => bar.value = progress ?? 0);
await viewer.createPresentation("linkedin-teaser", { title: "Casa Poblenou", targetSec: 20 });

const { bytes, mimeType } = await viewer.exportPresentation({ resolution: 1080 });
video.src = URL.createObjectURL(new Blob([bytes], { type: mimeType }));

Una portada para tu post

Genera un pin de Pinterest o una tarjeta de LinkedIn del modelo con tu título y recibe el PNG.

JavaScript
await viewer.createCover({
  recipe: "pinterest",
  text: { title: "Casa Poblenou", location: "Barcelona", website: "https://example.com" },
});
const { bytes, mimeType } = await viewer.exportCover({ type: "png" });
img.src = URL.createObjectURL(new Blob([bytes], { type: mimeType }));

Un modelo dentro de un artículo

ui: 'article' es solo el lienzo, encuadrado para que el modelo lo llene, con una rueda que deja al lector seguir leyendo. Elige el ángulo con frame().

JavaScript
const viewer = new IfcViewer("#figure", {
  ui: "article",            // canvas only, tight iso framing, wheel scrolls the page
  model: "https://example.com/pavilion.ifc",
  lazy: true,               // nothing loads until the reader asks
  poster: "/img/pavilion.jpg",
  posterTitle: "The pavilion, live",
  aspectRatio: "16/10",     // no layout shift from poster to viewer
  background: "auto",       // paper on a light page, studio on a dark one
  turntable: true,          // a slow turn until the reader takes over
  fullscreenButton: true,
  validate: false,
});

Una historia que avanza con el texto

Fija el visor y deja que cada párrafo marque el plano: aísla los muros, gira alrededor, trae el sol de la tarde.

JavaScript
// <div id="figure" style="position:sticky;top:1rem"></div>
// <p id="s1">…</p> <p id="s2">…</p> <p id="s3">…</p>
const viewer = new IfcViewer("#figure", { ui: "article", aspectRatio: "16/10", model: url });
viewer.bindSteps([
  { el: "#s1", frame: { view: "iso" } },
  { el: "#s2", isolate: "IfcWall", frame: { azimuth: 225, elevation: 20 } },
  { el: "#s3", isolate: null, solar: { active: true, date: "06-21", time: "19:30" } },
]);

Un gemelo de ciudad en vivo en tu página

Abre una escena publicada, reacciona a sus alertas y clics, añade tu propia fuente en vivo junto a ella y comparte un enlace al resultado.

JavaScript
const viewer = new IfcViewer("#twin", {
  ui: "client",
  scene: "https://www.ifcvieweronline.eu/scenes/barcelona-placa-catalunya.scene.json",
});
viewer.on("alert", (a) => { if (a.kind === "start") notify(a.name + ": " + a.rule) });
viewer.on("layer-feature-picked", (f) => showCard(f.layer, f.properties));

// Your own live source next to the scene's (GeoJSON with CORS, every 60 s)
await viewer.addLayer({ url: "https://example.org/sensors.geojson", live: 60 });
const { link, sources } = await viewer.exportScene({ title: "Our twin" });

Una ficha de producto en tu app

Fija la versión, entrega el archivo y muestra lo que el fabricante puso en el tipo: transmitancia, clase de carga de viento, fabricante.

JavaScript
import { IfcViewer } from "https://www.ifcvieweronline.eu/sdk/1.18.0/ifc-viewer.es.js";

const viewer = await IfcViewer.create("#product-3d", {
  ui: "embed",               // 3D + properties, Validate and Measure only
  panels: ["properties"],
  lang: "es",
});
const bytes = await fetch(product.ifcUrl).then(r => r.arrayBuffer());
await viewer.add("V-70-PR.ifc", bytes);                      // framed for you

// The product, by class (or by its GlobalId) — no expressIDs to know
const [win] = await viewer.findElements({ ifcClass: "IfcWindow" });

// What the manufacturer put in the type (IfcWindowType → HasPropertySets)
const el = await viewer.getElement(win.globalId);
const pset = (name) => el.effectivePropertySets.find(s => s.name === name);
const uw = pset("Pset_WindowCommon")?.properties.find(p => p.name === "ThermalTransmittance");
console.log(el.typeName, el.globalId, uw?.value, uw?.unit);   // "Ventana V-70 practicable" … 1.2 "W/(m²·K)"

const { qualityScore } = await viewer.validate();             // Health Score, on demand

// Declared performance per EN 14351-1: wind, water, acoustics, Uw, g / τv, DoP
const en = await viewer.checkEir("builtin-en14351-1");
const missing = en.specs.filter(s => s.status === "fail").map(s => s.name);

Adáptalo a tu marca

Iguala el visor a tu producto con un solo color de acento.

JavaScript
new IfcViewer("#viewer", { accent: "#3fa866" });

// …or on the web component:
// <ifc-viewer accent="#3fa866" model="…"></ifc-viewer>

Selector de idioma

Construye un selector con los idiomas que trae el visor.

JavaScript
// build a <select> from the bundled languages
IfcViewer.LANGUAGES.forEach(({ code, label }) => addOption(code, label));

// switch at runtime
viewer.setLanguage("ja");

Health Score

Ciclo de la validación

validate: true valida cada modelo cuando termina de cargar; validate() la ejecuta a demanda y resuelve con el resultado. Qué devuelve getValidation() en cada momento:

CuándogetValidation() devuelve
Antes de validar nadanull — getValidationStatus() dice idle.
Durante la primera ejecuciónnull — getValidationStatus() dice running, con progreso; ya se emitió validation-started.
Tras una ejecución{ qualityScore, errors, warnings, info, total, status: 'done', modelId }. Con varios modelos, el agregado (modelId: null).
Durante una ejecución posteriorLos números anteriores, con status: 'running'.
Tras un falloLos números anteriores con status: 'error' y error — o null si no los había. Se emitió validation-failed y validate() rechazó.

Versiones

Fija una versión

Desde la 1.18.0, cada versión se publica también en una ruta que nunca cambia. Fíjala en producción; la ruta móvil sigue a la última versión.

HTML
<script type="module">
  import { IfcViewer } from "https://www.ifcvieweronline.eu/sdk/1.18.0/ifc-viewer.es.js";
</script>
RutaCachéUso
/sdk/ifc-viewer.es.jsse revalida en cada cargaPara probar — siempre la última.
/sdk/1.18.0/ifc-viewer.es.jsinmutable, en caché un añoProducción — el código que probaste es el que se ejecuta.

¿Qué visor abre un SDK fijado?

El iframe es el visor en vivo del mismo origen, no una copia congelada: un SDK fijado conserva su API y su comportamiento mientras el visor de dentro sigue mejorando. El visor responde a todos los comandos que envía cualquier SDK publicado — el build lo comprueba —, así que una versión fijada antigua sigue funcionando.

Fija también el contenido

versions.json, junto al SDK, lista el hash Subresource Integrity de cada versión:

HTML
<script type="module" src="https://www.ifcvieweronline.eu/sdk/1.18.0/ifc-viewer.es.js"
        integrity="sha384-WsT4d0DS1oqva4rLWCGBDrx50uZVA3B95IDWbfxaFt8lnjs6NgOrjKNcWQL57/0z" crossorigin="anonymous"></script>

Registro de cambios

1.18.0 — objetos de catálogo en una app anfitriona. getElement() devuelve el GlobalId y los datos del tipo (typeName, typeProperties con unidades, effectivePropertySets; tipos IFC4 y estilos IFC2x3). validate(), getValidationStatus(), validation-started / validation-failed, y validate: true por fin valida tras cada carga. Encuadre automático tras cargar, fit() y frame() con margen para objetos pequeños, frame(elementId), y setView() vuelve a mover la cámara. ui: 'embed' con toolbar y tools. Versiones fijas en /sdk/1.18.0/. findElements(), GlobalIds en getElement() / select() / element-selected, conjuntos de marco y hoja de ventanas y puertas, y los materiales del tipo; en ui: 'embed' el panel de propiedades deja a la vista el objeto elegido. checkEir('builtin-en14351-1') comprueba las prestaciones declaradas de ventanas y puertas exteriores según EN 14351-1 — viento, estanqueidad al agua, acústica, transmitancia térmica, g y τv, fabricante y código de producto — en property sets estándar de IFC; las reglas EIR ganan where (solo elementos con un valor de propiedad) y optional.

Ayuda

Resolución de problemas

Las pocas cosas con las que la gente tropieza.

No se renderiza nada

Comprueba, en este orden: que el visor llegó a ready, que el modelo cargó (model-loaded frente a model-error), que el contenedor tiene altura distinta de cero y que el origen de bytes es IFC válido. El mensaje de model-error nombra la causa.

Me da un error de CORS al cargar un modelo

CORS solo afecta a addFromUrl(url). Si controlas los bytes, descárgalos tú y llama a add(name, bytes) — no aplica CORS porque entregas los datos directamente.

El iframe sale en blanco o se niega a cargar

La app tiene que ser embebible — sin X-Frame-Options: DENY y sin una CSP frame-ancestors restrictiva en el host. Nuestra app alojada permite el embebido.

Mi ArrayBuffer queda vacío después de add()

add() transfiere el buffer para entregarlo sin copia, lo que lo deja detached en tu código. Pasa una copia (bytes.slice(0)) si después vas a seguir necesitando los bytes.

Las cargas o las consultas dan timeout

add()/addFromUrl() se rechazan tras loadTimeout (120 s por defecto; 0 lo desactiva). Las consultas se rechazan a los 30 s y checkIds a los 120 s. Modelos grandes en dispositivos lentos pueden necesitar un loadTimeout mayor.

¿Qué navegadores están soportados?

Cualquier navegador moderno con WebAssembly y módulos ES — Chrome, Edge, Firefox, Safari. Internet Explorer no.

Me da 404 en mi servidor de desarrollo local

Un servidor de desarrollo SPA puede interceptar /sdk/ con su fallback HTML. Pruébalo contra un build real o un host estático, o importa directamente el módulo /sdk/ifc-viewer.es.js.