IFC Viewer SDK · v1.18.0
ฝังตัวแสดงผล IFC ลงในผลิตภัณฑ์ของคุณเอง
แทรกตัวแสดงผลบนเบราว์เซอร์เดียวกันลงใน CDE ดิจิทัลทวิน หรือเครื่องมือภายในของโครงการ โหลดข้อมูล IFC จากแอปของคุณ โดยแต่ละโมเดลถูกประมวลผลที่ฝั่งไคลเอนต์
สามบรรทัดถึงการเรนเดอร์ครั้งแรก
const viewer = new IfcViewer("#viewer");
await viewer.add("project.ifc", ifcBytes);
// → rendered, validated, all on the client- ทำงานฝั่งไคลเอนต์ 100%
- ไม่ต้องมีแบ็กเอนด์สำหรับอัปโหลด
- ไม่มี dependency ตอนรันไทม์
- ตัวห่อขนาด ~6 KB
- มี type ของ TypeScript
- 10 ภาษา
- เอนจินที่ผ่านการตรวจด้วย IDS
การติดตั้ง
เริ่มต้นอย่างรวดเร็ว
เลือกสแตกของคุณ คัดลอก วาง แล้วเรนเดอร์ — ไม่ต้องมีขั้นตอน build ไม่ต้องใช้ 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>ไม่ต้องเขียน JavaScript — วางแท็กลงในหน้าเว็บหรือแดชบอร์ดใดก็ได้ แล้วกำหนดแอตทริบิวต์
<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>mount ใน effect และคืนทรัพยากรตอน unmount ปลอดภัยกับการเรียกซ้ำสองครั้งของ StrictMode ใน 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 }} />;
}mount ที่ onMounted และคืนทรัพยากรที่ 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>โฮสต์โดยเราหรือโฮสต์เอง
ตัวอย่างโค้ดนำเข้าจาก CDN ของเรา ซึ่งใช้ได้กับทุกออริจิน หากต้องการโฮสต์เอง ให้เสิร์ฟ ifc-viewer.es.js จากโดเมนของคุณ — SDK จะหา URL ของแอปโดยอ้างอิงตำแหน่งของสคริปต์เอง หากแอปอยู่ที่อื่น ให้กำหนดตัวเลือก baseUrl
ยังไม่มีแพ็กเกจ npm: นำเข้าโมดูลผ่าน URL (ด้านบน) หรือรวม ifc-viewer.es.js ไว้ในแอปของคุณ
พิสูจน์
เดโมสด
หน้านี้กำลังรัน SDK คลิกปุ่มควบคุมเพื่อดึงไฟล์ IFC ตัวอย่างและสั่งงานตัวแสดงผล — แต่ละปุ่มจะแสดงเมธอดที่เรียกใช้
⚠ โฮสต์ตัวอย่างอาจกำลังจำกัดอัตราการเรียก ลองใหม่อีกครั้งในอีกสักครู่
สถาปัตยกรรม
ทำงานอย่างไร
ตัวห่อขนาดเล็กมากในหน้าเว็บของคุณ ตัวแสดงผลเต็มรูปแบบใน iframe ที่แยกออกมา ไบต์ของคุณส่งผ่านระหว่างกัน — และถูกประมวลผลในที่ที่มันอยู่แล้ว
แอปของคุณ
โมดูล ES ขนาด ~6 KB ที่สร้าง iframe และ JS API แบบมี type น้ำหนักของ 3D และการ parse ไม่แตะบันเดิลของคุณเลย
สะพาน postMessage
ไบต์ IFC ถูกโอนไปยัง iframe ในรูป ArrayBuffer (ไม่มีการคัดลอก) คำสั่งและคำค้นเป็นข้อความแบบ request/response ที่จับคู่กัน
ตัวแสดงผล
three.js และ web-ifc ที่คอมไพล์เป็น WebAssembly ทำการ parse และเรนเดอร์โมเดลภายใน iframe — บนเครื่องของผู้เข้าชม
ทำไมจึงเป็นส่วนตัว
โมเดลไม่เคยไปถึงเซิร์ฟเวอร์ ไม่มีสายการอัปโหลดให้ต้องดูแลความปลอดภัย ไม่มีคำถามเรื่องถิ่นที่อยู่ของข้อมูล ไม่มีคิวตามขนาดไฟล์
ทำไมจึงขยายตัวได้
การคำนวณเกิดขึ้นบนอุปกรณ์ของผู้เข้าชมแต่ละคน การเสิร์ฟโมเดล 200 MB มีต้นทุนเท่ากับ 2 MB — เพราะมันคือไฟล์สแตติก
แนวคิด
แนวคิดหลัก
สี่เรื่องที่ควรรู้ก่อนลงมือสร้าง
การ mount และความพร้อม
new IfcViewer(target) จะ mount ทันทีและเก็บคำสั่งไว้ในคิวจนกว่าตัวแสดงผลจะพร้อม ส่วน await IfcViewer.create(target) (หรือ whenReady()) จะให้อินสแตนซ์ที่ใช้ได้ทันที
// queues commands until ready
const viewer = new IfcViewer("#viewer");
// …or await an instance that is ready to use
const viewer = await IfcViewer.create("#viewer");ไบต์หรือ URL
add(name, bytes) ส่งไบต์ที่คุณมีอยู่แล้ว — ไม่เกี่ยวกับ CORS และไม่มีการอัปโหลด บัฟเฟอร์จะถูกโอน (กลายเป็น detached) ดังนั้นให้ส่งสำเนาไปหากยังต้องใช้ ส่วน addFromUrl(url) จะดึงจาก URL สาธารณะที่เปิด CORS
// 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");อีเวนต์
สมัครรับด้วย on(event, cb) ซึ่งคืนฟังก์ชันสำหรับยกเลิกการสมัคร มีให้ใช้ทั้ง ready, model-progress, model-loaded, validation-completed, model-error และ element-selected
const off = viewer.on("model-loaded", (m) => {
console.log(m.fileName, m.elementCount);
});
// later: off();การสืบค้น
ดึงข้อมูลตามต้องการ: getModels, getElement, getValidation, getStats, getIssues, checkIds, checkEir, screenshot แต่ละตัวเป็นแบบ request/response และจะ reject เมื่อหมดเวลา (30 วินาที; IDS / EIR สูงสุด 120 วินาที)
const models = await viewer.getModels();
const { qualityScore } = await viewer.getValidation();
const png = await viewer.screenshot();อ้างอิง
อ้างอิง API
ทุกเมธอด จัดเป็นกลุ่ม — พร้อมลายเซ็นและชนิดค่าที่คืน ถัดไปคือตัวเลือกของ constructor อีเวนต์ และ web component <ifc-viewer>
Constructor
new IfcViewer(target, options?)IfcViewerติดตั้งในตัวเลือก CSS หรืออิลิเมนต์ ดูออปชันด้านล่าง
IfcViewer.create(target, options?)Promise<IfcViewer>สร้างตัวแสดงผลและ resolve เมื่อพร้อมรับคำสั่ง
การโหลดโมเดล
add(name, bytes)Promise<ModelLoadedEvent>โหลดไบต์ IFC (ArrayBuffer/Uint8Array) จากแอปของคุณ บัฟเฟอร์ถูกถ่ายโอนแบบไม่คัดลอก
addFromUrl(url, name?)Promise<ModelLoadedEvent>โหลด URL IFC สาธารณะที่เปิด CORS
removeModel(modelId)voidยกเลิกการโหลดโมเดลตาม id
clear()voidลบโมเดลที่โหลดไว้ทั้งหมด
กล้องและการเลือก
select(expressId | globalId, modelId?)voidเลือกและจัดเฟรมชิ้นส่วน — ด้วย expressID ของ IFC หรือด้วย GlobalId (ซึ่งหาโมเดลให้ด้วย)
setView(view)voidบินไปยังมุมมองที่มีชื่อของฉาก — iso, front, back, left, right, top, bottom — ให้ทั้งฉากอยู่ในเฟรม ไม่มีการตอบกลับ (ตั้งแต่ 1.14 ถึง 1.17 ตัวแสดงผลเพิกเฉยต่อคำสั่งนี้)
frame({ view?, fill?, azimuth?, elevation?, animate? })Promise<{ scope }>จัดเฟรมเพื่อนำเสนอ: โมเดลกินพื้นที่ `fill` ของเฟรม (0.2–0.98 ค่าเริ่มต้น 0.85) โดยพอดีกับมุมของกล่อง ไม่ใช่ทรงกลมล้อมรอบ azimuth / elevation เลือกมุมมองได้ทุกมุม
frame(elementId, modelId?, options?)Promise<{ scope: 'element' }>จัดเฟรมชิ้นส่วนเดียว (expressID): จากมุมปัจจุบัน เว้นแต่ระบุมุมมอง ปฏิเสธหากไม่อยู่ในโมเดล
fit() · reset()voidจัดเฟรมโมเดลที่ใช้งานจากมุมปัจจุบันพร้อมระยะขอบ / รีเซ็ตกล้อง
setCamera(position, direction)voidวางกล้องที่ตำแหน่งหนึ่งโดยมองไปตามทิศทาง
getCamera()Promise<CameraState | null>ตำแหน่งกล้องและจุดที่มอง — บันทึกไว้แล้วคืนค่าด้วย lookAt
lookAt(position, target, animate?)Promise<void>พากล้องไปยังตำแหน่งหนึ่งโดยมองไปที่เป้าหมาย (เมตรในฉาก, แกน Y ชี้ขึ้น)
setWalkMode(enabled, { speed? })Promise<WalkState>โหมดเดินมุมมองบุคคลที่หนึ่ง (WASD, ลากเพื่อมอง, Esc เพื่อออก) ส่ง walk-changed
getWalkState()Promise<WalkState>โหมดเดินเปิดอยู่หรือไม่ และที่ความเร็วเท่าใด
การแสดงผล
isolate(ifcType?)voidแยกหมวดหมู่ (เช่น "IfcWall"); เว้นว่างเพื่อล้าง
showAll()voidคืนการมองเห็นทั้งหมด (ล้างที่ซ่อน + การแยก)
hideElements(ids, modelId?) · showElements(ids, modelId?)voidซ่อน / แสดงชุดอิลิเมนต์ตาม expressID
setModelVisible(modelId, visible)Promise<void>แสดงหรือซ่อนโมเดลหนึ่งในฉากรวมโดยไม่ต้องยกเลิกการโหลด
setModelOpacity(opacity, modelId?)Promise<void>ทำให้โมเดลโปร่งแสง (0.05–1) — เช่น งานสถาปัตย์รอบงานระบบ
isolateModel(modelId | null)Promise<void>แสดงเฉพาะโมเดลนี้; null เพื่อแสดงทั้งหมดอีกครั้ง
รูปลักษณ์และการนำเสนอ
setBackground(background)Promise<BackgroundState>เปลี่ยนพื้นหลังฉาก: พรีเซ็ต ('white', 'paper', 'blueprint', 'sky', 'studio'), '#rrggbb', '#บน,#ล่าง' หรือ { top, bottom } ไม่ถูกบันทึกเป็นค่ากำหนดของผู้เข้าชม
getBackground()Promise<BackgroundState>พื้นหลังฉากปัจจุบัน
setAccent(color)Promise<void>เปลี่ยนสีเน้นของ UI ระหว่างทำงาน (#rrggbb)
setClientMode(enabled)Promise<void>เปิดหรือปิดมุมมองลูกค้า — มุมมองสำหรับผู้มีส่วนได้ส่วนเสีย เหมือน ui: 'client'
setRenderQuality(quality)Promise<void>'quality' เปิดการเรนเดอร์ที่หนักขึ้นสำหรับภาพหลัก; 'standard' สำหรับใช้งานทั่วไป
การศึกษาแสงแดด
setSolar(opts?)Promise<SolarState>เงาจริงตามวันและเวลาท้องถิ่นของไซต์: { date: '06-21', time: '18:00' } ตำแหน่งไซต์มาจากการอ้างอิงพิกัดของ IFC; ส่ง location สำหรับโมเดลที่ไม่มี — ไม่มีการใช้เมืองเริ่มต้นแบบเงียบ ๆ
getSolar()Promise<SolarState>วันที่ เวลา และเขตเวลาของการศึกษา และตำแหน่งของไซต์
โหมดแผนที่
setSiteContext(opts?)Promise<SiteContextState>วางโมเดลบนแผนที่พร้อมภูมิประเทศและสภาพแวดล้อมจาก OpenStreetMap การเรียกใช้หมายถึงหน้าของคุณให้ความยินยอมโหลดไทล์แทนผู้เข้าชม; แสดงข้อความระบุแหล่งที่มา
getSiteContext()Promise<SiteContextState>สถานะโหมดแผนที่ ตำแหน่งการวาง และข้อความระบุแหล่งที่มาที่ต้องแสดง
ฉาก
openScene(url | SceneDocument)Promise<void>เปิดฉากอื่น: URL ของไฟล์ .scene.json หรือเอกสารฉาก (ถูกบีบอัดไปกับที่อยู่ของเฟรม จึงต้องใส่ในลิงก์ได้) ตัวแสดงผลจะโหลดใหม่ การเรียกที่รออยู่จะถูกปฏิเสธ และ ready จะทำงานอีกครั้ง
exportScene({ title?, description?, camera? })Promise<SceneExport>ฉากบนหน้าจอในรูปเอกสารฉาก — โมเดลตาม URL ชั้นข้อมูล ทวิน แผนที่ กล้อง — พร้อมแหล่งข้อมูลที่ต้องให้เครดิตและลิงก์ที่บรรจุฉากไว้
packScene(doc)Promise<string | null>บีบอัดเอกสารฉากสำหรับลิงก์ #scene= ด้วยตนเอง (เป็น null เมื่อใหญ่เกินลิงก์)
ชั้นข้อมูลและทวิน
getLayerPresets()Promise<LayerPreset[]>แค็ตตาล็อกแหล่งข้อมูลสาธารณะแบบสด ตั้งชื่อตามภาษาของตัวแสดงผล พร้อมสิ่งที่แต่ละแหล่งต้องใช้ในเบราว์เซอร์ (คีย์ พร็อกซี) และมีข้อมูลใกล้ที่ตั้งหรือไม่
addLayer({ preset } | { url, live? } | { geojson })Promise<DataLayerInfo>เพิ่มชั้นข้อมูล: แหล่งจากแค็ตตาล็อก ({ preset }) GeoJSON หรือฟีดสดที่ URL ({ url, live }) หรือ GeoJSON ที่คุณมีอยู่ ({ geojson }) ใช้เฉพาะเซสชัน ไม่บันทึกลงชั้นข้อมูลของผู้เยี่ยมชม
getLayers()Promise<DataLayerInfo[]>ชั้นข้อมูลทั้งหมด: สถานะ จำนวนฟีเจอร์ ขอบเขต การรีเฟรช และการอ้างอิงแหล่งที่มา
setLayerVisible(id, visible)Promise<DataLayerInfo>แสดงหรือซ่อนชั้นข้อมูล
frameLayer(id)Promise<void>พากล้องไปยังฟีเจอร์ของชั้นข้อมูล
removeLayer(id)Promise<void>ลบชั้นข้อมูล
getTwin()Promise<TwinState>ทวินเชิงปฏิบัติการตอนนี้: แหล่งข้อมูลอุปกรณ์ (สถานะ ค่าล่าสุด) และสำหรับแต่ละการผูก กฎที่ตรง สีของกฎ ค่าป้าย และกำลังแจ้งเตือนหรือไม่
ภาพตัด
addSection({ axis?, offset?, level?, flip? })Promise<SectionsState & { id }>เพิ่มระนาบตัด { level: 'ชั้น 1' } คือผังพื้นที่ชั้นนั้น; { axis: 'x', offset: 4.5 } คือภาพตัดที่ 4.5 ม.
updateSection(id, { offset?, enabled?, flipped? })Promise<SectionsState>เลื่อน เปิด/ปิด หรือกลับด้านระนาบ
removeSection(id?)Promise<SectionsState>ลบระนาบหนึ่ง หรือลบการตัดทั้งหมดเมื่อไม่ระบุ id
setSectionBox('model' | 'selection' | false)Promise<SectionsState>กล่องตัดรอบโมเดลหรือส่วนที่เลือก; false เพื่อลบ
getSections()Promise<SectionsState>ระนาบทั้งหมด กล่อง และชั้นที่ใช้ตัดตามระดับได้
ทัวร์
startTour(template?, { autoplay?, title? })Promise<TourState>เริ่มทัวร์สำเร็จรูป: 'social', 'client-walkthrough' หรือ 'technical-review' (ไล่ดูปัญหาจากการตรวจสอบ) เล่นด้วยแถบทัวร์ของตัวแสดงผลเอง
playTour({ title?, steps }, { startAt?, autoplay? })Promise<TourState>เล่นทัวร์ที่คุณสร้างเอง: จุดหยุดกล้องพร้อมคำบรรยาย การไฮไลต์ และคลาสที่แยกแสดง autoplay: true หรือ ms ต่อจุด
nextTourStep() · prevTourStep() · goToTourStep(i)Promise<TourState>เลื่อนไปมาระหว่างจุดหยุด
setTourAutoplay(autoplay)Promise<TourState>เปิดหรือปิดการเลื่อนอัตโนมัติของทัวร์ที่กำลังเล่น
stopTour()Promise<TourState>หยุดทัวร์และคืนการควบคุมกล้อง
getTour()Promise<TourState>ทัวร์และจุดหยุดในรูปแบบเดียวกับ playTour() — บันทึกไว้แล้วเล่นซ้ำภายหลังได้
ผู้กำกับงานนำเสนอ
getPresentationRecipes()Promise<PresentationRecipe[]>สูตรสำเร็จของผู้กำกับ (เดโมประชุม, รีล, ทีเซอร์ LinkedIn, สไตล์เปิดตัว, ลุคบรรณาธิการ…)
createPresentation(recipe?, options?)Promise<PresentationState>เปลี่ยนโมเดลเป็นวิดีโอที่ตัดต่อแล้วจากสูตร: ช็อตวางแผนจาก IFC คำบรรยาย ดนตรี เปิด Clip Studio พร้อมผลลัพธ์ ติดตามได้ทาง presentation-progress
exportPresentation({ resolution?, music? })Promise<PresentationVideo>เข้ารหัสงานนำเสนอในเบราว์เซอร์ของผู้เข้าชมและส่งไบต์ MP4 กลับมา — อัปโหลด แนบ หรือเล่นใน <video>
closePresentation()Promise<void>ปิด Clip Studio
สตูดิโอหน้าปก
getCoverOptions()Promise<CoverCatalog>สูตร เทมเพลต รูปแบบ และชุดสีที่สตูดิโอหน้าปกมีให้
createCover({ recipe?, template?, format?, palette?, text? })Promise<CoverState>สร้างหน้าปก: รันสูตร (ถ่ายมุมมองที่ต้องใช้) แล้วกำหนดเทมเพลต รูปแบบ ชุดสี และข้อความ เปิดสตูดิโอหน้าปกและยังแก้ไขต่อได้
getCover()Promise<CoverState | null>หน้าปกในสถานะปัจจุบัน หรือ null เมื่อปิดสตูดิโอ
exportCover({ type?, slide? })Promise<CoverFile>ส่งออก: png / jpeg (หนึ่งหน้า), pdf / pptx (ทั้งเอกสาร), zip (ทุกหน้า) ส่งไบต์กลับมา
closeCover()Promise<void>ปิดสตูดิโอหน้าปก
ชุดสำหรับบทความ
activate()voidเริ่มตัวดูที่โหลดภายหลังทันที (เหมือนปุ่มบนโปสเตอร์) คำสั่งก่อนหน้านี้จะรอในคิว
bindSteps(steps, { rootMargin? })() => voidScrollytelling: ขณะแต่ละย่อหน้าผ่านกลางจอ จะจัดเฟรม แยกองค์ประกอบ ขยับดวงอาทิตย์ หรือรันโค้ดของคุณ คืนค่าฟังก์ชันสำหรับยกเลิก
setTurntable(enabled | degPerSec)Promise<{ active, speed }>หมุนช้าๆ ขณะว่าง (องศาต่อวินาที; false เพื่อหยุด) หยุดเมื่อแตะครั้งแรก และไม่ทำงานเมื่อลดการเคลื่อนไหว
toggleFullscreen()Promise<void>ขยายภาพประกอบเต็มจอ หรือย้อนกลับ
setPaused(paused)Promise<{ paused }>หยุดหรือเริ่มการเรนเดอร์ pauseOffscreen ทำให้อัตโนมัติ
กลุ่มในฉาก
getGroups()Promise<SceneGroupsState>ทุกกลุ่มในฉาก — อนุมานจาก IFC (โครงการ ไซต์ ตำแหน่ง) และที่ผู้ใช้สร้าง
createGroup(name, modelIds?)Promise<string>สร้างกลุ่ม ใส่โมเดลได้ตามต้องการ ส่ง id กลับมา
renameGroup(id, name) · deleteGroup(id)Promise<void>เปลี่ยนชื่อหรือลบกลุ่มของผู้ใช้ กลุ่มอัตโนมัติแก้ไขไม่ได้
assignToGroup(itemId, groupId | null | 'loose')Promise<void>ย้ายโมเดลหรือพอยต์คลาวด์เข้ากลุ่ม; null คืนให้การจัดกลุ่มอัตโนมัติ, 'loose' ไม่อยู่ในกลุ่มใด
setGroupVisible(id, visible) · isolateGroup(id | null)Promise<void>แสดงหรือซ่อนทั้งกลุ่ม หรือแสดงเพียงกลุ่มเดียว
frameGroup(id)Promise<void>จัดกล้องให้พอดีกับกลุ่ม รวมสมาชิกที่ซ่อนอยู่
การวัด
setMeasureTool(tool)Promise<void>เปิดเครื่องมือวัดให้ผู้เข้าชม (เปิดแผงวัด) หรือ 'none' ผลลัพธ์มาทาง measurements-changed
getMeasurements()Promise<MeasurementsState>การวัดทั้งหมดบนหน้าจอ ค่าเป็นหน่วย SI เสมอ — เมตร, ตร.ม., องศา — ไม่ว่าจะแสดงอย่างไร
clearMeasurements(id?)Promise<MeasurementsState>ลบการวัดหนึ่งรายการ หรือทั้งหมด
การสืบค้น
getModels()Promise<ModelSummary[]>แสดงรายการโมเดลที่โหลด คืนค่า Promise
findElements({ ifcClass?, globalId?, name?, modelId?, limit? })Promise<ElementSummary[]>ค้นหาชิ้นส่วนตามคลาส IFC, GlobalId และ/หรือชื่อ ในโมเดลที่โหลด แต่ละผลลัพธ์มีข้อมูลที่ getElement(), select() และ frame() ต้องการ
getElement(expressId | globalId, modelId?)Promise<IfcElementData | null>ด้วย expressID หรือ GlobalId ข้อมูล IFC ของชิ้นส่วน: แอตทริบิวต์พร้อม GlobalId ชุดคุณสมบัติและปริมาณ และของประเภท — typeName, typeProperties — พร้อมหน่วย effectivePropertySets รวมทั้งสองแบบโดยค่าของชิ้นส่วนเองมีผลเหนือกว่า
validate(modelId?, { force? })Promise<ValidationRunResult>ตรวจสอบโมเดลทันที (ค่าเริ่มต้นคือโมเดลที่ใช้งาน) และคืนค่าด้วย Health Score และจำนวนปัญหา ปฏิเสธพร้อมเหตุผล
getValidation()Promise<ValidationSummary | null>ผลการตรวจสอบที่แสดงอยู่ หรือ null ก่อนที่การรันใดจะให้ผล ดู “วงจรการตรวจสอบ”
getValidationStatus()Promise<ValidationStatus>สถานะการตรวจสอบขณะนี้: idle, running (พร้อมความคืบหน้า), done หรือ error
getStats()Promise<StatsResult>จำนวนอิลิเมนต์ตามหมวดหมู่ของแต่ละโมเดล — สำหรับกราฟแดชบอร์ด คืนค่า Promise
getIssues({ severity?, limit?, modelId? })Promise<IssuesResult>ปัญหาการตรวจสอบสำหรับตาราง (กรองตามระดับ / จำกัดจำนวน) คืนค่า Promise
screenshot()Promise<string>จับภาพมุมมอง 3D ปัจจุบันเป็น PNG data URL คืนค่า Promise
compare({ base, head, baseLabel?, headLabel? })Promise<{ changes }>เปรียบเทียบงานส่งมอบสองชุดจาก URL (ตาม GlobalId) และเปิดพื้นที่เปรียบเทียบพร้อมผลลัพธ์
แผงเครื่องมือ
openPanel(panel | null) · closePanel()voidเปิดแผงเครื่องมือ หรือส่ง null เพื่อปิดแผงที่เปิดอยู่ แผงที่ไม่พร้อมใช้งาน — ถูกซ่อนโดยโครงหน้าจอ หรือยังไม่มีข้อมูลให้ทำงานด้วย — จะไม่เกิดอะไรขึ้น และไม่ถือเป็นข้อผิดพลาด
getPanels()Promise<PanelsResult>แผงที่เปิดอยู่ และแผงที่มีให้เลือกในขณะนี้ เครื่องมือที่ไม่ปรากฏใน `available` จะเปิดไม่ได้ ไม่ใช่แค่ถูกปิดการใช้งาน
setPanels(panels)voidจำกัดแถบเครื่องมือให้เหลือเฉพาะแผงเหล่านี้ขณะทำงาน ใช้คำเดียวกับพารามิเตอร์ URL `panels=` และมีลำดับความสำคัญเหนือกว่า จะจำกัดสิ่งที่ตัวแสดงผลมีให้เท่านั้น ไม่เพิ่มใหม่ อาร์เรย์ว่างหมายถึงไม่มีแถบ
พอยต์คลาวด์
addPointCloud(fileName, bytes)Promise<string>โหลดไฟล์สแกนจากไบต์ — LAS, LAZ, COPC, PLY, PCD หรือข้อความ บัฟเฟอร์จะถูกโอนย้ายไม่ใช่คัดลอก และคืนค่า id ของกลุ่มจุด
addPointCloudFromUrl(url, fileName?)Promise<string>โหลดไฟล์สแกนที่ตัวโปรแกรมไปดึงมาเอง URL ต้องอนุญาต CORS
listPointClouds()Promise<PointCloudInfo[]>ไฟล์สแกนที่โหลดไว้ทั้งหมด `pointCount` คือจำนวนที่อยู่ในหน่วยความจำ ส่วน `declaredCount` คือจำนวนที่ไฟล์มี — ต่างกันเมื่อโควตาตัดการอ่านให้สั้นลง
removePointCloud(cloudId) · clearPointClouds()Promise<void>เอาไฟล์สแกนหนึ่งชิ้นออกและคืนบัฟเฟอร์ GPU หรือเอาออกทั้งหมด
setPointCloudVisible(cloudId, visible)Promise<void>แสดงหรือซ่อนไฟล์สแกนโดยไม่ต้องเอาออกจากหน่วยความจำ
fitPointCloud(cloudId?)Promise<void>จัดกล้องให้พอดีกับไฟล์สแกนหนึ่งชิ้น หรือชิ้นแรกที่โหลด
setPointCloudDisplay(display, renderBudget?)Promise<void>ขนาดจุด ความทึบ โหมดสี ความหนาแน่น และเกณฑ์ความเชื่อมั่น — ใช้ร่วมกันทุกไฟล์ ทุกค่าเป็น uniform ของเชเดอร์ จึงมีผลทันทีแม้ที่ 20 ล้านจุด
inspectPointCloud(enabled?)Promise<void>เปิดโหมดคลิกเพื่ออ่านค่า เมื่อเปิดอยู่ การคลิกจุดจะส่ง `pointcloud-picked` พร้อมพิกัดในไฟล์ควบคู่กับพิกัดในฉาก
setPointCloudPlacement(placement, cloudId?)Promise<void>ปรับไฟล์สแกนด้วยมือ: ตำแหน่ง การหมุน การปรับระดับ และมาตราส่วน ส่งบางส่วนได้และค่าจะถูกจำกัด โดยซ้อนทับการจัดแนวที่คำนวณไว้ ไม่ได้แทนที่
setPointCloudUpAxis(axis, cloudId?)Promise<void>แก้ว่าไฟล์สแกนถือแกนใดเป็นแกนขึ้นบน PLY, PCD และข้อความไม่ระบุทิศทาง โปรแกรมจึงอนุมานเอง — ตรวจ `upAxisSource` ก่อนเชื่อ
Imported 3D models
addMesh(files)Promise<string>นำเข้าโมเดลจากไบต์ รับเป็นรายการ: .gltf ต้องมี .bin และเท็กซ์เจอร์ ส่วน .obj ต้องมี .mtl — ถ้าส่งเฉพาะไฟล์หลักจะได้เรขาคณิตสีเทา
addMeshFromUrl(urls)Promise<string>นำเข้าโมเดลที่ตัวโปรแกรมไปดึงมาเอง ส่ง URL ทุกไฟล์ที่ต้องใช้ ระบบจะดึงพร้อมกันและทุกไฟล์ต้องอนุญาต CORS
listMeshes()Promise<MeshInfo[]>โมเดลที่นำเข้าทั้งหมด พร้อมจำนวนสามเหลี่ยมและเท็กซ์เจอร์ ควรอ่าน `unitSource` และ `upAxisSource` ก่อนเชื่อค่าที่อยู่ข้าง ๆ
removeMesh(meshId?) · clearMeshes()Promise<void>เอาโมเดลที่นำเข้าออกหนึ่งชิ้นและคืนเรขาคณิต วัสดุ และเท็กซ์เจอร์ หรือเอาออกทั้งหมด
setMeshVisible(visible, meshId?)Promise<void>แสดงหรือซ่อนโมเดลที่นำเข้าโดยไม่ต้องเอาออกจากหน่วยความจำ
fitMesh(meshId?)Promise<void>จัดกล้องให้พอดีกับโมเดลที่นำเข้าหนึ่งชิ้น หรือทั้งหมด
setMeshPlacement(placement, meshId?)Promise<void>วางโมเดลที่นำเข้าด้วยมือ: ตำแหน่ง การหมุน การปรับระดับ และมาตราส่วน ส่งบางส่วนได้และค่าจะถูกจำกัด โมเดลจะเริ่มอยู่กึ่งกลางบน IFC และวางบนพื้นของมัน
setMeshUpAxis(axis, meshId?)Promise<void>แก้แกนแนวตั้งของต้นทาง มีความหมายเฉพาะกับ OBJ — ข้อกำหนดของ glTF บังคับให้ Y ขึ้นบน จึงรายงานเป็น `upAxisSource: 'declared'`
setMeshUnit(unitScale, meshId?)Promise<void>แก้หน่วยของต้นทาง: 1 เมตร, 0.01 เซนติเมตร, 0.001 มิลลิเมตร, 0.3048 ฟุต ไม่มีรูปแบบใดบันทึกหน่วยไว้ โปรแกรมจึงอนุมานจากขนาดของโมเดล
ความสอดคล้อง IDS และ EIR
checkIds(idsXml)Promise<IdsResult>ตรวจโมเดลกับ IDS ของ buildingSMART (ไฟล์ XML .ids) ทำงานใน worker และคืนผลผ่าน/ไม่ผ่านแยกตามข้อกำหนด
checkEir(profile)Promise<IdsResult>ตรวจแบบจำลองตามโปรไฟล์การตรวจสอบ EIR / BIM — เป็นออบเจกต์ JSON ของมัน (รวมรูปแบบย่อ) หรือ id ของโปรไฟล์ในตัว เช่น 'builtin-en14351-1' คอมไพล์เป็น IDS และทำงานบนเอนจินเดียวกัน: ได้ IdsResult รูปแบบเดียวกับ checkIds
ภาษา
setLanguage(lang)voidเปลี่ยนภาษาอินเทอร์เฟซขณะทำงาน
getLanguages()string[]แสดงรายการรหัสภาษาที่รองรับ (ป้ายกำกับดูที่ IfcViewer.LANGUAGES)
วงจรชีวิตและอีเวนต์
whenReady()Promise<void>Resolve เมื่อตัวแสดงผลพร้อมรับคำสั่ง
isReadybooleanGetter — เป็น true ทันทีที่ตัวแสดงผลแจ้งว่าพร้อมแล้ว
on(event, cb)() => voidสมัครรับอีเวนต์ คืนค่าฟังก์ชันสำหรับยกเลิก
off(event, cb)voidลบตัวรับอีเวนต์ที่เพิ่มไว้ก่อนหน้า
dispose()voidรื้อตัวแสดงผลและลบ iframe
การเรียก add() / addFromUrl() พร้อมกันจะถูกทำให้เป็นลำดับภายใน และแต่ละ promise จะจับคู่กับการโหลดของตัวเอง — การโหลดที่เริ่มโดยแอป (พารามิเตอร์ URL หรือการอัปโหลดภายใน iframe) จะไม่มีวัน resolve promise ของ add() ที่คุณเรียก
ตัวเลือกของ constructor
| ตัวเลือก | ชนิด | ค่าเริ่มต้น | คำอธิบาย |
|---|---|---|---|
ui | 'minimal' | 'full' | 'kiosk' | 'client' | 'article' | 'embed' | 'minimal' | พรีเซ็ตอินเทอร์เฟซ 'embed': สำหรับแอปโฮสต์ที่ส่งไฟล์เอง — 3D แสดงคุณสมบัติเมื่อเลือก แถบเครื่องมือแบบกะทัดรัดที่มี ตรวจสอบ และ วัด ไม่มี เปิด แชร์ บัญชี หรือภาษา |
toolbar | boolean | preset | แสดงแถบเครื่องมือ (ค่าเริ่มต้นตามพรีเซ็ต) false จะเหลือเพียงมุมมอง 3D และแผง |
tools | ('validate' | 'measure')[] | ['validate', 'measure'] | เครื่องมือบนแถบกะทัดรัดของ ui: 'embed' — 'validate', 'measure' [] หมายถึงไม่มี |
autoFrame | boolean | true | จัดเฟรมทั้งฉากจากมุม iso พร้อมระยะขอบทุกครั้งที่การโหลดเสร็จ false ให้คุณควบคุมกล้องเอง |
validate | boolean | true | รันการตรวจสอบตอนโหลด (เป็นที่มาของ Health Score) |
panel | boolean | false | เปิดแผงการตรวจสอบโดยอัตโนมัติ |
panels | PanelName[] | — | จำกัดแถบเครื่องมือให้เหลือเฉพาะแผงเหล่านี้ตั้งแต่เฟรมแรก — `['scene','map']` หรือ `[]` เพื่อไม่ให้มีแถบ ใช้ชื่อเดียวกับ setPanels และพารามิเตอร์ URL `panels=` ดีกว่าการเรียก setPanels หลังโหลด ซึ่งจะแสดงแถบเต็มก่อนแล้วค่อยเอาเครื่องมือออก |
lang | string | auto | บังคับภาษาของอินเทอร์เฟซ (เช่น en, es, ja) |
accent | #rrggbb | brand | ปรับสีตัวแสดงผลให้เข้ากับแดชบอร์ดของคุณ |
model | string | — | โหลด URL สาธารณะของ IFC นี้โดยอัตโนมัติเมื่อพร้อม |
background | BackgroundSpec | — | พื้นหลังฉากตั้งแต่เฟรมแรก — พรีเซ็ต, '#rrggbb' หรือ '#บน,#ล่าง' |
map | boolean | ('terrain' | 'buildings' | 'showcase')[] | — | วางโมเดลบนแผนที่เมื่อโหลดเสร็จ: true หรือเลเยอร์ ['terrain','buildings','showcase'] |
solar · moon | 'MM-DDTHH:MM' · boolean | — | เปิดการศึกษาแสงแดดที่เวลาท้องถิ่นของไซต์: '06-21T18:00' |
scans | string[] | — | URL ของพอยต์คลาวด์ที่จะโหลดพร้อมโมเดล |
layers | string | — | การตั้งค่าชั้นข้อมูลที่จะเปิด: URL ของ JSON ที่ส่งออกจากแผงชั้นข้อมูล (แหล่งสด สไตล์ การแจ้งเตือน) ตั้งแต่ 1.16 |
scene | string | — | ฉากที่จะเปิด: URL ของเอกสารฉาก (โมเดล ชั้นข้อมูล อุปกรณ์สด แผนที่ กล้อง) ตั้งแต่ 1.17 |
view · fill | CameraView · number | — | เมื่อโหลดโมเดลครบ จัดเฟรมให้พอดีจากมุมมองนี้ ui: 'article' ใช้ 'iso' โดยอัตโนมัติ |
wheel | 'always' | 'ctrl' | 'always' | 'ctrl': ล้อเมาส์เลื่อนหน้าเว็บของคุณ และซูมเฉพาะเมื่อกด Ctrl/⌘ เหมือนแผนที่ฝัง ui: 'article' ใช้โดยอัตโนมัติ |
lazy | boolean | 'visible' | false | ไม่โหลดจนกว่าจะต้องการ: true แสดงโปสเตอร์พร้อมปุ่ม, 'visible' เริ่มเมื่อภาพเข้าใกล้หน้าจอ |
poster · posterTitle · posterText · launchLabel | string | — | ภาพ ชื่อ ข้อความ และปุ่มของโปสเตอร์ จนกว่าตัวดูจะพร้อม |
aspectRatio | string ('16/10') | — | กำหนดขนาดตามสัดส่วน ('16/10'): โปสเตอร์และตัวดูใช้กล่องเดียวกัน หน้าเว็บไม่กระโดด |
turntable | boolean | number | false | หมุนช้าๆ หลังโหลด (true = 6°/วินาที หรือระบุองศาต่อวินาที) |
pauseOffscreen | boolean | ui === 'article' | หยุดเรนเดอร์เมื่ออยู่นอกจอ ค่าเริ่มต้น true เมื่อใช้ ui: 'article' |
fullscreenButton | boolean | false | ปุ่มขยายเล็กๆ ที่มุม |
height · width | number | string | '100%' | ขนาดของ iframe (ตัวเลข → px) |
baseUrl | string | auto | URL ฐานของแอป อนุมานจาก URL ของสคริปต์ |
loadTimeout | number | 120000 | Reject add()/addFromUrl() หลังผ่านไป N มิลลิวินาที (0 คือปิดใช้งาน) |
className · title | string | — | คลาสเพิ่มเติม / title ของ iframe |
on* callbacks | function | — | คอลแบ็กเพื่อความสะดวก — เหมือนกับ .on(...) |
อีเวนต์
| อีเวนต์ | Payload | คำอธิบาย |
|---|---|---|
ready | { languages } | ตัวแสดงผลติดตั้งและพร้อม |
model-progress | { percent, phase } | ความคืบหน้าการโหลด (ดาวน์โหลด → แปลง → เรนเดอร์) |
model-loaded | { modelId, fileName, elementCount, fromCache } | โมเดลโหลดเสร็จแล้ว |
validation-started | { modelId } | เริ่มการตรวจสอบโมเดลแล้ว — หลังการโหลด (validate: true) หรือจาก validate() |
validation-completed | { modelId, qualityScore, errors, warnings, info, total } | การตรวจสอบเสร็จแล้ว: Health Score และจำนวน พร้อมโมเดลที่เกี่ยวข้อง (null สำหรับผลรวมของฉากที่รวมหลายโมเดล) |
validation-failed | { modelId, message } | การตรวจสอบไม่สามารถรันหรือไม่เสร็จ — พร้อมเหตุผล |
model-error | { message, url?, name? } | การโหลดล้มเหลว |
element-selected | { expressId, modelId, ifcType, name, globalId } | ผู้ใช้เลือกชิ้นส่วน — พร้อม GlobalId เพื่อจับคู่กับข้อมูลของคุณเอง |
pointcloud-picked | { cloudId, position, sourcePosition, classification, intensity, distance } | อ่านค่าจุดจากข้อมูลสแกน โดยใช้พิกัดของไฟล์ ไม่ใช่ของฉาก |
map-feature-picked | { id, name?, label?, featureKind, heightM?, heightEstimated } | คลิกอาคารในสภาพแวดล้อมจาก OpenStreetMap เป็นบริบท ไม่ใช่โมเดล |
walk-changed | { active, speed } | โหมดเดินถูกเปิดหรือปิด — โดยผู้เข้าชมหรือโฮสต์ |
measurements-changed | { tool, units, items } | มีการเพิ่ม ลบ หรือเปลี่ยนชื่อการวัด ส่งรายการทั้งหมด |
tour-started | { title, total, template } | ทัวร์เริ่มเล่น |
tour-step | { index, total, caption } | ทัวร์ย้ายไปจุดหยุดอื่น |
tour-ended | { completed } | ทัวร์หยุดลง; completed เมื่อถึงจุดสุดท้าย |
presentation-progress | { stage, label?, progress } | ผู้กำกับกำลังสร้างหรือส่งออกงานนำเสนอ |
layer-feature-picked | { layerId, layer, featureId, geometry, lonLat, properties } | มีการคลิกฟีเจอร์ของชั้นข้อมูล: ชั้นข้อมูล คุณสมบัติ และจุดสำหรับทำเครื่องหมาย |
alert | { kind, from, id, name, rule, count, sample } | กฎของชั้นข้อมูลหรือของทวินเริ่มหรือหยุดแจ้งเตือน |
สมาชิกแบบสแตติก
IfcViewer.LANGUAGES ({ code, label }[], ชื่อในภาษานั้น ๆ) และ IfcViewer.SUPPORTED_LANGUAGES (รหัสภาษา) — ใช้สร้างตัวเลือกภาษาได้ก่อนที่ตัวแสดงผลจะพร้อม
Web component
ไม่ต้องเขียน JavaScript — วางแท็กลงในหน้าเว็บหรือแดชบอร์ดใดก็ได้ แล้วกำหนดแอตทริบิวต์
แอตทริบิวต์: modeluilangaccentvalidatepanelbase-urltoolbartoolsauto-frame
อีเวนต์จะถูกส่งซ้ำเป็น CustomEvent ของ DOM ชื่อ ifcviewer:<type> (detail = payload) ตัว IfcViewer ที่อยู่เบื้องหลังอยู่ที่ .viewer ของอิลิเมนต์ ส่วน getStats / getIssues / screenshot จะส่งต่อไปยังตัวนั้น
const el = document.querySelector("ifc-viewer");
el.addEventListener("ifcviewer:validation-completed", (e) => {
updateScore(e.detail.qualityScore);
});
const stats = await el.getStats();คู่มือ
สูตรสำเร็จ
รูปแบบที่คัดลอกไปวางได้ สำหรับสิ่งที่ทีมสร้างกันจริง ๆ
ตัวตรวจสอบอิลิเมนต์
คลิกในมุมมอง 3 มิติ แล้วอ่าน property set ของ IFC เข้าสู่แผงของคุณเอง
viewer.on("element-selected", async (e) => {
const data = await viewer.getElement(e.expressId, e.modelId);
renderPanel(data.name, data.globalId, data.propertySets);
});วิดเจ็ต Health Score
แสดงคะแนนคุณภาพของโมเดลและจำนวนปัญหาบนแดชบอร์ดของคุณ
viewer.on("validation-completed", (v) => {
setScore(v.qualityScore); // 0–100, or null
setCounts(v.errors, v.warnings, v.info);
});ตรวจความสอดคล้องด้วย IDS
ตรวจกับ IDS ของ buildingSMART แล้วแสดงผลผ่าน / ไม่ผ่าน แยกตามข้อกำหนด
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)));
});การตรวจสอบ EIR / BIM
รันโปรไฟล์ EIR ที่แก้ไขได้ (สไตล์ ISO 19650) — ไม่ต้องมีไฟล์ .ids ผลที่ไม่ผ่านจะมี GlobalId ของอิลิเมนต์ติดมาด้วย
// 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(" · ")));
});ฉากสำหรับบทความบล็อก
พื้นหลังสีขาว โมเดลบนไซต์ของมัน และเงายามเย็นของวันครีษมายัน — กำหนดไว้ล่วงหน้า ไม่ต้องคลิก
// 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ผังพื้นและการวัด
ตัดผังพื้นที่ชั้นตามชื่อ ให้ผู้อ่านวัด แล้วอ่านผลเป็นหน่วย 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)));
});ทัวร์นำชมในบทความ
กำหนดจุดหยุดครั้งเดียวด้วย getCamera() แล้วปล่อยให้ทัวร์เล่นเองข้างเนื้อหา
// 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));วิดีโอของโมเดลจากหน้าของคุณ
สร้างงานนำเสนอด้วยสูตรแล้วรับไฟล์ MP4 กลับมา — ไม่ต้องอัดหน้าจอ ไม่ต้องอัปโหลด
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 }));หน้าปกสำหรับโพสต์ของคุณ
สร้างพินบน Pinterest หรือการ์ด LinkedIn ของโมเดลพร้อมชื่อเรื่องของคุณ แล้วรับไฟล์ 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 }));โมเดลในบทความ
ui: 'article' มีเพียงแคนวาส จัดเฟรมให้โมเดลเต็มพื้นที่ และล้อเมาส์ให้ผู้อ่านเลื่อนอ่านต่อได้ เลือกมุมด้วย 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,
});เรื่องราวที่เดินไปพร้อมข้อความ
ตรึงตัวดูไว้ ให้แต่ละย่อหน้ากำหนดมุมกล้อง: แยกเฉพาะผนัง หมุนรอบ และเติมแสงแดดยามเย็น
// <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" } },
]);ทวินเมืองแบบสดในหน้าเว็บของคุณ
เปิดฉากที่เผยแพร่แล้ว ตอบสนองต่อการแจ้งเตือนและการคลิก เพิ่มแหล่งข้อมูลสดของคุณเองไว้ข้างกัน และแชร์ลิงก์ของผลลัพธ์
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" });หน้าข้อมูลสินค้าในแอปของคุณ
ตรึงเวอร์ชัน ส่งไฟล์ และแสดงสิ่งที่ผู้ผลิตใส่ไว้ในประเภท: ค่า U ระดับแรงลม ผู้ผลิต
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);ปรับให้เข้ากับแบรนด์
จับคู่ตัวแสดงผลกับผลิตภัณฑ์ของคุณด้วยสีเน้นเพียงสีเดียว
new IfcViewer("#viewer", { accent: "#3fa866" });
// …or on the web component:
// <ifc-viewer accent="#3fa866" model="…"></ifc-viewer>ตัวเลือกภาษา
สร้างตัวเลือกจากภาษาที่มาพร้อมกับตัวแสดงผล
// build a <select> from the bundled languages
IfcViewer.LANGUAGES.forEach(({ code, label }) => addOption(code, label));
// switch at runtime
viewer.setLanguage("ja");Health Score
วงจรการตรวจสอบ
validate: true ตรวจสอบแต่ละโมเดลเมื่อโหลดเสร็จ validate() รันตามต้องการและคืนค่าด้วยผลลัพธ์ สิ่งที่ getValidation() คืนในแต่ละช่วง:
| เมื่อใด | getValidation() คืนค่า |
|---|---|
| ก่อนการตรวจสอบใด ๆ | null — getValidationStatus() บอกว่า idle |
| ระหว่างการรันครั้งแรก | null — getValidationStatus() บอกว่า running พร้อมความคืบหน้า และ validation-started ถูกส่งแล้ว |
| หลังการรัน | { qualityScore, errors, warnings, info, total, status: 'done', modelId } หากมีหลายโมเดลจะเป็นผลรวม (modelId: null) |
| ระหว่างการรันครั้งใหม่ | ตัวเลขก่อนหน้า พร้อม status: 'running' |
| หลังการรันล้มเหลว | ตัวเลขก่อนหน้าพร้อม status: 'error' และ error — หรือ null หากไม่มี validation-failed ถูกส่งและ validate() ปฏิเสธแล้ว |
เวอร์ชัน
ตรึงเวอร์ชัน
ตั้งแต่ 1.18.0 ทุกรีลีสจะเผยแพร่ที่พาธที่ไม่เปลี่ยนแปลงด้วย ตรึงไว้ในโปรดักชัน ส่วนพาธที่เปลี่ยนได้จะตามรีลีสล่าสุด
<script type="module">
import { IfcViewer } from "https://www.ifcvieweronline.eu/sdk/1.18.0/ifc-viewer.es.js";
</script>| พาธ | แคช | การใช้งาน |
|---|---|---|
/sdk/ifc-viewer.es.js | ตรวจสอบใหม่ทุกครั้งที่โหลด | สำหรับทดลอง — ล่าสุดเสมอ |
/sdk/1.18.0/ifc-viewer.es.js | ไม่เปลี่ยนแปลง แคชหนึ่งปี | โปรดักชัน — โค้ดที่คุณทดสอบคือโค้ดที่รัน |
SDK ที่ตรึงไว้เปิดตัวแสดงผลตัวไหน?
iframe คือตัวแสดงผลจริงที่ origin เดียวกัน ไม่ใช่สำเนาที่แช่แข็ง: SDK ที่ตรึงไว้คง API และพฤติกรรมของตัวเอง ขณะที่ตัวแสดงผลด้านในยังพัฒนาต่อ ตัวแสดงผลตอบทุกคำสั่งจาก SDK ทุกเวอร์ชันที่เผยแพร่ — มีการตรวจตอน build — เวอร์ชันเก่าที่ตรึงไว้จึงยังใช้งานได้
ตรึงเนื้อหาด้วย
versions.json ข้าง SDK แสดงแฮช Subresource Integrity ของแต่ละเวอร์ชัน:
<script type="module" src="https://www.ifcvieweronline.eu/sdk/1.18.0/ifc-viewer.es.js"
integrity="sha384-WsT4d0DS1oqva4rLWCGBDrx50uZVA3B95IDWbfxaFt8lnjs6NgOrjKNcWQL57/0z" crossorigin="anonymous"></script>บันทึกการเปลี่ยนแปลง
1.18.0 — วัตถุแคตตาล็อกในแอปโฮสต์ getElement() คืน GlobalId และข้อมูลของประเภท (typeName, typeProperties พร้อมหน่วย, effectivePropertySets; ประเภทของ IFC4 และสไตล์ของ IFC2x3) เพิ่ม validate(), getValidationStatus(), validation-started / validation-failed และ validate: true ตรวจสอบจริงหลังการโหลดทุกครั้ง จัดเฟรมอัตโนมัติหลังโหลด fit() และ frame() มีระยะขอบสำหรับวัตถุขนาดเล็ก frame(elementId) และ setView() ขยับกล้องได้อีกครั้ง ui: 'embed' พร้อม toolbar และ tools บิลด์ที่ตรึงไว้อยู่ที่ /sdk/1.18.0/ เพิ่ม findElements(), GlobalId ใน getElement() / select() / element-selected, ชุดคุณสมบัติวงกบและบานของหน้าต่างและประตู และวัสดุของประเภท ใน ui: 'embed' แผงคุณสมบัติจะไม่บังวัตถุที่เลือก checkEir('builtin-en14351-1') ตรวจสมรรถนะที่ประกาศของหน้าต่างและประตูภายนอกตาม EN 14351-1 — แรงลม การกันน้ำ เสียง ค่าการถ่ายเทความร้อน g และ τv ผู้ผลิตและรหัสผลิตภัณฑ์ — บน property set มาตรฐานของ IFC; กฎ EIR เพิ่ม where (เฉพาะองค์ประกอบที่มีค่าคุณสมบัติ) และ optional
ความช่วยเหลือ
การแก้ปัญหา
ไม่กี่เรื่องที่คนมักสะดุด
ไม่มีอะไรถูกเรนเดอร์
ตรวจตามลำดับนี้: ตัวแสดงผลถึงสถานะ ready แล้วหรือไม่, โมเดลโหลดสำเร็จหรือไม่ (model-loaded ไม่ใช่ model-error), คอนเทนเนอร์มีความสูงไม่เป็นศูนย์หรือไม่ และแหล่งไบต์เป็น IFC ที่ถูกต้องหรือไม่ ข้อความของ model-error จะบอกสาเหตุ
เจอข้อผิดพลาด CORS ตอนโหลดโมเดล
CORS มีผลเฉพาะกับ addFromUrl(url) หากคุณควบคุมไบต์ได้เอง ให้ดึงเองแล้วเรียก add(name, bytes) — ไม่มี CORS เข้ามาเกี่ยวข้อง เพราะคุณส่งข้อมูลให้โดยตรง
iframe ว่างเปล่าหรือไม่ยอมโหลด
แอปต้องฝังได้ — ต้องไม่มี X-Frame-Options: DENY และไม่มี CSP frame-ancestors ที่เข้มงวดบนโฮสต์ แอปที่เราโฮสต์อนุญาตให้ฝังได้
ArrayBuffer ของฉันว่างเปล่าหลังเรียก add()
add() จะโอนบัฟเฟอร์เพื่อส่งมอบแบบไม่คัดลอก ซึ่งทำให้มันกลายเป็น detached ในโค้ดของคุณ ให้ส่งสำเนา (bytes.slice(0)) หากยังต้องใช้ไบต์เหล่านั้นภายหลัง
การโหลดหรือการสืบค้นหมดเวลา
add()/addFromUrl() จะ reject หลังครบ loadTimeout (ค่าเริ่มต้น 120 วินาที; 0 คือปิดใช้งาน) การสืบค้นจะ reject ที่ 30 วินาที และ checkIds ที่ 120 วินาที โมเดลขนาดใหญ่บนอุปกรณ์ที่ช้าอาจต้องใช้ loadTimeout ที่สูงขึ้น
รองรับเบราว์เซอร์ใดบ้าง?
เบราว์เซอร์สมัยใหม่ที่อัปเดตต่อเนื่องและรองรับ WebAssembly กับ ES module — Chrome, Edge, Firefox, Safari ไม่รองรับ Internet Explorer
ได้ 404 บนเซิร์ฟเวอร์พัฒนาเครื่องผม
เซิร์ฟเวอร์พัฒนาแบบ SPA อาจดักเส้นทาง /sdk/ ด้วย HTML fallback ให้ทดสอบกับ build จริงหรือโฮสต์แบบสแตติก หรือนำเข้าโมดูล /sdk/ifc-viewer.es.js โดยตรง