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
const viewer = new IfcViewer("#viewer");
await viewer.add("project.ifc", ifcBytes);
// → rendered, validated, all on the client- 100% en el cliente
- Sin backend de subida
- Cero dependencias en runtime
- Wrapper de ~6 KB
- Tipos TypeScript
- 10 idiomas
- Motor validado con IDS
Puesta en marcha
Inicio rápido
Elige tu stack. Copia, pega y renderiza — sin paso de build, sin API key.
<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>Cero JavaScript — coloca la etiqueta en cualquier página o dashboard y define atributos.
<script type="module" src="https://www.ifcvieweronline.eu/sdk/ifc-viewer.es.js"></script>
<ifc-viewer model="https://your-cde.com/model.ifc" ui="minimal" accent="#3fa866"
style="display:block;height:520px"></ifc-viewer>Monta en un effect y destruye al desmontar. Seguro con la doble invocación de StrictMode en React 18.
import { useEffect, useRef } from "react";
import { IfcViewer } from "https://www.ifcvieweronline.eu/sdk/ifc-viewer.es.js";
export function ModelViewer({ url }) {
const host = useRef(null);
useEffect(() => {
const viewer = new IfcViewer(host.current, { model: url });
return () => viewer.dispose(); // StrictMode-safe teardown
}, [url]);
return <div ref={host} style={{ height: 520 }} />;
}Monta en onMounted, destruye en onUnmounted.
<script setup>
import { onMounted, onUnmounted, ref } from "vue";
import { IfcViewer } from "https://www.ifcvieweronline.eu/sdk/ifc-viewer.es.js";
const host = ref(null);
let viewer;
onMounted(() => { viewer = new IfcViewer(host.value, { model: "https://host/m.ifc" }); });
onUnmounted(() => viewer && viewer.dispose());
</script>
<template>
<div ref="host" style="height: 520px" />
</template>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.
⚠ El host de ejemplo puede estar limitando peticiones. Inténtalo de nuevo en un momento.
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.
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.
// 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.
// 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.
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).
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?)IfcViewerMonta 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)voidDescarga un modelo por id.
clear()voidElimina todos los modelos cargados.
Cámara y selección
select(expressId | globalId, modelId?)voidSelecciona y encuadra un elemento — por su expressID IFC o por su GlobalId (que además encuentra su modelo).
setView(view)voidVuela 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()voidEncuadra el modelo activo desde el ángulo actual, con margen / reinicia la cámara.
setCamera(position, direction)voidColoca 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?)voidAísla una categoría (p. ej. "IfcWall"); omítelo para limpiar.
showAll()voidRestaura toda la visibilidad (quita ocultos y aislamiento).
hideElements(ids, modelId?) · showElements(ids, modelId?)voidOculta / 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()voidArranca ya un visor diferido (lo que hace el botón del póster). Las llamadas anteriores quedaron en cola.
bindSteps(steps, { rootMargin? })() => voidScrollytelling: 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()voidAbre 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)voidLimita 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)voidCambia 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.
isReadybooleanGetter — true en cuanto el visor ha señalado que está listo.
on(event, cb)() => voidSuscríbete a un evento; devuelve una función para desuscribir.
off(event, cb)voidElimina un listener de eventos añadido previamente.
dispose()voidDesmonta 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ón | Tipo | Por defecto | Descripció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. |
toolbar | boolean | preset | Muestra 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. |
autoFrame | boolean | true | Encuadra toda la escena en iso, con margen, cada vez que termina una carga. false deja la cámara en tus manos. |
validate | boolean | true | Ejecuta la validación al cargar (alimenta el Health Score). |
panel | boolean | false | Abre automáticamente el panel de validación. |
panels | PanelName[] | — | 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. |
lang | string | auto | Fuerza el idioma de la interfaz (p. ej. en, es, ja). |
accent | #rrggbb | brand | Tiñe el visor para que encaje con tu dashboard. |
model | string | — | Carga automáticamente esta URL pública de IFC al estar listo. |
background | BackgroundSpec | — | Fondo de escena desde el primer fotograma — un preset, '#rrggbb' o '#arriba,#abajo'. |
map | boolean | ('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'. |
scans | string[] | — | URLs de nubes de puntos que cargar junto al modelo. |
layers | string | — | 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. |
scene | string | — | Escena que abrir: la URL de un documento de escena (modelos, capas de datos, dispositivos en vivo, mapa, cámara). Desde 1.17. |
view · fill | CameraView · 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. |
lazy | boolean | 'visible' | false | No 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 · launchLabel | string | — | Imagen, título, texto y etiqueta del botón del póster, visibles hasta que el visor está listo. |
aspectRatio | string ('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. |
turntable | boolean | number | false | Rotación lenta en reposo tras cargar (true = 6°/s, o grados por segundo). |
pauseOffscreen | boolean | ui === 'article' | Deja de renderizar fuera de pantalla. Por defecto true con ui: 'article'. |
fullscreenButton | boolean | false | Un pequeño botón de ampliar en la esquina. |
height · width | number | string | '100%' | Tamaño del iframe (número → px). |
baseUrl | string | auto | URL base de la app. Se deduce de la URL del script. |
loadTimeout | number | 120000 | Rechaza add()/addFromUrl() tras N ms (0 lo desactiva). |
className · title | string | — | Clase extra / title del iframe. |
on* callbacks | function | — | Callbacks de conveniencia — equivalen a .on(...). |
Eventos
| Evento | Payload | Descripció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.
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.
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.
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.
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.
// 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.
// 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 thesePlanta y mediciones
Corta una planta en un nivel por nombre, deja medir al lector y lee los resultados en unidades SI.
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.
// 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.
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.
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().
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.
// <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.
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.
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.
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.
// 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ándo | getValidation() devuelve |
|---|---|
| Antes de validar nada | null — getValidationStatus() dice idle. |
| Durante la primera ejecución | null — 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 posterior | Los números anteriores, con status: 'running'. |
| Tras un fallo | Los 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.
<script type="module">
import { IfcViewer } from "https://www.ifcvieweronline.eu/sdk/1.18.0/ifc-viewer.es.js";
</script>| Ruta | Caché | Uso |
|---|---|---|
/sdk/ifc-viewer.es.js | se revalida en cada carga | Para probar — siempre la última. |
/sdk/1.18.0/ifc-viewer.es.js | inmutable, en caché un año | Producció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:
<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.