From 218f68b13a2c119f07cbd6de2054b83c765b88cc Mon Sep 17 00:00:00 2001 From: Poovizhi99 Date: Mon, 26 May 2025 10:33:27 +0530 Subject: [PATCH 1/4] dxf loader --- app/package-lock.json | 69 +++++++-- app/package.json | 3 +- .../components/temporary/SelectFloorPlan.tsx | 82 +++++++++- app/src/modules/builder/builder.tsx | 5 +- app/src/modules/builder/dfx/LoadBlueprint.tsx | 38 +++++ .../dfx/functions/convertDxfToThree.ts | 65 ++++++++ .../functions/getWallPointsFromBlueprint.ts | 141 ++++++++++++++++++ app/src/modules/builder/findZoneName.tsx | 33 ---- .../modules/builder/groups/floorPlanGroup.tsx | 1 + app/src/store/builder/store.ts | 103 ++++++------- app/src/store/builder/uselayoutStore.ts | 2 +- app/src/types/builderTypes.d.ts | 78 ++++++---- 12 files changed, 489 insertions(+), 131 deletions(-) create mode 100644 app/src/modules/builder/dfx/LoadBlueprint.tsx create mode 100644 app/src/modules/builder/dfx/functions/convertDxfToThree.ts create mode 100644 app/src/modules/builder/dfx/functions/getWallPointsFromBlueprint.ts delete mode 100644 app/src/modules/builder/findZoneName.tsx diff --git a/app/package-lock.json b/app/package-lock.json index 3d5aea4..239990c 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -27,9 +27,11 @@ "@use-gesture/react": "^10.3.1", "chart.js": "^4.4.8", "chartjs-plugin-annotation": "^3.1.0", + "dxf-parser": "^1.1.2", "glob": "^11.0.0", "gsap": "^3.12.5", "html2canvas": "^1.4.1", + "immer": "^9.0.21", "leva": "^0.10.0", "mqtt": "^5.10.4", "postprocessing": "^6.36.4", @@ -2021,7 +2023,7 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, + "devOptional": true, "dependencies": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -2033,7 +2035,7 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, + "devOptional": true, "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -4136,6 +4138,25 @@ "url": "https://github.com/sponsors/gregberge" } }, + "node_modules/@testing-library/dom": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", + "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@testing-library/jest-dom": { "version": "5.17.0", "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz", @@ -4247,25 +4268,25 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true + "devOptional": true }, "node_modules/@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true + "devOptional": true }, "node_modules/@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true + "devOptional": true }, "node_modules/@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true + "devOptional": true }, "node_modules/@turf/along": { "version": "7.2.0", @@ -9019,7 +9040,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true + "devOptional": true }, "node_modules/cross-env": { "version": "7.0.3", @@ -9896,7 +9917,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.3.1" } @@ -10067,6 +10088,14 @@ "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" }, + "node_modules/dxf-parser": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/dxf-parser/-/dxf-parser-1.1.2.tgz", + "integrity": "sha512-GPTumUvRkounlIazLIyJMmTWt+nlg+ksS0Hdm8jWvejmZKBTz6gvHTam76wRm4PQMma5sgKLThblQyeIJcH79Q==", + "dependencies": { + "loglevel": "^1.7.1" + } + }, "node_modules/earcut": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", @@ -15181,6 +15210,18 @@ "node": ">=8" } }, + "node_modules/loglevel": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz", + "integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==", + "engines": { + "node": ">= 0.6.0" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/loglevel" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -15259,7 +15300,7 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true + "devOptional": true }, "node_modules/makeerror": { "version": "1.0.12", @@ -20727,7 +20768,7 @@ "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, + "devOptional": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -20770,7 +20811,7 @@ "version": "8.3.4", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, + "devOptional": true, "dependencies": { "acorn": "^8.11.0" }, @@ -20782,7 +20823,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true + "devOptional": true }, "node_modules/tsconfig-paths": { "version": "3.15.0", @@ -21278,7 +21319,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true + "devOptional": true }, "node_modules/v8-to-istanbul": { "version": "8.1.1", @@ -22337,7 +22378,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, + "devOptional": true, "engines": { "node": ">=6" } diff --git a/app/package.json b/app/package.json index 66158c0..f3fc217 100644 --- a/app/package.json +++ b/app/package.json @@ -22,10 +22,11 @@ "@use-gesture/react": "^10.3.1", "chart.js": "^4.4.8", "chartjs-plugin-annotation": "^3.1.0", + "dxf-parser": "^1.1.2", "glob": "^11.0.0", "gsap": "^3.12.5", "html2canvas": "^1.4.1", - "immer": "^10.1.1", + "immer": "^9.0.21", "leva": "^0.10.0", "mqtt": "^5.10.4", "postprocessing": "^6.36.4", diff --git a/app/src/components/temporary/SelectFloorPlan.tsx b/app/src/components/temporary/SelectFloorPlan.tsx index 7a95e8e..8f16006 100644 --- a/app/src/components/temporary/SelectFloorPlan.tsx +++ b/app/src/components/temporary/SelectFloorPlan.tsx @@ -1,8 +1,53 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; import useLayoutStore from "../../store/builder/uselayoutStore"; +import { useDfxUpload } from "../../store/builder/store"; +import DxfParser from "dxf-parser"; +import { Box3, BufferGeometry, MathUtils, Vector3 } from "three"; +import { getWallPointsFromBlueprint } from "../../modules/builder/dfx/functions/getWallPointsFromBlueprint"; +import { convertDXFToThree } from "../../modules/builder/dfx/functions/convertDxfToThree"; +// Define types for DXF entities and geometries + const SelectFloorPlan: React.FC = () => { const { currentLayout, setLayout } = useLayoutStore(); + const { setDfxUploaded, setDfxGenerate } = useDfxUpload(); + + const [parsedFile, setParsedFile] = useState(); + const [generate, setGenerate] = useState(false); + + const handleFileUpload = async (event: React.ChangeEvent) => { + const file = event.target.files?.[0]; + if (!file || !file.name.endsWith(".dxf")) { + alert("Please upload a valid .dxf file."); + return; + } + + const reader = new FileReader(); + + reader.onload = async (e) => { + const dxfContent = e.target?.result as string; + + try { + const parser = new DxfParser(); + const parsedData = parser.parse(dxfContent) as DXFData; + const geometries = convertDXFToThree(parsedData); + + setParsedFile(parsedData); + setDfxUploaded(geometries); + + } catch (error) { + echo.error("Failed to import your .dxf file") + } + }; + + reader.readAsText(file); + }; + + useEffect(() => { + if (parsedFile !== undefined) + getWallPointsFromBlueprint({ parsedData: parsedFile, setDfxGenerate }) + }, [generate]) + return (
Preset Layouts @@ -23,9 +68,42 @@ const SelectFloorPlan: React.FC = () => { > Preset 2 + + + +
- + ); }; export default SelectFloorPlan; + + + + + diff --git a/app/src/modules/builder/builder.tsx b/app/src/modules/builder/builder.tsx index 96894fe..82c93ba 100644 --- a/app/src/modules/builder/builder.tsx +++ b/app/src/modules/builder/builder.tsx @@ -49,7 +49,7 @@ import CalculateAreaGroup from "./groups/calculateAreaGroup"; import LayoutImage from "./layout/layoutImage"; import AssetsGroup from "./assetGroup/assetsGroup"; import { Bvh } from "@react-three/drei"; -import FindZoneName from "./findZoneName"; +import DxfFile from "./dfx/LoadBlueprint"; export default function Builder() { const state = useThree(); // Importing the state from the useThree hook, which contains the scene, camera, and other Three.js elements. @@ -312,9 +312,10 @@ export default function Builder() { - + + diff --git a/app/src/modules/builder/dfx/LoadBlueprint.tsx b/app/src/modules/builder/dfx/LoadBlueprint.tsx new file mode 100644 index 0000000..9be72dc --- /dev/null +++ b/app/src/modules/builder/dfx/LoadBlueprint.tsx @@ -0,0 +1,38 @@ +import { useEffect } from 'react'; +import { useDfxUpload, useToggleView } from '../../../store/builder/store'; +import { LineBasicMaterial, Line } from "three"; +import loadInitialPoint from '../IntialLoad/loadInitialPoint'; +import loadInitialLine from '../IntialLoad/loadInitialLine'; +import { TransformControls } from '@react-three/drei'; + +const DxfFile = ({ floorPlanGroupPoint, currentLayerPoint, dragPointControls, floorPlanGroupLine, lines, setUpdateScene }: any) => { + const { dfxuploaded, dfxWallGenerate } = useDfxUpload(); + const { toggleView } = useToggleView(); + + useEffect(() => { + if (dfxWallGenerate && dragPointControls && floorPlanGroupPoint && currentLayerPoint && floorPlanGroupLine) { + lines.current = dfxWallGenerate; + loadInitialPoint(lines, floorPlanGroupPoint, currentLayerPoint, dragPointControls); + loadInitialLine(floorPlanGroupLine, lines); + setUpdateScene(true); + } + }, [lines, floorPlanGroupLine, floorPlanGroupPoint, currentLayerPoint, dragPointControls, dfxWallGenerate]) + + return ( + <> + {dfxuploaded && dfxuploaded.length > 0 && toggleView && dfxuploaded?.map((geometry: any, index: any) => ( + + + + + + ))} + + ); +} + +export default DxfFile; diff --git a/app/src/modules/builder/dfx/functions/convertDxfToThree.ts b/app/src/modules/builder/dfx/functions/convertDxfToThree.ts new file mode 100644 index 0000000..7bf2634 --- /dev/null +++ b/app/src/modules/builder/dfx/functions/convertDxfToThree.ts @@ -0,0 +1,65 @@ +import { BufferGeometry, Vector3 } from "three"; + +export const convertDXFToThree = (dxfData: DXFData): BufferGeometry[] => { + const geometries: BufferGeometry[] = []; + const UNIT = 1000; + if (dxfData.entities) { + dxfData.entities.forEach((entity) => { + // LINE + if (entity.type === "LINE" && entity.vertices) { + const points = [ + new Vector3(entity.vertices[0].x, entity.vertices[0].y, 0), + new Vector3(entity.vertices[1].x, entity.vertices[1].y, 0), + ]; + const geometry = new BufferGeometry().setFromPoints(points); + geometry.scale(1 / UNIT, 1 / UNIT, 1 / UNIT); + geometries.push(geometry); + } + + // LWPOLYLINE + else if (entity.type === "LWPOLYLINE" && entity.vertices) { + const points: Vector3[] = entity.vertices.map( + (v: any) => new Vector3(v.x, v.y, 0) + ); + + for (let i = 0; i < points.length - 1; i++) { + const segment = [points[i], points[i + 1]]; + const geometry = new BufferGeometry().setFromPoints(segment); + geometry.scale(1 / UNIT, 1 / UNIT, 1 / UNIT); + geometries.push(geometry); + } + } + + // ARC + else if ( + entity.type === "ARC" && + entity.center && + entity.radius !== undefined + ) { + const { center, radius, startAngle, endAngle } = entity; + if ( + center === undefined || + radius === undefined || + startAngle === undefined || + endAngle === undefined + ) + return; + const numSegments = 32; + const points: Vector3[] = []; + + for (let i = 0; i <= numSegments; i++) { + const t = i / numSegments; + const angle = startAngle + t * (endAngle - startAngle); // linear interpolation + const x = center.x + radius * Math.cos(angle); + const y = center.y + radius * Math.sin(angle); + points.push(new Vector3(x, y, 0)); + } + + const geometry = new BufferGeometry().setFromPoints(points); + geometry.scale(1 / UNIT, 1 / UNIT, 1 / UNIT); + geometries.push(geometry); + } + }); + } + return geometries; +}; diff --git a/app/src/modules/builder/dfx/functions/getWallPointsFromBlueprint.ts b/app/src/modules/builder/dfx/functions/getWallPointsFromBlueprint.ts new file mode 100644 index 0000000..72057dd --- /dev/null +++ b/app/src/modules/builder/dfx/functions/getWallPointsFromBlueprint.ts @@ -0,0 +1,141 @@ +import { MathUtils, Vector3, BufferGeometry } from "three"; + +interface Props { + parsedData: DXFData; + setDfxGenerate: (walls: WallLineVertex[][]) => void; +} + +export function getWallPointsFromBlueprint({ + parsedData, + setDfxGenerate, +}: Props) { + if (!parsedData.entities) return; + + const unit = 1000; // Convert mm to meters + + const wallVertex: WallLineVertex[][] = []; + + parsedData.entities.forEach((entity: DXFEntity) => { + if (entity.type === "LINE" && entity.vertices) { + const startVec = new Vector3( + entity.vertices[0].x / unit, + 0.01, + -entity.vertices[0].y / unit + ); + const endVec = new Vector3( + entity.vertices[1].x / unit, + 0.01, + -entity.vertices[1].y / unit + ); + + const existingStart = wallVertex + .flat() + .find((v) => v[0].equals(startVec)); + const startPoint: WallLineVertex = existingStart || [ + startVec, + MathUtils.generateUUID(), + 1, + "WallLine", + ]; + + const existingEnd = wallVertex.flat().find((v) => v[0].equals(endVec)); + const endPoint: WallLineVertex = existingEnd || [ + endVec, + MathUtils.generateUUID(), + 1, + "WallLine", + ]; + + wallVertex.push([startPoint, endPoint]); + } else if (entity.type === "LWPOLYLINE" && entity.vertices) { + let firstPoint: WallLineVertex | undefined; + + for (let i = 0; i < entity.vertices.length - 1; i++) { + const startVec = new Vector3( + entity.vertices[i].x / unit, + 0.01, + -entity.vertices[i].y / unit + ); + const endVec = new Vector3( + entity.vertices[i + 1].x / unit, + 0.01, + -entity.vertices[i + 1].y / unit + ); + + const existingStart = wallVertex + .flat() + .find((v) => v[0].equals(startVec)); + const startPoint: WallLineVertex = existingStart || [ + startVec, + MathUtils.generateUUID(), + 1, + "WallLine", + ]; + + const existingEnd = wallVertex.flat().find((v) => v[0].equals(endVec)); + const endPoint: WallLineVertex = existingEnd || [ + endVec, + MathUtils.generateUUID(), + 1, + "WallLine", + ]; + + wallVertex.push([startPoint, endPoint]); + + if (i === 0) firstPoint = startPoint; + if (i === entity.vertices.length - 2 && firstPoint) + wallVertex.push([endPoint, firstPoint]); + } + } else if (entity.type === "ARC") { + const { center, radius, startAngle, endAngle } = entity; + if ( + !center || + radius === undefined || + startAngle === undefined || + endAngle === undefined + ) + return; + + const numSegments = 16; + const angleStep = (endAngle - startAngle) / numSegments; + + const arcPoints: Vector3[] = []; + + for (let i = 0; i <= numSegments; i++) { + const angle = startAngle + i * angleStep; + const x = center.x + radius * Math.cos(angle); + const y = -center.y + radius * Math.sin(angle); + arcPoints.push(new Vector3(x / unit, 0.01, y / unit)); + } + + for (let i = 0; i < arcPoints.length - 1; i++) { + const startVec = arcPoints[i]; + const endVec = arcPoints[i + 1]; + + const existingStart = wallVertex + .flat() + .find((v) => v[0].equals(startVec)); + const startPoint: WallLineVertex = existingStart || [ + startVec, + MathUtils.generateUUID(), + 1, + "WallLine", + ]; + + const existingEnd = wallVertex.flat().find((v) => v[0].equals(endVec)); + const endPoint: WallLineVertex = existingEnd || [ + endVec, + MathUtils.generateUUID(), + 1, + "WallLine", + ]; + + wallVertex.push([startPoint, endPoint]); + } + } else { + console.error("Unsupported entity type:", entity.type); + } + }); + + setDfxGenerate(wallVertex); +} diff --git a/app/src/modules/builder/findZoneName.tsx b/app/src/modules/builder/findZoneName.tsx deleted file mode 100644 index 1f8966c..0000000 --- a/app/src/modules/builder/findZoneName.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import { useToggleView, useZones } from '../../store/builder/store'; -import { Html } from '@react-three/drei'; - -const FindZoneName = () => { - const { zones } = useZones(); - const { toggleView } = useToggleView(); - return ( - <> - {!toggleView && zones?.map((zone: any) => ( - - {zone.zoneName} - - ))} - - ); -} - -export default FindZoneName; diff --git a/app/src/modules/builder/groups/floorPlanGroup.tsx b/app/src/modules/builder/groups/floorPlanGroup.tsx index a8832f2..6a9e50f 100644 --- a/app/src/modules/builder/groups/floorPlanGroup.tsx +++ b/app/src/modules/builder/groups/floorPlanGroup.tsx @@ -50,6 +50,7 @@ const FloorPlanGroup = ({ floorPlanGroup, floorPlanGroupLine, floorPlanGroupPoin getLines(organization).then((data) => { const Lines: Types.Lines = objectLinesToArray(data); + console.log('Lines: ', Lines); // const data = localStorage.getItem("Lines"); diff --git a/app/src/store/builder/store.ts b/app/src/store/builder/store.ts index f05a1b2..f1f4133 100644 --- a/app/src/store/builder/store.ts +++ b/app/src/store/builder/store.ts @@ -459,88 +459,91 @@ interface ShortcutStore { } export const useShortcutStore = create((set) => ({ - showShortcuts: false, - setShowShortcuts: (value) => set({ showShortcuts: value }), - toggleShortcuts: () => - set((state) => ({ showShortcuts: !state.showShortcuts })), + showShortcuts: false, + setShowShortcuts: (value) => set({ showShortcuts: value }), + toggleShortcuts: () => + set((state) => ({ showShortcuts: !state.showShortcuts })), })); export const useMachineCount = create((set: any) => ({ - machineCount: 0, - setMachineCount: (x: any) => set({ machineCount: x }), + machineCount: 0, + setMachineCount: (x: any) => set({ machineCount: x }), })); export const useMachineUptime = create((set: any) => ({ - machineActiveTime: 0, - setMachineActiveTime: (x: any) => set({ machineActiveTime: x }), + machineActiveTime: 0, + setMachineActiveTime: (x: any) => set({ machineActiveTime: x }), })); export const useMaterialCycle = create((set: any) => ({ - materialCycleTime: 0, - setMaterialCycleTime: (x: any) => set({ materialCycleTime: x }), + materialCycleTime: 0, + setMaterialCycleTime: (x: any) => set({ materialCycleTime: x }), })); export const useThroughPutData = create((set: any) => ({ - throughputData: 0, - setThroughputData: (x: any) => set({ throughputData: x }), + throughputData: 0, + setThroughputData: (x: any) => set({ throughputData: x }), })); export const useProductionCapacityData = create((set: any) => ({ - productionCapacityData: 0, - setProductionCapacityData: (x: any) => set({ productionCapacityData: x }), + productionCapacityData: 0, + setProductionCapacityData: (x: any) => set({ productionCapacityData: x }), })); export const useProcessBar = create((set: any) => ({ - processBar: [], - setProcessBar: (x: any) => set({ processBar: x }), + processBar: [], + setProcessBar: (x: any) => set({ processBar: x }), +})); +export const useDfxUpload = create((set: any) => ({ + dfxuploaded: [], + dfxWallGenerate: [], + setDfxUploaded: (x: any) => set({ dfxuploaded: x }), + setDfxGenerate: (x: any) => set({ dfxWallGenerate: x }), })); - type InputValuesStore = { - inputValues: Record; - setInputValues: (values: Record) => void; - updateInputValue: (label: string, value: string) => void; // <- New + inputValues: Record; + setInputValues: (values: Record) => void; + updateInputValue: (label: string, value: string) => void; // <- New }; export const useInputValues = create((set) => ({ - inputValues: {}, - setInputValues: (values) => set({ inputValues: values }), - updateInputValue: (label, value) => - set((state) => ({ - inputValues: { - ...state.inputValues, - [label]: value, - }, - })), + inputValues: {}, + setInputValues: (values) => set({ inputValues: values }), + updateInputValue: (label, value) => + set((state) => ({ + inputValues: { + ...state.inputValues, + [label]: value, + }, + })), })); export interface ROISummaryData { - productName: string; - roiPercentage: number; - paybackPeriod: number; - totalCost: number; - revenueGenerated: number; - netProfit: number; - netLoss: number; + productName: string; + roiPercentage: number; + paybackPeriod: number; + totalCost: number; + revenueGenerated: number; + netProfit: number; + netLoss: number; } interface ROISummaryStore { - roiSummary: ROISummaryData; - setRoiSummaryData: (values: ROISummaryData) => void; + roiSummary: ROISummaryData; + setRoiSummaryData: (values: ROISummaryData) => void; } export const useROISummaryData = create((set) => ({ - roiSummary: { - productName: "", - roiPercentage: 0, - paybackPeriod: 0, - totalCost: 0, - revenueGenerated: 0, - netProfit: 0, - netLoss: 0, - }, - setRoiSummaryData: (values) => set({ roiSummary: values }), + roiSummary: { + productName: "", + roiPercentage: 0, + paybackPeriod: 0, + totalCost: 0, + revenueGenerated: 0, + netProfit: 0, + netLoss: 0, + }, + setRoiSummaryData: (values) => set({ roiSummary: values }), })); - - interface CompareStore { comparePopUp: boolean; setComparePopUp: (value: boolean) => void; diff --git a/app/src/store/builder/uselayoutStore.ts b/app/src/store/builder/uselayoutStore.ts index acaeb50..a8de713 100644 --- a/app/src/store/builder/uselayoutStore.ts +++ b/app/src/store/builder/uselayoutStore.ts @@ -1,6 +1,6 @@ import { create } from 'zustand'; -type Layout = null | 'layout1' | 'layout2'; +type Layout = null | 'layout1' | 'layout2' ; type LayoutState = { currentLayout: Layout; diff --git a/app/src/types/builderTypes.d.ts b/app/src/types/builderTypes.d.ts index adfdc78..3c5bdfb 100644 --- a/app/src/types/builderTypes.d.ts +++ b/app/src/types/builderTypes.d.ts @@ -1,31 +1,53 @@ interface Asset { - modelUuid: string; - modelName: string; - assetId: string; - position: [number, number, number]; - rotation: [number, number, number]; - isLocked: boolean; - isCollidable: boolean; - isVisible: boolean; - opacity: number; - animations?: string[]; - animationState?: { - current: string; - playing: boolean; + modelUuid: string; + modelName: string; + assetId: string; + position: [number, number, number]; + rotation: [number, number, number]; + isLocked: boolean; + isCollidable: boolean; + isVisible: boolean; + opacity: number; + animations?: string[]; + animationState?: { + current: string; + playing: boolean; + }; + eventData?: { + type: string; + point?: { + uuid: string; + position: [number, number, number]; + rotation: [number, number, number]; }; - eventData?: { - type: string; - point?: { - uuid: string; - position: [number, number, number]; - rotation: [number, number, number]; - } - points?: { - uuid: string; - position: [number, number, number]; - rotation: [number, number, number]; - }[]; - } -}; + points?: { + uuid: string; + position: [number, number, number]; + rotation: [number, number, number]; + }[]; + }; +} -type Assets = Asset[]; \ No newline at end of file +type Assets = Asset[]; + +// DXF Entity Schema + +interface DXFEntity { + type: string; + vertices?: { x: number; y: number }[]; + center?: { x: number; y: number; z: number }; + radius?: number; + startAngle?: number; + endAngle?: number; +} + +interface DXFData { + entities?: DXFEntity[]; +} + +type WallLineVertex = [ + Vector3, + string, // uuid + number, + string // "WallLine" +]; From 3d5159f1b6d0e737f8adc4d74ae2bfbd66aa3742 Mon Sep 17 00:00:00 2001 From: Poovizhi99 Date: Wed, 28 May 2025 12:07:31 +0530 Subject: [PATCH 2/4] Update Generated DXF Walls to backend --- .../components/temporary/SelectFloorPlan.tsx | 66 +++++--- app/src/modules/builder/builder.tsx | 8 +- app/src/modules/builder/dfx/LoadBlueprint.tsx | 147 +++++++++++++++--- .../functions/getWallPointsFromBlueprint.ts | 108 +++++++++---- app/src/store/builder/store.ts | 4 +- 5 files changed, 254 insertions(+), 79 deletions(-) diff --git a/app/src/components/temporary/SelectFloorPlan.tsx b/app/src/components/temporary/SelectFloorPlan.tsx index 8f16006..2035173 100644 --- a/app/src/components/temporary/SelectFloorPlan.tsx +++ b/app/src/components/temporary/SelectFloorPlan.tsx @@ -2,20 +2,25 @@ import React, { useEffect, useState } from "react"; import useLayoutStore from "../../store/builder/uselayoutStore"; import { useDfxUpload } from "../../store/builder/store"; import DxfParser from "dxf-parser"; -import { Box3, BufferGeometry, MathUtils, Vector3 } from "three"; import { getWallPointsFromBlueprint } from "../../modules/builder/dfx/functions/getWallPointsFromBlueprint"; import { convertDXFToThree } from "../../modules/builder/dfx/functions/convertDxfToThree"; -// Define types for DXF entities and geometries - const SelectFloorPlan: React.FC = () => { + // Access layout state and state setters const { currentLayout, setLayout } = useLayoutStore(); - const { setDfxUploaded, setDfxGenerate } = useDfxUpload(); + // Access DXF-related states and setters + const { setDfxUploaded, setDfxGenerate, setObjValue, objValue } = useDfxUpload(); - const [parsedFile, setParsedFile] = useState(); + // Local state to store the parsed DXF file + const [parsedFile, setParsedFile] = useState(undefined); + + // Flag to trigger generation after file upload const [generate, setGenerate] = useState(false); + // Handles file upload and DXF parsing const handleFileUpload = async (event: React.ChangeEvent) => { + setLayout(null); // Reset current layout + setParsedFile(undefined); // Clear any previously parsed file const file = event.target.files?.[0]; if (!file || !file.name.endsWith(".dxf")) { alert("Please upload a valid .dxf file."); @@ -23,47 +28,60 @@ const SelectFloorPlan: React.FC = () => { } const reader = new FileReader(); - reader.onload = async (e) => { const dxfContent = e.target?.result as string; try { const parser = new DxfParser(); - const parsedData = parser.parse(dxfContent) as DXFData; - const geometries = convertDXFToThree(parsedData); - - setParsedFile(parsedData); + const parsedDatas = parser.parse(dxfContent) as DXFData; + const geometries = convertDXFToThree(parsedDatas); + setParsedFile(parsedDatas); + setObjValue({ x: 0, y: 0, z: 0 }); setDfxUploaded(geometries); - } catch (error) { - echo.error("Failed to import your .dxf file") + console.error("Failed to import your .dxf file", error); + } finally { + // ✅ Reset input AFTER processing + event.target.value = ""; } }; - reader.readAsText(file); + reader.readAsText(file); // Read the uploaded file as text }; + // Trigger wall point generation when `generate` flag changes useEffect(() => { - if (parsedFile !== undefined) - getWallPointsFromBlueprint({ parsedData: parsedFile, setDfxGenerate }) - }, [generate]) + if (parsedFile !== undefined) { + getWallPointsFromBlueprint({ parsedData: parsedFile, setDfxGenerate, objValue }); + } + }, [generate]); + + useEffect(() => { + console.log("parsedFile: ", parsedFile); + }, [parsedFile]); return (
Preset Layouts +
+
-
+ ); }; -export default SelectFloorPlan; - - - - - +export default SelectFloorPlan; \ No newline at end of file diff --git a/app/src/modules/builder/builder.tsx b/app/src/modules/builder/builder.tsx index 82c93ba..d6df0d0 100644 --- a/app/src/modules/builder/builder.tsx +++ b/app/src/modules/builder/builder.tsx @@ -314,7 +314,13 @@ export default function Builder() { - + diff --git a/app/src/modules/builder/dfx/LoadBlueprint.tsx b/app/src/modules/builder/dfx/LoadBlueprint.tsx index 9be72dc..66fc558 100644 --- a/app/src/modules/builder/dfx/LoadBlueprint.tsx +++ b/app/src/modules/builder/dfx/LoadBlueprint.tsx @@ -1,38 +1,145 @@ -import { useEffect } from 'react'; -import { useDfxUpload, useToggleView } from '../../../store/builder/store'; -import { LineBasicMaterial, Line } from "three"; +import { useEffect, useRef } from 'react'; +import { useDfxUpload, useSocketStore, useToggleView, useUpdateScene } from '../../../store/builder/store'; +import { LineBasicMaterial, Line } from 'three'; import loadInitialPoint from '../IntialLoad/loadInitialPoint'; import loadInitialLine from '../IntialLoad/loadInitialLine'; import { TransformControls } from '@react-three/drei'; +import { getWallPointsFromBlueprint } from './functions/getWallPointsFromBlueprint'; +import * as Types from '../../../types/world/worldTypes'; +import arrayLineToObject from '../geomentries/lines/lineConvertions/arrayLineToObject'; -const DxfFile = ({ floorPlanGroupPoint, currentLayerPoint, dragPointControls, floorPlanGroupLine, lines, setUpdateScene }: any) => { - const { dfxuploaded, dfxWallGenerate } = useDfxUpload(); +// Interface defining the props for the DxfFile component +interface DxfFileProps { + lines: Types.RefLines; // Reference to lines in the DXF file + floorPlanGroupPoint: Types.RefGroup; // Reference to floor plan points group + dragPointControls: Types.RefDragControl; // Reference to drag controls + floorPlanGroupLine: Types.RefGroup; // Reference to floor plan lines group + currentLayerPoint: Types.RefMeshArray; // Reference to current layer points +} + +/** + * DxfFile component handles the rendering and manipulation of DXf file data in a 3D scene. + * It processes the DXF data to create points and lines representing walls and allows + * transformation controls for interactive editing. + */ +const DxfFile = ({ + floorPlanGroupPoint, + currentLayerPoint, + dragPointControls, + floorPlanGroupLine, + lines, +}: DxfFileProps) => { + // State management hooks + const { dfxuploaded, dfxWallGenerate, setObjValue, objValue } = useDfxUpload(); + const { setUpdateScene } = useUpdateScene(); const { toggleView } = useToggleView(); + const { socket } = useSocketStore(); + // Refs for storing line objects + const lineRefs = useRef([]); + + /** + * Effect hook that runs when DXF wall generation is triggered. + * Loads initial points and lines from the DXF data and updates the scene. + */ useEffect(() => { - if (dfxWallGenerate && dragPointControls && floorPlanGroupPoint && currentLayerPoint && floorPlanGroupLine) { - lines.current = dfxWallGenerate; + if ( + dfxWallGenerate && + dragPointControls && + floorPlanGroupPoint && + currentLayerPoint && + floorPlanGroupLine + ) { + // Store generated lines in ref + lines.current.push(...dfxWallGenerate); + dfxWallGenerate.map((line: any) => { + const lineData = arrayLineToObject(line as Types.Line); + const email = localStorage.getItem('email') + const organization = (email!.split("@")[1]).split(".")[0]; + + //REST + + // setLine(organization, lineData.layer!, lineData.line!, lineData.type!); + + //SOCKET + + const input = { + organization: organization, + layer: lineData.layer, + line: lineData.line, + type: lineData.type, + socketId: socket.id + } + + socket.emit('v1:Line:create', input); + + }) + + // Load initial points and lines from DXF data loadInitialPoint(lines, floorPlanGroupPoint, currentLayerPoint, dragPointControls); loadInitialLine(floorPlanGroupLine, lines); + + // Trigger scene update setUpdateScene(true); } - }, [lines, floorPlanGroupLine, floorPlanGroupPoint, currentLayerPoint, dragPointControls, dfxWallGenerate]) + }, [ + lines, + floorPlanGroupLine, + floorPlanGroupPoint, + currentLayerPoint, + dragPointControls, + dfxWallGenerate, + ]); + + /** + * Handles transformation changes for individual lines. + * Updates the object value state with new position and recalculates wall points. + * @param index - Index of the line being transformed + */ + const handleTransformChange = (index: number) => { + const line = lineRefs.current[index]; + if (!line) return; + + // Get current position of the line + const position = line.position; + + // Update state with new position + setObjValue({ x: position.x, y: position.y, z: position.z }); + + // Recalculate wall points based on new position + getWallPointsFromBlueprint({ + objValue: { x: position.x, y: position.y, z: position.z }, + setDfxGenerate: () => { }, + }); + }; return ( <> - {dfxuploaded && dfxuploaded.length > 0 && toggleView && dfxuploaded?.map((geometry: any, index: any) => ( - - - 0 && + toggleView && + dfxuploaded?.map((geometry: any, index: number) => { + // Create a new line object for each geometry in the DXF data + const line = new Line(geometry, new LineBasicMaterial({ color: 'red' })); + line.rotation.set(-Math.PI / 2, 0, 0); + + // Store line reference + lineRefs.current[index] = line; + + return ( + - - - ))} + object={line} + onMouseUp={() => handleTransformChange(index)} + > + {/* Render the line with current position from state */} + + + ); + })} ); -} +}; -export default DxfFile; +export default DxfFile; \ No newline at end of file diff --git a/app/src/modules/builder/dfx/functions/getWallPointsFromBlueprint.ts b/app/src/modules/builder/dfx/functions/getWallPointsFromBlueprint.ts index 72057dd..13cae84 100644 --- a/app/src/modules/builder/dfx/functions/getWallPointsFromBlueprint.ts +++ b/app/src/modules/builder/dfx/functions/getWallPointsFromBlueprint.ts @@ -1,41 +1,59 @@ import { MathUtils, Vector3, BufferGeometry } from "three"; interface Props { - parsedData: DXFData; - setDfxGenerate: (walls: WallLineVertex[][]) => void; + parsedData?: DXFData; // Parsed DXF file data + setDfxGenerate?: (walls: WallLineVertex[][]) => void; // Callback to set generated walls + objValue: any; // Object position values for offset calculation } +/** + * Processes DXF entities to generate wall points for a blueprint. + * Handles LINE, LWPOLYLINE, and ARC entity types, converting them to wall segments. + * Applies unit conversion and positional offsets to all points. + * + * @param {Props} params - Configuration parameters + * @param {DXFData} params.parsedData - Parsed DXF file data + * @param {Function} params.setDfxGenerate - Callback to store generated walls + * @param {Object} params.objValue - Contains x,y,z offsets for position adjustment + */ export function getWallPointsFromBlueprint({ parsedData, setDfxGenerate, + objValue, }: Props) { + // Early return if no data is provided + if (!parsedData) return; if (!parsedData.entities) return; - const unit = 1000; // Convert mm to meters - - const wallVertex: WallLineVertex[][] = []; + const unit = 1000; // Conversion factor from millimeters to meters + const wallVertex: WallLineVertex[][] = []; // Stores all generated wall segments + // Process each entity in the DXF file parsedData.entities.forEach((entity: DXFEntity) => { + // Handle LINE entities if (entity.type === "LINE" && entity.vertices) { + // Create start and end vectors with unit conversion and position offset const startVec = new Vector3( entity.vertices[0].x / unit, - 0.01, - -entity.vertices[0].y / unit - ); + 0.01, // Slightly above ground to avoid z-fighting + -entity.vertices[0].y / unit // Invert Y-axis to match Three.js coordinate system + ).add(new Vector3(objValue.x, 0, objValue.z)); + const endVec = new Vector3( entity.vertices[1].x / unit, 0.01, -entity.vertices[1].y / unit - ); + ).add(new Vector3(objValue.x, 0, objValue.z)); + // Check if points already exist to avoid duplicates const existingStart = wallVertex .flat() .find((v) => v[0].equals(startVec)); const startPoint: WallLineVertex = existingStart || [ startVec, - MathUtils.generateUUID(), - 1, - "WallLine", + MathUtils.generateUUID(), // Generate unique ID for new points + 1, // Default weight + "WallLine", // Type identifier ]; const existingEnd = wallVertex.flat().find((v) => v[0].equals(endVec)); @@ -46,22 +64,29 @@ export function getWallPointsFromBlueprint({ "WallLine", ]; + // Add the line segment to our collection wallVertex.push([startPoint, endPoint]); - } else if (entity.type === "LWPOLYLINE" && entity.vertices) { - let firstPoint: WallLineVertex | undefined; + } + // Handle LWPOLYLINE entities (connected line segments) + else if (entity.type === "LWPOLYLINE" && entity.vertices) { + let firstPoint: WallLineVertex | undefined; // Store first point for closing the polyline + // Process each vertex pair in the polyline for (let i = 0; i < entity.vertices.length - 1; i++) { + // Convert vertices to Three.js vectors with offset const startVec = new Vector3( entity.vertices[i].x / unit, 0.01, -entity.vertices[i].y / unit - ); + ).add(new Vector3(objValue.x, 0, objValue.z)); + const endVec = new Vector3( entity.vertices[i + 1].x / unit, 0.01, -entity.vertices[i + 1].y / unit - ); + ).add(new Vector3(objValue.x, 0, objValue.z)); + // Check for existing points const existingStart = wallVertex .flat() .find((v) => v[0].equals(startVec)); @@ -82,36 +107,52 @@ export function getWallPointsFromBlueprint({ wallVertex.push([startPoint, endPoint]); + // Store first point and create closing segment if this is the last vertex if (i === 0) firstPoint = startPoint; - if (i === entity.vertices.length - 2 && firstPoint) + if (i === entity.vertices.length - 2 && firstPoint) { wallVertex.push([endPoint, firstPoint]); + } } - } else if (entity.type === "ARC") { + } + // Handle ARC entities + else if (entity.type === "ARC") { const { center, radius, startAngle, endAngle } = entity; + // Validate required ARC properties if ( !center || radius === undefined || startAngle === undefined || endAngle === undefined - ) + ) { return; - - const numSegments = 16; - const angleStep = (endAngle - startAngle) / numSegments; - - const arcPoints: Vector3[] = []; - - for (let i = 0; i <= numSegments; i++) { - const angle = startAngle + i * angleStep; - const x = center.x + radius * Math.cos(angle); - const y = -center.y + radius * Math.sin(angle); - arcPoints.push(new Vector3(x / unit, 0.01, y / unit)); } + // Convert ARC to series of line segments + const numSegments = 16; // Number of segments to approximate the arc + const angleStep = (endAngle - startAngle) / numSegments; + const arcPoints: Vector3[] = []; // Stores points along the arc + + // Generate points along the arc + for (let i = 0; i <= numSegments; i++) { + const angle = startAngle + i * angleStep; + // Calculate arc point in DXF coordinate system + const x = center.x + radius * Math.cos(angle); + const y = -center.y + radius * Math.sin(angle); // Invert Y-axis + + // Convert to Three.js vector with offset + const vec = new Vector3(x / unit, 0.01, y / unit).add( + new Vector3(objValue.x, 0, objValue.z) + ); + + arcPoints.push(vec); + } + + // Create line segments between arc points for (let i = 0; i < arcPoints.length - 1; i++) { const startVec = arcPoints[i]; const endVec = arcPoints[i + 1]; + // Check for existing points const existingStart = wallVertex .flat() .find((v) => v[0].equals(startVec)); @@ -132,10 +173,13 @@ export function getWallPointsFromBlueprint({ wallVertex.push([startPoint, endPoint]); } - } else { + } + // Log unsupported entity types + else { console.error("Unsupported entity type:", entity.type); } }); - setDfxGenerate(wallVertex); + // Return the generated walls through callback if provided + setDfxGenerate && setDfxGenerate(wallVertex); } diff --git a/app/src/store/builder/store.ts b/app/src/store/builder/store.ts index c0af867..01dc754 100644 --- a/app/src/store/builder/store.ts +++ b/app/src/store/builder/store.ts @@ -494,8 +494,10 @@ export const useProcessBar = create((set: any) => ({ export const useDfxUpload = create((set: any) => ({ dfxuploaded: [], dfxWallGenerate: [], + objValue: { x: 0, y: 0, z: 0 }, setDfxUploaded: (x: any) => set({ dfxuploaded: x }), setDfxGenerate: (x: any) => set({ dfxWallGenerate: x }), + setObjValue: (x: any) => set({ objValue: x }), })); type InputValuesStore = { @@ -551,7 +553,7 @@ interface CompareStore { } export const useCompareStore = create((set) => ({ - comparePopUp: false, + comparePopUp: true, setComparePopUp: (value) => set({ comparePopUp: value }), toggleComparePopUp: () => set((state) => ({ comparePopUp: !state.comparePopUp })), From c0fed911eba9e00a3716eef0acbba1c38f2de8e3 Mon Sep 17 00:00:00 2001 From: Poovizhi99 Date: Wed, 28 May 2025 12:40:40 +0530 Subject: [PATCH 3/4] Refactor SelectFloorPlan component by removing debug useEffect and cleaning up setGenerate calls; add showY prop to TransformControls in DxfFile component; initialize package-lock.json --- app/src/components/temporary/SelectFloorPlan.tsx | 7 ++----- app/src/modules/builder/dfx/LoadBlueprint.tsx | 1 + package-lock.json | 6 ++++++ 3 files changed, 9 insertions(+), 5 deletions(-) create mode 100644 package-lock.json diff --git a/app/src/components/temporary/SelectFloorPlan.tsx b/app/src/components/temporary/SelectFloorPlan.tsx index 2035173..5056e4c 100644 --- a/app/src/components/temporary/SelectFloorPlan.tsx +++ b/app/src/components/temporary/SelectFloorPlan.tsx @@ -56,9 +56,6 @@ const SelectFloorPlan: React.FC = () => { } }, [generate]); - useEffect(() => { - console.log("parsedFile: ", parsedFile); - }, [parsedFile]); return (
@@ -70,7 +67,7 @@ const SelectFloorPlan: React.FC = () => { onClick={() => { setLayout("layout1"); setDfxUploaded([]); - setGenerate(false); + setGenerate(false); }} > Preset 1 @@ -112,7 +109,7 @@ const SelectFloorPlan: React.FC = () => { onClick={() => { setDfxUploaded([]); setLayout(null); - setGenerate(!generate); + setGenerate(!generate); }} > Generate diff --git a/app/src/modules/builder/dfx/LoadBlueprint.tsx b/app/src/modules/builder/dfx/LoadBlueprint.tsx index 66fc558..8e0a1ab 100644 --- a/app/src/modules/builder/dfx/LoadBlueprint.tsx +++ b/app/src/modules/builder/dfx/LoadBlueprint.tsx @@ -131,6 +131,7 @@ const DxfFile = ({ handleTransformChange(index)} > {/* Render the line with current position from state */} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..5ef6c4e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "Dwinzo_dev", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} From c77abff4248284183b33aea8b91a62df3137ecc2 Mon Sep 17 00:00:00 2001 From: Poovizhi99 Date: Thu, 29 May 2025 14:29:53 +0530 Subject: [PATCH 4/4] Implement rename mode functionality and integrate RenameTooltip component; update state management for renaming and mouse interactions in FloorGroup and Project components. --- .../components/ui/features/RenameTooltip.tsx | 7 ++- app/src/modules/builder/groups/floorGroup.tsx | 19 +++++++- .../modules/builder/groups/floorPlanGroup.tsx | 1 - app/src/pages/Project.tsx | 31 +++++++++++++ app/src/store/builder/store.ts | 5 ++ .../utils/shortcutkeys/handleShortcutKeys.ts | 46 ++++++++++++------- 6 files changed, 87 insertions(+), 22 deletions(-) diff --git a/app/src/components/ui/features/RenameTooltip.tsx b/app/src/components/ui/features/RenameTooltip.tsx index 180ba85..87de122 100644 --- a/app/src/components/ui/features/RenameTooltip.tsx +++ b/app/src/components/ui/features/RenameTooltip.tsx @@ -4,6 +4,7 @@ import { useLeftData, useTopData, } from "../../../store/visualization/useZone3DWidgetStore"; +import { useRenameModeStore } from "../../../store/builder/store"; type RenameTooltipProps = { name: string; @@ -13,12 +14,14 @@ type RenameTooltipProps = { const RenameTooltip: React.FC = ({ name, onSubmit }) => { const [value, setValue] = useState(name); - const { top } = useTopData(); - const { left } = useLeftData(); + const { top, setTop } = useTopData(); + const { left, setLeft } = useLeftData(); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); onSubmit(value.trim()); + setTop(0); + setLeft(0); }; return ( diff --git a/app/src/modules/builder/groups/floorGroup.tsx b/app/src/modules/builder/groups/floorGroup.tsx index 2c2a8df..2cf5586 100644 --- a/app/src/modules/builder/groups/floorGroup.tsx +++ b/app/src/modules/builder/groups/floorGroup.tsx @@ -6,6 +6,7 @@ import { useToggleView, useWallVisibility, useUpdateScene, + useRenameModeStore, } from "../../../store/builder/store"; import hideRoof from "../geomentries/roofs/hideRoof"; import hideWalls from "../geomentries/walls/hideWalls"; @@ -15,6 +16,7 @@ import addPillar from "../geomentries/pillars/addPillar"; import DeletePillar from "../geomentries/pillars/deletePillar"; import DeletableHoveredPillar from "../geomentries/pillars/deletableHoveredPillar"; import loadFloor from "../geomentries/floors/loadFloor"; +import { useLeftData, useTopData } from "../../../store/visualization/useZone3DWidgetStore"; const FloorGroup = ({ floorGroup, @@ -30,6 +32,9 @@ const FloorGroup = ({ const { addAction } = useAddAction(); const { deleteTool } = useDeleteTool(); const { updateScene, setUpdateScene } = useUpdateScene(); + const { setTop } = useTopData(); + const { setLeft } = useLeftData(); + const { isRenameMode, setIsRenameMode } = useRenameModeStore(); useEffect(() => { if (updateScene) { @@ -55,6 +60,7 @@ const FloorGroup = ({ let isLeftMouseDown = false; const onMouseDown = (evt: any) => { + if (evt.button === 0) { isLeftMouseDown = true; drag = false; @@ -62,6 +68,7 @@ const FloorGroup = ({ }; const onMouseUp = (evt: any) => { + setIsRenameMode(false); if (evt.button === 0) { isLeftMouseDown = false; if (!drag) { @@ -75,7 +82,15 @@ const FloorGroup = ({ } }; - const onMouseMove = () => { + const onMouseMove = (evt: any) => { + if (!canvasElement) return; + const canvasRect = canvasElement.getBoundingClientRect(); + const relativeX = evt.clientX - canvasRect.left; + const relativeY = evt.clientY - canvasRect.top; + if (!isRenameMode) { + setTop(relativeY); + setLeft(relativeX); + } if (isLeftMouseDown) { drag = true; } @@ -90,7 +105,7 @@ const FloorGroup = ({ canvasElement.removeEventListener("mouseup", onMouseUp); canvasElement.removeEventListener("mousemove", onMouseMove); }; - }, [deleteTool, addAction]); + }, [deleteTool, addAction, isRenameMode]); useFrame(() => { hideRoof(roofVisibility, floorGroup, camera); diff --git a/app/src/modules/builder/groups/floorPlanGroup.tsx b/app/src/modules/builder/groups/floorPlanGroup.tsx index dfb52c3..74f0783 100644 --- a/app/src/modules/builder/groups/floorPlanGroup.tsx +++ b/app/src/modules/builder/groups/floorPlanGroup.tsx @@ -50,7 +50,6 @@ const FloorPlanGroup = ({ floorPlanGroup, floorPlanGroupLine, floorPlanGroupPoin getLines(organization).then((data) => { const Lines: Types.Lines = objectLinesToArray(data); - console.log('Lines: ', Lines); // const data = localStorage.getItem("Lines"); diff --git a/app/src/pages/Project.tsx b/app/src/pages/Project.tsx index 3b75365..3fa0d0f 100644 --- a/app/src/pages/Project.tsx +++ b/app/src/pages/Project.tsx @@ -15,6 +15,8 @@ import { useLoadingProgress, useWidgetSubOption, useSaveVersion, + useRenameModeStore, + useSelectedFloorItem, } from "../store/builder/store"; import { useNavigate } from "react-router-dom"; import { usePlayButtonStore } from "../store/usePlayButtonStore"; @@ -39,6 +41,8 @@ import RegularDropDown from "../components/ui/inputs/RegularDropDown"; import VersionSaved from "../components/layout/sidebarRight/versionHisory/VersionSaved"; import SimulationPlayer from "../components/ui/simulation/simulationPlayer"; import { useProductStore } from "../store/simulation/useProductStore"; +import RenameTooltip from "../components/ui/features/RenameTooltip"; +import { setFloorItemApi } from "../services/factoryBuilder/assest/floorAsset/setFloorItemApi"; const Project: React.FC = () => { let navigate = useNavigate(); @@ -50,10 +54,13 @@ const Project: React.FC = () => { const { setUserName } = useUserName(); const { setOrganization } = useOrganization(); const { setFloorItems } = useFloorItems(); + const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem(); const { setWallItems } = useWallItems(); const { setZones } = useZones(); const { isVersionSaved } = useSaveVersion(); const { products } = useProductStore(); + const { isRenameMode, setIsRenameMode } = useRenameModeStore(); + // console.log('isRenameMode: ', isRenameMode); useEffect(() => { if (!isVersionSaved) { @@ -112,6 +119,29 @@ const Project: React.FC = () => { setSelectedLayout(option); // Set selected layout console.log("Selected layout:", option); }; + + const handleObjectRename = async (newName: string) => { + const email = localStorage.getItem("email") ?? ""; + const organization = email?.split("@")[1]?.split(".")[0]; + let response = await setFloorItemApi( + organization, + selectedFloorItem.userData.modelUuid, + newName + ); + selectedFloorItem.userData.name = newName; + setSelectedFloorItem(selectedFloorItem); + setIsRenameMode(false); + setFloorItems((prevFloorItems: any[]) => + prevFloorItems.map((floorItems) => + floorItems.modelUuid === selectedFloorItem.userData.modelUuid + .id + ? { ...floorItems, modelName: response.modelName } + : floorItems + ) + ); + } + + return (
{!selectedUser && ( @@ -161,6 +191,7 @@ const Project: React.FC = () => { >
+ {isRenameMode && selectedFloorItem?.userData.name && } {selectedUser && } {isLogListVisible && ( diff --git a/app/src/store/builder/store.ts b/app/src/store/builder/store.ts index 01dc754..e8440ce 100644 --- a/app/src/store/builder/store.ts +++ b/app/src/store/builder/store.ts @@ -297,6 +297,11 @@ export const useUserName = create((set: any) => ({ setUserName: (x: any) => set({ userName: x }), })); +export const useRenameModeStore = create((set: any) => ({ + isRenameMode: false, + setIsRenameMode: (state: boolean) => set({ isRenameMode: state }), +})); + export const useObjectPosition = create((set: any) => ({ objectPosition: { x: undefined, y: undefined, z: undefined }, setObjectPosition: (newObjectPosition: any) => diff --git a/app/src/utils/shortcutkeys/handleShortcutKeys.ts b/app/src/utils/shortcutkeys/handleShortcutKeys.ts index eb0e3c3..14403fd 100644 --- a/app/src/utils/shortcutkeys/handleShortcutKeys.ts +++ b/app/src/utils/shortcutkeys/handleShortcutKeys.ts @@ -6,7 +6,9 @@ import { useActiveTool, useAddAction, useDeleteTool, + useRenameModeStore, useSaveVersion, + useSelectedFloorItem, useSelectedWallItem, useShortcutStore, useToggleView, @@ -37,6 +39,8 @@ const KeyPressListener: React.FC = () => { const { setIsVersionSaved } = useSaveVersion(); const { isLogListVisible, setIsLogListVisible } = useLogger(); const { hidePlayer, setHidePlayer } = usePlayerStore(); + const { isRenameMode, setIsRenameMode } = useRenameModeStore(); + const { selectedFloorItem } = useSelectedFloorItem(); const isTextInput = (element: Element | null): boolean => element instanceof HTMLInputElement || @@ -152,9 +156,24 @@ const KeyPressListener: React.FC = () => { }; const handleKeyPress = (event: KeyboardEvent) => { - if (isTextInput(document.activeElement)) return; - const keyCombination = detectModifierKeys(event); + + if (isTextInput(document.activeElement) && keyCombination !== "ESCAPE") + return; + + if (keyCombination === "ESCAPE") { + console.log("esc"); + setWalkMode(false); + setActiveTool("cursor"); + setActiveSubTool("cursor"); + setIsPlaying(false); + clearSelectedZone(); + setShowShortcuts(false); + setIsVersionSaved(false); + setIsLogListVisible(false); + setIsRenameMode(false); + } + if ( !keyCombination || ["F5", "F11", "F12"].includes(event.key) || @@ -186,26 +205,17 @@ const KeyPressListener: React.FC = () => { setHidePlayer(!hidePlayer); } - if (keyCombination === "ESCAPE") { - setWalkMode(false); - setActiveTool("cursor"); - setActiveSubTool("cursor"); - setIsPlaying(false); - clearSelectedZone(); - setShowShortcuts(false); - setIsVersionSaved(false); - setIsLogListVisible(false); - } - if (keyCombination === "Ctrl+Shift+?") { setShowShortcuts(!showShortcuts); } + if (selectedFloorItem && keyCombination === "F2") { + setIsRenameMode(true); + } + // Placeholder for future implementation if ( - ["Ctrl+Z", "Ctrl+Y", "Ctrl+Shift+Z", "Ctrl+F"].includes( - keyCombination - ) + ["Ctrl+Z", "Ctrl+Y", "Ctrl+Shift+Z", "Ctrl+F"].includes(keyCombination) ) { // Implement undo/redo/help/find/shortcuts } @@ -223,7 +233,9 @@ const KeyPressListener: React.FC = () => { showShortcuts, isPlaying, isLogListVisible, - hidePlayer + hidePlayer, + selectedFloorItem, + isRenameMode, ]); return null;