diff --git a/app/src/components/layout/sidebarRight/properties/SelectedWallProperties.tsx b/app/src/components/layout/sidebarRight/properties/SelectedWallProperties.tsx
index 517927c..794ede0 100644
--- a/app/src/components/layout/sidebarRight/properties/SelectedWallProperties.tsx
+++ b/app/src/components/layout/sidebarRight/properties/SelectedWallProperties.tsx
@@ -133,11 +133,17 @@ const SelectedWallProperties = () => {
diff --git a/app/src/components/layout/sidebarRight/properties/WallProperties.tsx b/app/src/components/layout/sidebarRight/properties/WallProperties.tsx
index ae17c1f..e158b44 100644
--- a/app/src/components/layout/sidebarRight/properties/WallProperties.tsx
+++ b/app/src/components/layout/sidebarRight/properties/WallProperties.tsx
@@ -63,11 +63,17 @@ const WallProperties = () => {
handleHeightChange(val)}
/>
handleThicknessChange(val)}
/>
diff --git a/app/src/modules/builder/Decal/decalInstance.tsx b/app/src/modules/builder/Decal/decalInstance.tsx
index dfdd622..1b68f3d 100644
--- a/app/src/modules/builder/Decal/decalInstance.tsx
+++ b/app/src/modules/builder/Decal/decalInstance.tsx
@@ -7,7 +7,7 @@ import { useBuilderStore } from '../../../store/builder/useBuilderStore';
import defaultMaterial from '../../../assets/textures/floor/wall-tex.png';
import useModuleStore from '../../../store/useModuleStore';
-function DecalInstance({ visible = true, decal }: { visible?: boolean, decal: Decal }) {
+function DecalInstance({ visible = true, decal, zPosition = decal.decalPosition[2] }: { visible?: boolean, decal: Decal, zPosition?: number }) {
const { setSelectedWall, setSelectedFloor, selectedDecal, setSelectedDecal } = useBuilderStore();
const { togglView } = useToggleView();
const { activeModule } = useModuleStore();
@@ -17,7 +17,7 @@ function DecalInstance({ visible = true, decal }: { visible?: boolean, decal: De
{
const canvasElement = gl.domElement;
- const onDrop = (event: any) => {
+ const onDrop = (event: DragEvent) => {
if (!event.dataTransfer?.files[0]) return;
if (selectedItem.id !== "" && event.dataTransfer?.files[0] && selectedItem.category !== 'Fenestration') {
diff --git a/app/src/modules/builder/builder.tsx b/app/src/modules/builder/builder.tsx
index adea2c6..ab9434b 100644
--- a/app/src/modules/builder/builder.tsx
+++ b/app/src/modules/builder/builder.tsx
@@ -2,8 +2,8 @@
import * as THREE from "three";
import { useEffect, useRef } from "react";
-import { useThree } from "@react-three/fiber";
-import { Bvh } from "@react-three/drei";
+import { useFrame, useThree } from "@react-three/fiber";
+import { Geometry } from "@react-three/csg";
////////// Zustand State Imports //////////
@@ -39,16 +39,14 @@ import ZoneGroup from "./zone/zoneGroup";
import { useParams } from "react-router-dom";
import { useBuilderStore } from "../../store/builder/useBuilderStore";
import { getUserData } from "../../functions/getUserData";
+import WallAssetGroup from "./wallAsset/wallAssetGroup";
export default function Builder() {
- const state = useThree(); // Importing the state from the useThree hook, which contains the scene, camera, and other Three.js elements.
+ const state = useThree();
+ const plane = useRef(null);
+ const csgRef = useRef(null);
- // Assigning the scene and camera from the Three.js state to the references.
-
- const plane = useRef(null); // Reference for a plane object for raycaster reference.
- const grid = useRef() as any; // Reference for a grid object for raycaster reference.
-
- const { toggleView } = useToggleView(); // State for toggling between 2D and 3D.
+ const { toggleView } = useToggleView();
const { setToolMode } = useToolMode();
const { setRoofVisibility } = useRoofVisibility();
const { setWallVisibility } = useWallVisibility();
@@ -59,14 +57,6 @@ export default function Builder() {
const { setHoveredPoint, setHoveredLine } = useBuilderStore();
const { userId, organization } = getUserData();
- // const loader = new GLTFLoader();
- // const dracoLoader = new DRACOLoader();
-
- // dracoLoader.setDecoderPath('https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/');
- // loader.setDRACOLoader(dracoLoader);
-
- ////////// All Toggle's //////////
-
useEffect(() => {
if (!toggleView) {
setHoveredLine(null);
@@ -91,29 +81,31 @@ export default function Builder() {
fetchVisibility();
}, []);
- ////////// Return //////////
+ useFrame(() => {
+ if (csgRef.current) {
+ csgRef.current.update();
+ }
+ })
return (
<>
-
+
- {/* */}
-
-
+
+
-
+
+
+
+
+
+
+
+
diff --git a/app/src/modules/builder/wall/Instances/instance/wall.tsx b/app/src/modules/builder/wall/Instances/instance/wall.tsx
index 0498b5b..280167d 100644
--- a/app/src/modules/builder/wall/Instances/instance/wall.tsx
+++ b/app/src/modules/builder/wall/Instances/instance/wall.tsx
@@ -154,7 +154,7 @@ function Wall({ wall }: { readonly wall: Wall }) {
{wall.decals.map((decal) => (
-
+
))}
diff --git a/app/src/modules/builder/wall/Instances/wallInstances.tsx b/app/src/modules/builder/wall/Instances/wallInstances.tsx
index 98d00fc..c5e22bf 100644
--- a/app/src/modules/builder/wall/Instances/wallInstances.tsx
+++ b/app/src/modules/builder/wall/Instances/wallInstances.tsx
@@ -1,6 +1,5 @@
import React, { useEffect, useMemo } from 'react';
import { DoubleSide, RepeatWrapping, Shape, SRGBColorSpace, TextureLoader, Vector2, Vector3 } from 'three';
-import { Geometry } from '@react-three/csg';
import { Html, Extrude } from '@react-three/drei';
import { useLoader } from '@react-three/fiber';
import { useSceneContext } from '../../../scene/sceneContext';
@@ -44,13 +43,9 @@ function WallInstances() {
<>
{!toggleView && walls.length > 1 && (
<>
-
-
- {walls.map((wall) => (
-
- ))}
-
-
+ {walls.map((wall) => (
+
+ ))}
{rooms.map((room, index) => (
diff --git a/app/src/modules/builder/wallAsset/Instances/Instances/wallAssetInstance.tsx b/app/src/modules/builder/wallAsset/Instances/Instances/wallAssetInstance.tsx
new file mode 100644
index 0000000..6261be6
--- /dev/null
+++ b/app/src/modules/builder/wallAsset/Instances/Instances/wallAssetInstance.tsx
@@ -0,0 +1,135 @@
+import * as THREE from 'three';
+import { useEffect, useMemo, useRef, useState } from 'react';
+import { retrieveGLTF, storeGLTF } from '../../../../../utils/indexDB/idbUtils';
+import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
+import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
+import { Base, Geometry, Subtraction } from '@react-three/csg';
+import { useFrame } from '@react-three/fiber';
+import { useSceneContext } from '../../../../scene/sceneContext';
+
+function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) {
+ const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
+ const { wallStore } = useSceneContext();
+ const { walls, getWallById } = wallStore();
+ const [gltfScene, setGltfScene] = useState(null);
+ const [boundingBox, setBoundingBox] = useState(null);
+ const groupRef = useRef(null);
+ const csgRef = useRef(null);
+ const wall = useMemo(() => getWallById(wallAsset.wallUuid), [getWallById, wallAsset.wallUuid, walls]);
+
+ useEffect(() => {
+ const loader = new GLTFLoader();
+ const dracoLoader = new DRACOLoader();
+
+ dracoLoader.setDecoderPath('https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/');
+ loader.setDRACOLoader(dracoLoader);
+ const loadModel = async () => {
+ try {
+ // Check Cache
+ const assetId = wallAsset.assetId;
+ const cachedModel = THREE.Cache.get(assetId);
+ if (cachedModel) {
+ setGltfScene(cachedModel.scene.clone());
+ calculateBoundingBox(cachedModel.scene);
+ return;
+ }
+
+ // Check IndexedDB
+ const indexedDBModel = await retrieveGLTF(assetId);
+ if (indexedDBModel) {
+ const blobUrl = URL.createObjectURL(indexedDBModel);
+ loader.load(blobUrl, (gltf) => {
+ URL.revokeObjectURL(blobUrl);
+ THREE.Cache.remove(blobUrl);
+ THREE.Cache.add(assetId, gltf);
+ setGltfScene(gltf.scene.clone());
+ calculateBoundingBox(gltf.scene);
+ },
+ undefined,
+ (error) => {
+ echo.error(`[IndexedDB] Error loading ${wallAsset.modelName}:`);
+ URL.revokeObjectURL(blobUrl);
+ }
+ );
+ return;
+ }
+
+ // Fetch from Backend
+ const modelUrl = `${url_Backend_dwinzo}/api/v2/AssetFile/${assetId}`;
+ const handleBackendLoad = async (gltf: GLTF) => {
+ try {
+ const response = await fetch(modelUrl);
+ const modelBlob = await response.blob();
+ await storeGLTF(assetId, modelBlob);
+ THREE.Cache.add(assetId, gltf);
+ setGltfScene(gltf.scene.clone());
+ calculateBoundingBox(gltf.scene);
+ } catch (error) {
+ console.error(`[Backend] Error storing/loading ${wallAsset.modelName}:`, error);
+ }
+ };
+ loader.load(
+ modelUrl,
+ handleBackendLoad,
+ undefined,
+ (error) => {
+ echo.error(`[Backend] Error loading ${wallAsset.modelName}:`);
+ }
+ );
+ } catch (err) {
+ console.error("Failed to load model:", wallAsset.assetId, err);
+ }
+ };
+
+ const calculateBoundingBox = (scene: THREE.Object3D) => {
+ const box = new THREE.Box3().setFromObject(scene);
+ setBoundingBox(box);
+ };
+
+ loadModel();
+
+ }, []);
+
+ useFrame(() => {
+ if (csgRef.current) {
+ csgRef.current.update();
+ }
+ })
+
+ if (!gltfScene || !boundingBox || !wall) { return null }
+ const size = new THREE.Vector3();
+ boundingBox.getSize(size);
+ const center = new THREE.Vector3();
+ boundingBox.getCenter(center);
+
+ return (
+ <>
+
+
+
+
+
+
+ {gltfScene && (
+ {
+ console.log(wallAsset);
+ }}
+ >
+
+
+ )}
+
+ >
+ )
+}
+
+export default WallAssetInstance
\ No newline at end of file
diff --git a/app/src/modules/builder/wallAsset/Instances/wallAssetInstances.tsx b/app/src/modules/builder/wallAsset/Instances/wallAssetInstances.tsx
new file mode 100644
index 0000000..581dfe7
--- /dev/null
+++ b/app/src/modules/builder/wallAsset/Instances/wallAssetInstances.tsx
@@ -0,0 +1,30 @@
+import { useEffect } from 'react';
+import { useSceneContext } from '../../../scene/sceneContext'
+import { useToggleView } from '../../../../store/builder/store';
+import WallAssetInstance from './Instances/wallAssetInstance';
+
+function WallAssetInstances() {
+ const { wallAssetStore } = useSceneContext();
+ const { wallAssets } = wallAssetStore();
+ const { toggleView } = useToggleView();
+
+ useEffect(() => {
+ // console.log('wallAssets: ', wallAssets);
+ }, [wallAssets])
+
+ return (
+ <>
+
+ {!toggleView && wallAssets.length > 0 && (
+ <>
+ {wallAssets.map((wallAsset) => (
+
+ ))}
+ >
+ )}
+
+ >
+ )
+}
+
+export default WallAssetInstances
\ No newline at end of file
diff --git a/app/src/modules/builder/wallAsset/wallAssetCreator.tsx b/app/src/modules/builder/wallAsset/wallAssetCreator.tsx
new file mode 100644
index 0000000..a64d957
--- /dev/null
+++ b/app/src/modules/builder/wallAsset/wallAssetCreator.tsx
@@ -0,0 +1,85 @@
+import { useThree } from '@react-three/fiber';
+import { useEffect } from 'react'
+import { useSelectedItem, useSocketStore, useToggleView } from '../../../store/builder/store';
+import useModuleStore from '../../../store/useModuleStore';
+import { MathUtils, Vector3 } from 'three';
+import { useSceneContext } from '../../scene/sceneContext';
+
+function WallAssetCreator() {
+ const { socket } = useSocketStore();
+ const { pointer, camera, raycaster, scene, gl } = useThree();
+ const { togglView } = useToggleView();
+ const { activeModule } = useModuleStore();
+ const { wallAssetStore } = useSceneContext();
+ const { addWallAsset } = wallAssetStore();
+ const { selectedItem, setSelectedItem } = useSelectedItem();
+
+ function closestPointOnLineSegment(p: Vector3, a: Vector3, b: Vector3) {
+ const ab = new Vector3().subVectors(b, a);
+ const ap = new Vector3().subVectors(p, a);
+
+ const abLengthSq = ab.lengthSq();
+ const dot = ap.dot(ab);
+ const t = Math.max(0, Math.min(1, dot / abLengthSq));
+
+ return new Vector3().copy(a).add(ab.multiplyScalar(t));
+ }
+
+ useEffect(() => {
+ const canvasElement = gl.domElement;
+
+ const onDrop = (event: DragEvent) => {
+ if (!event.dataTransfer?.files[0]) return;
+ if (selectedItem.id !== "" && event.dataTransfer?.files[0] && selectedItem.category === 'Fenestration') {
+ pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
+ pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;
+
+ raycaster.setFromCamera(pointer, camera);
+ const intersects = raycaster.intersectObjects(scene.children, true);
+ const intersect = intersects.find((intersect) => intersect.object.name.includes('WallReference'));
+
+ if (intersect) {
+ const wall = intersect.object.userData as Wall;
+ const closestPoint = closestPointOnLineSegment(
+ new Vector3(intersect.point.x, 0, intersect.point.z),
+ new Vector3(...wall.points[0].position),
+ new Vector3(...wall.points[1].position)
+ )
+
+ const wallRotation = intersect.object.rotation.clone();
+
+ const newWallAsset: WallAsset = {
+ modelName: selectedItem.name,
+ modelUuid: MathUtils.generateUUID(),
+ wallUuid: wall.wallUuid,
+ wallAssetType: selectedItem.subCategory,
+ assetId: selectedItem.id,
+ position: [closestPoint.x, selectedItem.subCategory === "fixed-move" ? 0 : intersect.point.y, closestPoint.z],
+ rotation: [wallRotation.x, wallRotation.y, wallRotation.z],
+ isLocked: false,
+ isVisible: true,
+ opacity: 1,
+ };
+
+ addWallAsset(newWallAsset);
+ }
+ }
+ };
+
+ if (!togglView && activeModule === 'builder') {
+ canvasElement.addEventListener('drop', onDrop);
+ }
+
+ return () => {
+ canvasElement.removeEventListener('drop', onDrop);
+ };
+
+ }, [gl, camera, togglView, activeModule, socket, selectedItem, setSelectedItem]);
+
+ return (
+ <>
+ >
+ )
+}
+
+export default WallAssetCreator
\ No newline at end of file
diff --git a/app/src/modules/builder/wallAsset/wallAssetGroup.tsx b/app/src/modules/builder/wallAsset/wallAssetGroup.tsx
new file mode 100644
index 0000000..d9ec309
--- /dev/null
+++ b/app/src/modules/builder/wallAsset/wallAssetGroup.tsx
@@ -0,0 +1,17 @@
+import WallAssetCreator from './wallAssetCreator'
+import WallAssetInstances from './Instances/wallAssetInstances'
+
+function WallAssetGroup() {
+
+ return (
+ <>
+
+
+
+
+
+ >
+ )
+}
+
+export default WallAssetGroup
\ No newline at end of file
diff --git a/app/src/modules/builder/zone/zoneGroup.tsx b/app/src/modules/builder/zone/zoneGroup.tsx
index fc9dc7f..46bddb7 100644
--- a/app/src/modules/builder/zone/zoneGroup.tsx
+++ b/app/src/modules/builder/zone/zoneGroup.tsx
@@ -29,7 +29,6 @@ function ZoneGroup() {
useEffect(() => {
if (projectId && selectedVersion) {
getZonesApi(projectId, selectedVersion?.versionId || '').then((zones) => {
- console.log('zones: ', zones);
if (zones && zones.length > 0) {
setZones(zones);
} else {
diff --git a/app/src/modules/scene/environment/ground.tsx b/app/src/modules/scene/environment/ground.tsx
index ef51791..f6baeef 100644
--- a/app/src/modules/scene/environment/ground.tsx
+++ b/app/src/modules/scene/environment/ground.tsx
@@ -1,14 +1,13 @@
import { useTileDistance, useToggleView } from "../../../store/builder/store";
import * as CONSTANTS from "../../../types/world/worldConstants";
-const Ground = ({ grid, plane }: any) => {
+const Ground = ({ plane }: any) => {
const { toggleView } = useToggleView();
const { planeValue, gridValue } = useTileDistance();
return (
diff --git a/app/src/services/factoryBuilder/zone/upsertZoneApi.ts b/app/src/services/factoryBuilder/zone/upsertZoneApi.ts
index ffa79c5..e19eae8 100644
--- a/app/src/services/factoryBuilder/zone/upsertZoneApi.ts
+++ b/app/src/services/factoryBuilder/zone/upsertZoneApi.ts
@@ -6,7 +6,7 @@ export const upsertZoneApi = async (
zoneData: Zone
) => {
try {
- const response = await fetch(`${url_Backend_dwinzo}/api/V1/UpsertZone`, {
+ const response = await fetch(`${url_Backend_dwinzo}/api/V1/upsertZone`, {
method: "POST",
headers: {
Authorization: "Bearer ",
diff --git a/app/src/types/builderTypes.d.ts b/app/src/types/builderTypes.d.ts
index d346547..de7586a 100644
--- a/app/src/types/builderTypes.d.ts
+++ b/app/src/types/builderTypes.d.ts
@@ -51,9 +51,11 @@ type Assets = Asset[];
interface WallAsset {
modelUuid: string;
modelName: string;
+ wallAssetType: string;
assetId: string;
wallUuid: string;
position: [number, number, number];
+ rotation: [number, number, number];
isLocked: boolean;
isVisible: boolean;
opacity: number;