Closed Amar-Gill closed 2 years ago
I'having the same issue, which was not present in previous versions.
Hi! What version of IFC.js are you using? Are you accessing / changing ifcManager.state.models
somewhere else? Are you using the version of Three.js specified by the peer dependency of IFC.js? 🤔
In my case, I have these dependencies:
"node_modules/web-ifc-three": {
"version": "0.0.102",
"resolved": "https://registry.npmjs.org/web-ifc-three/-/web-ifc-three-0.0.102.tgz",
"integrity": "sha512-Mxx4tN89Lb4dZd/....",
"dependencies": {
"three-mesh-bvh": "^0.5.2",
"web-ifc": "^0.0.32"
},
"peerDependencies": {
"three": "^0.135.0"
}
},
"node_modules/web-ifc-viewer": {
"version": "1.0.127",
"resolved": "https://registry.npmjs.org/web-ifc-viewer/-/web-ifc-viewer-1.0.127.tgz",
"integrity": "sha512-0K4ivXDoGXiP3Q8/...",
"dependencies": {
"camera-controls": "^1.33.1",
"dat.gui": "^0.7.7",
"gsap": "^3.7.1",
"postprocessing": "^6.23.1",
"three-mesh-bvh": "^0.5.2",
"web-ifc": "^0.0.32",
"web-ifc-three": "^0.0.102"
},
"peerDependencies": {
"three": "^0.135.0"
}
},
And in package.json
"three": "^0.135.0",
"three-mesh-bvh": "^0.5.2",
"web-ifc": "^0.0.32",
"web-ifc-three": "0.0.102",
"web-ifc-viewer": "^1.0.127",
And I think I'm not using ifcManager.state.models anywhere else. And ifc is IFC2x3
package.json
"dependencies": {
"@react-three/drei": "^8.6.3",
"@react-three/fiber": "^7.0.25",
"@types/three": "^0.136.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"three": "^0.136.0",
"web-ifc-three": "0.0.102"
},
In node_modules, web-ifc-three
shows
"peerDependencies": {
"three": "^0.135.0"
}
I'll try downgrading to 0.135
I'm also not changing ifcManager.state.models
anywhere else
@agviegas still same error after downgrading to three@0.135.0
which matches peer dependancy of web-ifc-three
version I installed.
Do you think it's because I'm using useBVH
hook from @react-three/drei
and not three-mesh-bvh
used in examples?
import { useBVH } from '@react-three/drei';
Edit: tried without the hook, doesn't look like it's the issue.
Hum. Two questions:
If you have an example I can debug (either a small repo or a live example) I will happily look into it.
Correct I'm using IFCLoader from web-ifc-three. So I was talking with r3f team, and was able to use the useLoader
hook from r3f package to load the load the ifc model. It seems to fix my first issue, where I need to manually set manager.state.models[modelID]
. However, the removeSubset
functionality is still not working. I will close this issue and make a separate issue for that.
I've updated my git repo to show this working. I'll share the code here as well.
// App.tsx
import { OrbitControls } from '@react-three/drei';
import { Canvas } from '@react-three/fiber';
import React, { Suspense } from 'react';
import IFCContainer from './IFCContainer';
function App() {
return (
<>
<Canvas
camera={{
far: 500,
near: 1,
zoom: 1,
position: [1, 8, 20],
rotation: [-0.4, 0.55, 0.2],
}}>
<ambientLight intensity={0.1} color="white" />
<directionalLight color="white" position={[40, 40, 100]} />
<OrbitControls enablePan={true} enableZoom={true} enableRotate={true} />
<Suspense fallback={null}>
<IFCContainer />
</Suspense>
<gridHelper args={[100, 100]} />
<axesHelper args={[25]} />
</Canvas>
</>
);
}
export default App;
// IFCContainer.tsx
import { useBVH } from '@react-three/drei';
import { useLoader, useThree } from '@react-three/fiber';
import React, { useRef, useState } from 'react';
import { Intersection, Material, MeshLambertMaterial } from 'three';
import { IFCManager } from 'web-ifc-three/IFC/components/IFCManager';
import { IFCModel } from 'web-ifc-three/IFC/components/IFCModel';
import { IFCLoader } from 'web-ifc-three/IFCLoader';
let manager: IFCManager;
export default function IFCContainer() {
const ifc = useLoader(IFCLoader, 'test-file.ifc', (loader: any) => {
manager = loader.ifcManager;
manager.setWasmPath('resources/');
});
const [highlightedModel, setHighlightedModel] = useState({ id: -1 });
const mesh = useRef(null!);
useBVH(mesh);
const scene = useThree((state) => state.scene);
const highlightMaterial = new MeshLambertMaterial({
transparent: true,
opacity: 0.6,
color: 0xff00ff,
depthTest: false,
});
function handleDblClick(event: Intersection<IFCModel<Event>>) {
manager.removeSubset(highlightedModel.id, highlightMaterial);
highlight(event, highlightMaterial);
}
function highlight(intersection: Intersection<IFCModel<Event>>, material: Material) {
const { faceIndex } = intersection;
const { modelID, geometry } = intersection.object;
const id = manager.getExpressId(geometry, faceIndex);
setHighlightedModel({ id: modelID });
manager.createSubset({
modelID,
ids: [id],
material,
scene,
removePrevious: true,
});
}
return <primitive ref={mesh} object={ifc} onDoubleClick={handleDblClick} />;
}
Thanks a lot for sharing it! 💛
I couldn't solve the problem. I'm basically following this example: https://ifcjs.github.io/info/docs/Guide/web-ifc-three/Tutorials/Highlighting/#how-to-do-it, inside a React.useEffect hook.
It's curious that I get the error the second time I go the page, but the first time it works fine; and, also, I didn't have this problem with version 0.0.67.
I'm also having this warning when I leave the page: Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
Maybe, it has to do?
Hi @iserranoe! Just FYI, right now IFC.js is not supposed to be destroyed / reinitialized multiple times during the execution of the application. This might change in the near future, but right now this is how it is. Are you destroying / reinitializing it?
Hi @agviegas! I don't think I am destroying or reinitializing it
Are you sure @iserranoe? I mention this because I am aware that libraries/frameworks like react or angular do this automatically. For instance, if you are initializing IFC.js in a component, it is destroyed whenever that component is removed from the DOM (e.g. the user navigates away from the viewer).
The plan is to support this destruction / reinitialization very soon (hopefully before the end of next week). Until then, you can try locating IFC.js somewhere that doesn't get automatically destroyed. If that's not your case, let us know and we'll investigate further!
Well, I'm destroying the scene in the useEffect cleanup function, but if I remove it I get the same error
return () => {
try {
setRenderer()
mount_current.removeChild(renderer.domElement);
scene.clear();
} catch (error) {
console.error(error);
}
};
This is the complete function:
const [renderersnap, setRenderer] = React.useState()
const mount = React.useRef(null);
const [properties, setProp] = React.useState([])
const [message, setMessage] = React.useState(false)
React.useEffect(() => {
// let height = mount.current.clientHeight;
// Se crean los nuevos constructores
const scene = new Scene();
const width = mount.current.clientWidth;
const ratio = 16/9;
const size = {
width: width,
height: width/ratio
}
// Se crea la cámara, punto de vista del usuario
const camera = new PerspectiveCamera(75, ratio);
const lightColor = 0xffffff;
const ambientLight = new AmbientLight(lightColor, 0.5);
scene.add(ambientLight);
const directionalLight = new DirectionalLight(lightColor, 1);
directionalLight.position.set(0, 10, 0);
directionalLight.target.position.set(-5, 0, 0);
scene.add(directionalLight);
scene.add(directionalLight.target);
scene.background = new Color('white');
// Se crea el renderer
const renderer = new WebGLRenderer({
antialias: true,
alpha: true,
preserveDrawingBuffer: true
});
renderer.setSize(size.width, size.height);
//renderer.setClearColor('#000000');
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
const threeCanvas = renderer.domElement
// Se crea el grid y los ejes de la escena:
//const grid = new GridHelper(50, 30);
//scene.add(grid);
const axes = new AxesHelper();
axes.material.depthTest = false;
axes.renderOrder = 1;
scene.add(axes);
// Se crean los controladores de las órbitas, para navegar por la escena
const controls = new OrbitControls(camera, threeCanvas); //renderer.domElement coge la ref={mount}, en los tutoriales, se pone en lugar the three canvas
controls.enableDamping = true;
controls.target.set(-2, 0, 0);
//controls.enableZoom = true;
// Cargar el archivo
let loader;
loader = new IFCLoader();
//Este método indica dónde se sitúa el archivo wasm, que he copiado y pegado de node_modules/web-ifc
//Este es el path de public
loader.ifcManager.setWasmPath('../../');
//Este método permite seleccionar objetos más rápidamente, especialmente aquellos con geometrÃas grandes.
loader.ifcManager.setupThreeMeshBVH (
computeBoundsTree,
disposeBoundsTree,
acceleratedRaycast)
// Creamos un array para almacenar las referencias de a los modelos IFC en la escena para seleccionarlos
let ifcModels = []
//Método para obtener todas las propiedades del archivo
//* **** Propiedades del objeto *****/
// Se carga el objeto desde una url y se modifican las propiedades de la cámara según el objeto
let center = {}
const url = installation.model3d.storage.url_ifc
loader.load(
// '/media/modelos/RadioTower.fbx',
url,
(object) => {
scene.add(object);
ifcModels.push(object)
// Comento esto porque si muevo el objeto los elementos destacados aparecen en la posición original.
// Tengo que solucionarlo
// Se crea un cubo alrededor del objeto
const box = new Box3().setFromObject(object);
// Se calcula el centro del cubo
center = box.getCenter(new Vector3());
// Se calcula el lado del cubo (creo que es lado)
const size = box.getSize(new Vector3()).length();
// ***** Propiedades de la cámara *****
// Movemos la cámara algo más lejos del objeto. Cubo dentro de esfera.
const r = size//Math.sqrt(3)*size/2;
const theta = 0;
// Con phi=0, la cámara se queda en el plano x-z
const phi = 0;
// Muevo la cámara en todo caso para que se vea bien el objeto
const camara_x = r * Math.cos(phi) * Math.sin(theta)+center.x;
const camara_y = r * Math.sin(phi) * Math.sin(theta)+center.y;
const camara_z = r * Math.cos(theta)+center.z;
camera.position.x = camara_x;
camera.position.y = camara_y;
camera.position.z = camara_z;
scene.add(object);
}
);
// scene.add(cube);
// Raycaster, sólo coge información del primer objeto que se encuentra
// Funciones para extraer el id del objecto que se selecciona
const raycaster = new Raycaster()
raycaster.firstHitOnly = true;
const mouse = new Vector2()
// Esta función lanza rayos para calcular la posición actual del ratón en la pantalla
// Especifica con qué objetos choca el rayo, que sólo puede chocar con los modelos IFC cargados. Si hay más objetos en la escena, los ignorará.
const cast = (event) =>{
// Computes the position of the mouse on the screen
const bounds = threeCanvas.getBoundingClientRect();
const x1 = event.clientX - bounds.left;
const x2 = bounds.right - bounds.left;
mouse.x = (x1 / x2) * 2 - 1;
const y1 = event.clientY - bounds.top;
const y2 = bounds.bottom - bounds.top;
mouse.y = -(y1 / y2) * 2 + 1;
// Places it on the camera pointing to the mouse
raycaster.setFromCamera(mouse, camera);
// Casts a ray
return raycaster.intersectObjects(ifcModels);
}
//https://ifcjs.github.io/info/docs/Guide/web-ifc-three/Tutorials/Highlighting/#how-to-do-it
// Crear material para seleccionar el elemento
const preselectMat = new MeshLambertMaterial({
transparent: true,
opacity: 0.6,
color: '#0063c1',
depthTest: false
})
// Reference to the previous selection:
let preselectModel = { id: - 1};
const ifc = loader.ifcManager;
//const output = document.getElementById("id-output");
const highlight = async (event, material, model) => {
//console.log(ifc)
const found = cast(event)[0];
if (found){
// Gets model ID
model.id = found.object.modelID;
const index = found.faceIndex;
const geometry = found.object.geometry;
const id = ifc.getExpressId(geometry,index);
// Pongo esto a ver si se soluciona un error, pero no me va: https://github.com/IFCjs/web-ifc-three/issues/83
//ifc.state.models[model.id] = found.object
// Creates subset
ifc.createSubset({
modelID: model.id,
ids: [id],
material: material,
scene: scene,
removePrevious: true,
})
const properties_new = await ifc.getItemProperties(model.id, id, true);
setProp(properties_new)
setMessage(true)
}
else {
// Removes previous highlight. Para que funcione hay que quitar scene
ifc.removeSubset(model.id, material);
setMessage(false)
}
}
threeCanvas.onclick = (event) => highlight(
event,
preselectMat,
preselectModel);
// Monta el renderizador, no entiendo
const mount_current = mount.current
mount_current.appendChild(renderer.domElement);
//* **** Función para la animación, necesaria para mover el objeto */
const animate = () => {
window.requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
setRenderer(renderer) //Aquà no está cogiendo bien el renderer
};
requestAnimationFrame(animate);
return () => {
// He puesto este try para los casos en los que no hay modelo.
// Aunque haya un condicional para comprobar que el modelo no sea nulo,
// cuando se va a la instalación sin modelo sin refrescar,
// daba error porque hace el contenido de useEffect una vez antes de cargar la página.
try {
setRenderer()
mount_current.removeChild(renderer.domElement);
scene.clear();
} catch (error) {
console.error(error);
}
};
},[mount,installation.model3d]);
I finally made it work using @Amar-Gill sugestion and making model.id=0: When I set 'ifc.state.models[model.id] = found.object' I had problems with 'ifc.getItemProperties'. As before, this was happening when I changed pages. I found that model.id was 0 in the first case and an int number in the second case, so I just set model.id to 0, but I really don't know what is going on, just that it works so far!
const highlight = async (event, material, model) => {
const found = cast(event)[0];
if (found){
// Gets model ID
model.id = found.object.modelID;
const index = found.faceIndex;
const geometry = found.object.geometry;
let id = await ifc.getExpressId(geometry,index);
ifc.state.models[model.id] = found.object
// Creates subset
ifc.createSubset({
modelID: model.id,
ids: [id],
material: material,
scene: scene,
removePrevious: true,
})
model.id=0
const properties_new = await ifc.getItemProperties(model.id, id, true);
setProp(properties_new)
setMessage(true)
}
else {
// Removes previous highlight. Para que funcione hay que quitar scene
ifc.removeSubset(model.id, material);
setMessage(false)
}
}
This new tutorial might be relevant. 🙂
Description
In my react-three-fiber project, I have to manually set
ifcManager.state.models[modelId]
before making a call toifcManager.createSubset()
. I thought thecreateSubset
method would handle this on it's own:Without the call to
manager.state.models[modelID] = intersection.object;
I get the following error:Additionally, when I call
removeSubset(modelID, highlightMaterial)
visually it appears the highlight remains on the model:Steps to Reproduce
I have a repo where I reproduce this issue here.
Double click any mesh, it will be highlighted. Double clicking a new mesh will highlight it also, but the highlight on the previously clicked mesh will remain:
Next, comment out the line for
manager.state.models[modelID] = intersection.object;
and double click a mesh. The mesh will not highlight and error will appear on console: