🚀 Auto-deploy: melhoria no snap e medição AR em 01/06/2026 00:41:59
This commit is contained in:
@@ -9,6 +9,19 @@ import { registerModelLocalGroup, unregisterModelLocalGroup, getModelWorldScaleF
|
|||||||
import { parseIFCtoThree } from '@/lib/convertIFC';
|
import { parseIFCtoThree } from '@/lib/convertIFC';
|
||||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
||||||
import { mainCameraRef, mainControlsRef, viewAnim, calibration, pushModelFaceNormal, computeCalibrationQuaternion, subscribeCalibration } from './viewCubeBus';
|
import { mainCameraRef, mainControlsRef, viewAnim, calibration, pushModelFaceNormal, computeCalibrationQuaternion, subscribeCalibration } from './viewCubeBus';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
function getColorForMaterialName(name: string): string {
|
||||||
|
if (!name) return '#a1a1aa';
|
||||||
|
let hash = 0;
|
||||||
|
for (let i = 0; i < name.length; i++) {
|
||||||
|
hash = name.charCodeAt(i) + ((hash << 5) - hash);
|
||||||
|
}
|
||||||
|
const h = Math.abs(hash) % 360;
|
||||||
|
const s = 65 + (Math.abs(hash >> 8) % 15);
|
||||||
|
const l = 45 + (Math.abs(hash >> 16) % 10);
|
||||||
|
return `hsl(${h}, ${s}%, ${l}%)`;
|
||||||
|
}
|
||||||
|
|
||||||
interface ModelViewerProps {
|
interface ModelViewerProps {
|
||||||
url?: string; // legacy, ignored — uses store.models
|
url?: string; // legacy, ignored — uses store.models
|
||||||
@@ -195,6 +208,7 @@ function GLBModel({ sceneModel, isActive }: { sceneModel: SceneModel; isActive:
|
|||||||
const setActive = useModelStore((s) => s.setActiveModel);
|
const setActive = useModelStore((s) => s.setActiveModel);
|
||||||
const selectionMode = useModelStore((s) => s.selectionMode);
|
const selectionMode = useModelStore((s) => s.selectionMode);
|
||||||
const measureMode = useModelStore((s) => s.measureMode);
|
const measureMode = useModelStore((s) => s.measureMode);
|
||||||
|
const ifcMaterialColors = useModelStore((s) => s.ifcMaterialColors);
|
||||||
const fineTuning = sceneModel.fineTuning;
|
const fineTuning = sceneModel.fineTuning;
|
||||||
|
|
||||||
// Re-render when calibration state changes (so calQuat resets to identity during the flow).
|
// Re-render when calibration state changes (so calQuat resets to identity during the flow).
|
||||||
@@ -260,11 +274,17 @@ function GLBModel({ sceneModel, isActive }: { sceneModel: SceneModel; isActive:
|
|||||||
mat.color.setHSL(0, 0.7, 0.5);
|
mat.color.setHSL(0, 0.7, 0.5);
|
||||||
} else if (allApproved) {
|
} else if (allApproved) {
|
||||||
mat.color.setHSL(0.38, 0.7, 0.45);
|
mat.color.setHSL(0.38, 0.7, 0.45);
|
||||||
|
} else {
|
||||||
|
const root = findElementRoot(child);
|
||||||
|
const matName = root?.userData?.materialName;
|
||||||
|
if (ifcMaterialColors && matName) {
|
||||||
|
mat.color.set(getColorForMaterialName(matName));
|
||||||
} else {
|
} else {
|
||||||
// Tint with the per-model color (subtle)
|
// Tint with the per-model color (subtle)
|
||||||
mat.color.set(sceneModel.color);
|
mat.color.set(sceneModel.color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
mat.needsUpdate = true;
|
mat.needsUpdate = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -284,7 +304,7 @@ function GLBModel({ sceneModel, isActive }: { sceneModel: SceneModel; isActive:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [scene, opacity, renderMode, checklist, wireframeColor, wireframeThickness, edgeThresholdAngle, sceneModel.color, measureMode]);
|
}, [scene, opacity, renderMode, checklist, wireframeColor, wireframeThickness, edgeThresholdAngle, sceneModel.color, measureMode, ifcMaterialColors]);
|
||||||
|
|
||||||
const rotXRad = (fineTuning.rotX * Math.PI) / 180;
|
const rotXRad = (fineTuning.rotX * Math.PI) / 180;
|
||||||
const rotYRad = (fineTuning.rotY * Math.PI) / 180;
|
const rotYRad = (fineTuning.rotY * Math.PI) / 180;
|
||||||
@@ -716,6 +736,7 @@ function HoverDetector() {
|
|||||||
const hoverTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
const hoverTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
const hideTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
const hideTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
const lastHitKey = useRef('');
|
const lastHitKey = useRef('');
|
||||||
|
const lastHitExpressId = useRef<number>(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const clearTimers = () => {
|
const clearTimers = () => {
|
||||||
@@ -741,11 +762,59 @@ function HoverDetector() {
|
|||||||
|
|
||||||
if (!hit || !(hit.object instanceof THREE.Mesh)) {
|
if (!hit || !(hit.object instanceof THREE.Mesh)) {
|
||||||
lastHitKey.current = '';
|
lastHitKey.current = '';
|
||||||
|
lastHitExpressId.current = 0;
|
||||||
setHoverInfo(null);
|
setHoverInfo(null);
|
||||||
clearTimers();
|
clearTimers();
|
||||||
|
toast.dismiss('ifc-hover-info');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Detecção de propriedades do IFC de forma ágil baseada em mudança de elemento
|
||||||
|
const elementRoot = findElementRoot(hit.object);
|
||||||
|
const expressID = elementRoot?.userData?.ifcId ?? 0;
|
||||||
|
|
||||||
|
if (expressID !== lastHitExpressId.current) {
|
||||||
|
lastHitExpressId.current = expressID;
|
||||||
|
const props = elementRoot?.userData?.properties;
|
||||||
|
|
||||||
|
if (props && (props.name || props.material || props.tag)) {
|
||||||
|
const infoLines: string[] = [];
|
||||||
|
if (props.tag || props.name) {
|
||||||
|
infoLines.push(`Peça: ${props.tag || props.name}`);
|
||||||
|
}
|
||||||
|
if (props.material) {
|
||||||
|
infoLines.push(`Material: ${props.material}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const lengthKey = Object.keys(props).find(k => {
|
||||||
|
const kl = k.toLowerCase();
|
||||||
|
return kl.includes('length') || kl.includes('comprimento') || kl.includes('lengthvalue');
|
||||||
|
});
|
||||||
|
|
||||||
|
if (lengthKey && props[lengthKey]) {
|
||||||
|
infoLines.push(`Comprimento: ${props[lengthKey]}`);
|
||||||
|
} else {
|
||||||
|
const commonLengthKeys = ['Length', 'Comprimento', 'LengthValue', 'height', 'comprimento'];
|
||||||
|
for (const key of commonLengthKeys) {
|
||||||
|
if (props[key]) {
|
||||||
|
infoLines.push(`Comprimento: ${props[key]}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (infoLines.length > 0) {
|
||||||
|
toast(infoLines.join(' | '), {
|
||||||
|
id: 'ifc-hover-info',
|
||||||
|
duration: 8000,
|
||||||
|
position: 'bottom-center',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toast.dismiss('ifc-hover-info');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Stability check – same approximate position for debounce (~3 mm)
|
// Stability check – same approximate position for debounce (~3 mm)
|
||||||
const key = `${hit.point.x.toFixed(3)},${hit.point.y.toFixed(3)},${hit.point.z.toFixed(3)}`;
|
const key = `${hit.point.x.toFixed(3)},${hit.point.y.toFixed(3)},${hit.point.z.toFixed(3)}`;
|
||||||
if (key === lastHitKey.current) return; // timer already running
|
if (key === lastHitKey.current) return; // timer already running
|
||||||
@@ -808,8 +877,10 @@ function HoverDetector() {
|
|||||||
|
|
||||||
const onLeave = () => {
|
const onLeave = () => {
|
||||||
lastHitKey.current = '';
|
lastHitKey.current = '';
|
||||||
|
lastHitExpressId.current = 0;
|
||||||
setHoverInfo(null);
|
setHoverInfo(null);
|
||||||
clearTimers();
|
clearTimers();
|
||||||
|
toast.dismiss('ifc-hover-info');
|
||||||
};
|
};
|
||||||
|
|
||||||
gl.domElement.addEventListener('pointermove', onMove);
|
gl.domElement.addEventListener('pointermove', onMove);
|
||||||
@@ -818,6 +889,7 @@ function HoverDetector() {
|
|||||||
gl.domElement.removeEventListener('pointermove', onMove);
|
gl.domElement.removeEventListener('pointermove', onMove);
|
||||||
gl.domElement.removeEventListener('pointerleave', onLeave);
|
gl.domElement.removeEventListener('pointerleave', onLeave);
|
||||||
clearTimers();
|
clearTimers();
|
||||||
|
toast.dismiss('ifc-hover-info');
|
||||||
};
|
};
|
||||||
}, [camera, scene, gl, raycaster, mouse, setHoverInfo]);
|
}, [camera, scene, gl, raycaster, mouse, setHoverInfo]);
|
||||||
|
|
||||||
|
|||||||
+158
-3
@@ -7,6 +7,53 @@ const WASM_PATH = 'https://unpkg.com/web-ifc@0.0.57/';
|
|||||||
/**
|
/**
|
||||||
* Convert an IFC file (ArrayBuffer) into a GLB Blob using web-ifc + GLTFExporter.
|
* Convert an IFC file (ArrayBuffer) into a GLB Blob using web-ifc + GLTFExporter.
|
||||||
*/
|
*/
|
||||||
|
function getMaterialName(ifcApi: WebIFC.IfcAPI, modelID: number, matRef: any): string | null {
|
||||||
|
if (!matRef) return null;
|
||||||
|
const matId = matRef.value;
|
||||||
|
if (!matId) return null;
|
||||||
|
try {
|
||||||
|
const matLine = ifcApi.GetLine(modelID, matId);
|
||||||
|
if (!matLine) return null;
|
||||||
|
|
||||||
|
if (matLine.Name && matLine.Name.value) {
|
||||||
|
return matLine.Name.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matLine.Materials) {
|
||||||
|
for (const mRef of matLine.Materials) {
|
||||||
|
const name = getMaterialName(ifcApi, modelID, mRef);
|
||||||
|
if (name) return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matLine.MaterialConstituents) {
|
||||||
|
for (const mcRef of matLine.MaterialConstituents) {
|
||||||
|
const mcLine = ifcApi.GetLine(modelID, mcRef.value);
|
||||||
|
if (mcLine && mcLine.Material) {
|
||||||
|
const name = getMaterialName(ifcApi, modelID, mcLine.Material);
|
||||||
|
if (name) return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matLine.MaterialProfileSet) {
|
||||||
|
return getMaterialName(ifcApi, modelID, matLine.MaterialProfileSet);
|
||||||
|
}
|
||||||
|
if (matLine.MaterialProfiles) {
|
||||||
|
for (const mpRef of matLine.MaterialProfiles) {
|
||||||
|
const mpLine = ifcApi.GetLine(modelID, mpRef.value);
|
||||||
|
if (mpLine && mpLine.Material) {
|
||||||
|
const name = getMaterialName(ifcApi, modelID, mpLine.Material);
|
||||||
|
if (name) return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
// Silencia erros de atributos inexistentes
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
export async function parseIFCtoThree(buffer: ArrayBuffer): Promise<THREE.Scene> {
|
export async function parseIFCtoThree(buffer: ArrayBuffer): Promise<THREE.Scene> {
|
||||||
const ifcApi = new WebIFC.IfcAPI();
|
const ifcApi = new WebIFC.IfcAPI();
|
||||||
ifcApi.SetWasmPath(WASM_PATH, true);
|
ifcApi.SetWasmPath(WASM_PATH, true);
|
||||||
@@ -15,6 +62,87 @@ export async function parseIFCtoThree(buffer: ArrayBuffer): Promise<THREE.Scene>
|
|||||||
const data = new Uint8Array(buffer);
|
const data = new Uint8Array(buffer);
|
||||||
const modelID = ifcApi.OpenModel(data);
|
const modelID = ifcApi.OpenModel(data);
|
||||||
|
|
||||||
|
// Mapeamento de materiais
|
||||||
|
const elementMaterialMap = new Map<number, string>();
|
||||||
|
try {
|
||||||
|
const rels = ifcApi.GetLineIDsWithType(modelID, WebIFC.IFCRELASSOCIATESMATERIAL);
|
||||||
|
for (let i = 0; i < rels.size(); i++) {
|
||||||
|
const relId = rels.get(i);
|
||||||
|
const rel = ifcApi.GetLine(modelID, relId);
|
||||||
|
if (rel && rel.RelatedObjects && rel.RelatingMaterial) {
|
||||||
|
const matName = getMaterialName(ifcApi, modelID, rel.RelatingMaterial);
|
||||||
|
if (matName) {
|
||||||
|
for (const objRef of rel.RelatedObjects) {
|
||||||
|
elementMaterialMap.set(objRef.value, matName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('[IFC Parser] Falha ao mapear materiais:', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mapeamento de propriedades adicionais
|
||||||
|
const elementPropertiesMap = new Map<number, Record<string, string>>();
|
||||||
|
try {
|
||||||
|
const relsProp = ifcApi.GetLineIDsWithType(modelID, WebIFC.IFCRELDEFINESBYPROPERTIES);
|
||||||
|
for (let i = 0; i < relsProp.size(); i++) {
|
||||||
|
const relId = relsProp.get(i);
|
||||||
|
const rel = ifcApi.GetLine(modelID, relId);
|
||||||
|
if (rel && rel.RelatedObjects && rel.RelatingPropertyDefinition) {
|
||||||
|
const propDefId = rel.RelatingPropertyDefinition.value;
|
||||||
|
const propDef = ifcApi.GetLine(modelID, propDefId);
|
||||||
|
|
||||||
|
if (propDef) {
|
||||||
|
const props: Record<string, string> = {};
|
||||||
|
|
||||||
|
if (propDef.HasProperties) {
|
||||||
|
for (const pRef of propDef.HasProperties) {
|
||||||
|
const pLine = ifcApi.GetLine(modelID, pRef.value);
|
||||||
|
if (pLine && pLine.Name) {
|
||||||
|
const name = pLine.Name.value;
|
||||||
|
let value = '';
|
||||||
|
if (pLine.NominalValue) {
|
||||||
|
value = String(pLine.NominalValue.value);
|
||||||
|
}
|
||||||
|
if (name && value) {
|
||||||
|
props[name] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (propDef.Quantities) {
|
||||||
|
for (const qRef of propDef.Quantities) {
|
||||||
|
const qLine = ifcApi.GetLine(modelID, qRef.value);
|
||||||
|
if (qLine && qLine.Name) {
|
||||||
|
const name = qLine.Name.value;
|
||||||
|
let value = '';
|
||||||
|
if (qLine.LengthValue !== undefined) value = `${qLine.LengthValue.toFixed(1)} mm`;
|
||||||
|
else if (qLine.AreaValue !== undefined) value = `${qLine.AreaValue.toFixed(1)} m²`;
|
||||||
|
else if (qLine.VolumeValue !== undefined) value = `${qLine.VolumeValue.toFixed(2)} m³`;
|
||||||
|
else if (qLine.NominalValue) value = String(qLine.NominalValue.value);
|
||||||
|
|
||||||
|
if (name && value) {
|
||||||
|
props[name] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(props).length > 0) {
|
||||||
|
for (const objRef of rel.RelatedObjects) {
|
||||||
|
const existing = elementPropertiesMap.get(objRef.value) ?? {};
|
||||||
|
elementPropertiesMap.set(objRef.value, { ...existing, ...props });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('[IFC Parser] Falha ao mapear propriedades:', err);
|
||||||
|
}
|
||||||
|
|
||||||
const scene = new THREE.Scene();
|
const scene = new THREE.Scene();
|
||||||
const materials: Map<number, THREE.MeshStandardMaterial> = new Map();
|
const materials: Map<number, THREE.MeshStandardMaterial> = new Map();
|
||||||
|
|
||||||
@@ -22,11 +150,38 @@ export async function parseIFCtoThree(buffer: ArrayBuffer): Promise<THREE.Scene>
|
|||||||
const placedGeometries = mesh.geometries;
|
const placedGeometries = mesh.geometries;
|
||||||
const expressID = (mesh as unknown as { expressID?: number }).expressID ?? 0;
|
const expressID = (mesh as unknown as { expressID?: number }).expressID ?? 0;
|
||||||
|
|
||||||
// One Group per IfcProduct → represents a single "element" (beam, plate…).
|
let name = '';
|
||||||
// userData is preserved by GLTFExporter as `extras` and survives reload.
|
let tag = '';
|
||||||
|
let objectType = '';
|
||||||
|
let description = '';
|
||||||
|
try {
|
||||||
|
const elementLine = ifcApi.GetLine(modelID, expressID);
|
||||||
|
if (elementLine) {
|
||||||
|
name = elementLine.Name?.value ?? '';
|
||||||
|
tag = elementLine.Tag?.value ?? '';
|
||||||
|
objectType = elementLine.ObjectType?.value ?? '';
|
||||||
|
description = elementLine.Description?.value ?? '';
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
const materialName = elementMaterialMap.get(expressID) ?? '';
|
||||||
|
const extraProps = elementPropertiesMap.get(expressID) ?? {};
|
||||||
|
|
||||||
const elementGroup = new THREE.Group();
|
const elementGroup = new THREE.Group();
|
||||||
elementGroup.name = `ifc_${expressID}`;
|
elementGroup.name = `ifc_${expressID}`;
|
||||||
elementGroup.userData = { ifcElement: true, ifcId: expressID };
|
elementGroup.userData = {
|
||||||
|
ifcElement: true,
|
||||||
|
ifcId: expressID,
|
||||||
|
materialName,
|
||||||
|
properties: {
|
||||||
|
name,
|
||||||
|
tag,
|
||||||
|
objectType,
|
||||||
|
description,
|
||||||
|
material: materialName,
|
||||||
|
...extraProps
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
for (let i = 0; i < placedGeometries.size(); i++) {
|
for (let i = 0; i < placedGeometries.size(); i++) {
|
||||||
const placedGeometry = placedGeometries.get(i);
|
const placedGeometry = placedGeometries.get(i);
|
||||||
|
|||||||
+18
-1
@@ -24,7 +24,7 @@ const Index = () => {
|
|||||||
const [showLanding, setShowLanding] = useState(false);
|
const [showLanding, setShowLanding] = useState(false);
|
||||||
|
|
||||||
const [converting, setConverting] = useState(false);
|
const [converting, setConverting] = useState(false);
|
||||||
const { model, addModel, models, maxModels, xrSupported, setXrSupported } = useModelStore();
|
const { model, addModel, models, maxModels, xrSupported, setXrSupported, ifcMaterialColors, setIfcMaterialColors } = useModelStore();
|
||||||
|
|
||||||
// --- Painel de Logs Remotos em Tempo Real ---
|
// --- Painel de Logs Remotos em Tempo Real ---
|
||||||
const [debugLogs, setDebugLogs] = useState<{ level: 'info' | 'warn' | 'error'; message: string; timestamp: string; data?: unknown }[]>([]);
|
const [debugLogs, setDebugLogs] = useState<{ level: 'info' | 'warn' | 'error'; message: string; timestamp: string; data?: unknown }[]>([]);
|
||||||
@@ -265,6 +265,23 @@ const Index = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Toggle IFC Material Colors */}
|
||||||
|
<div className="flex items-center justify-between rounded-lg border border-border bg-card/50 p-3.5">
|
||||||
|
<div className="flex flex-col gap-0.5">
|
||||||
|
<Label htmlFor="ifc-mat-colors-toggle" className="text-xs font-semibold cursor-pointer text-foreground">
|
||||||
|
Cores por Material (IFC)
|
||||||
|
</Label>
|
||||||
|
<p className="text-[10px] text-muted-foreground">
|
||||||
|
{ifcMaterialColors ? 'Cores baseadas no material do IFC' : 'Cores padrão da cena'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
id="ifc-mat-colors-toggle"
|
||||||
|
checked={ifcMaterialColors}
|
||||||
|
onCheckedChange={setIfcMaterialColors}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Demo buttons */}
|
{/* Demo buttons */}
|
||||||
<div className="grid grid-cols-3 gap-2">
|
<div className="grid grid-cols-3 gap-2">
|
||||||
<CloudLoader />
|
<CloudLoader />
|
||||||
|
|||||||
@@ -94,6 +94,18 @@ function loadVisibility(fileName: string): { hidden: Set<string>; isolated: Set<
|
|||||||
return { hidden: new Set(v.hidden ?? []), isolated: v.isolated ? new Set(v.isolated) : null };
|
return { hidden: new Set(v.hidden ?? []), isolated: v.isolated ? new Set(v.isolated) : null };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const IFC_MAT_COLORS_KEY = 'tsxr_ifc_mat_colors_v1';
|
||||||
|
function loadIfcMaterialColors(): boolean {
|
||||||
|
try {
|
||||||
|
const raw = localStorage.getItem(IFC_MAT_COLORS_KEY);
|
||||||
|
return raw !== 'false';
|
||||||
|
} catch (e) { /* ignore */ }
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
function saveIfcMaterialColors(enabled: boolean) {
|
||||||
|
try { localStorage.setItem(IFC_MAT_COLORS_KEY, String(enabled)); } catch (e) { /* ignore */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export interface ScaleRatio {
|
export interface ScaleRatio {
|
||||||
@@ -354,6 +366,9 @@ interface ModelStore {
|
|||||||
xrFeatures: XRFeatureFlags;
|
xrFeatures: XRFeatureFlags;
|
||||||
setXRFeature: (key: keyof XRFeatureFlags, value: boolean) => void;
|
setXRFeature: (key: keyof XRFeatureFlags, value: boolean) => void;
|
||||||
resetXRFeatures: () => void;
|
resetXRFeatures: () => void;
|
||||||
|
|
||||||
|
ifcMaterialColors: boolean;
|
||||||
|
setIfcMaterialColors: (enabled: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sync top-level legacy `model` / `fineTuning` from active SceneModel. */
|
/** Sync top-level legacy `model` / `fineTuning` from active SceneModel. */
|
||||||
@@ -785,4 +800,9 @@ export const useModelStore = create<ModelStore>((set, get) => ({
|
|||||||
saveXRFeatures(next);
|
saveXRFeatures(next);
|
||||||
set({ xrFeatures: next });
|
set({ xrFeatures: next });
|
||||||
},
|
},
|
||||||
|
ifcMaterialColors: loadIfcMaterialColors(),
|
||||||
|
setIfcMaterialColors: (enabled: boolean) => {
|
||||||
|
saveIfcMaterialColors(enabled);
|
||||||
|
set({ ifcMaterialColors: enabled });
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
Reference in New Issue
Block a user