-
Rotation
+
{heading}
-
Rotate :
+
{label}
onChange(e.target.value)}
placeholder={placeholder}
value={value}
+ disabled={disabled}
+ min={min}
+ max={max}
+ step={step}
/>
diff --git a/app/src/components/layout/sidebarRight/properties/AisleProperties.tsx b/app/src/components/layout/sidebarRight/properties/AisleProperties.tsx
index c343805..ca2317d 100644
--- a/app/src/components/layout/sidebarRight/properties/AisleProperties.tsx
+++ b/app/src/components/layout/sidebarRight/properties/AisleProperties.tsx
@@ -21,24 +21,25 @@ interface TextureItem {
texture: string;
}
+export const aisleTextureList: TextureItem[] = [
+ { color: "yellow", id: "#FBE50E", brief: "pedestrian walkways", texture: "" },
+ { color: "gray", id: "#6F6F7A", brief: "basic", texture: "" },
+ { color: "green", id: "#43C06D", brief: "pedestrian walkways", texture: "" },
+ { color: "orange", id: "#FF711B", brief: "material flow", texture: "" },
+ { color: "blue", id: "#488EF6", brief: "vehicle paths", texture: "" },
+ { color: "purple", id: "#AF52DE", brief: "material flow", texture: "" },
+ { color: "red", id: "#FF3B30", brief: "safety zone", texture: "" },
+ { color: "bright green", id: "#66FF00", brief: "safety zone", texture: "" },
+ { color: "yellow-black", id: "yellow-black", brief: "utility aisles", texture: "" },
+ { color: "white-black", id: "white-black", brief: "utility aisles", texture: "" },
+];
+
const AisleProperties: React.FC = () => {
const [collapsePresets, setCollapsePresets] = useState(false);
const [collapseTexture, setCollapseTexture] = useState(true);
const { aisleType, aisleWidth, aisleColor, dashLength, gapLength, dotRadius, aisleLength, isFlipped, setAisleType, setAisleColor, setAisleWidth, setDashLength, setGapLength, setDotRadius, setAisleLength, setIsFlipped } = useBuilderStore();
- const aisleTextureList: TextureItem[] = [
- { color: "yellow", id: "yellow", brief: "pedestrian walkways", texture: "" },
- { color: "gray", id: "gray", brief: "basic", texture: "" },
- { color: "green", id: "green", brief: "pedestrian walkways", texture: "" },
- { color: "orange", id: "orange", brief: "material flow", texture: "" },
- { color: "blue", id: "blue", brief: "vehicle paths", texture: "" },
- { color: "purple", id: "purple", brief: "material flow", texture: "" },
- { color: "red", id: "red", brief: "safety zone", texture: "" },
- { color: "bright green", id: "#66FF00", brief: "safety zone", texture: "" },
- { color: "yellow-black", id: "yellow-black", brief: "utility aisles", texture: "" },
- { color: "white-black", id: "white-black", brief: "utility aisles", texture: "" },
- ];
const aisleTypes: {
name: string;
@@ -285,7 +286,12 @@ const AisleProperties: React.FC = () => {
onClick={() => setAisleColor(val.id)}
aria-pressed={aisleColor === val.id}
>
-
{val.texture}
+
+ {val.texture}
+
{val.color}
{`( ${val.brief} )`}
diff --git a/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx b/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx
index a62abda..3a36e6c 100644
--- a/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx
+++ b/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx
@@ -32,9 +32,9 @@ const AssetProperties: React.FC = () => {
setUserData([]);
};
- const handleUserDataChange = (id: number, newValue: string) => {};
+ const handleUserDataChange = (id: number, newValue: string) => { };
- const handleRemoveUserData = (id: number) => {};
+ const handleRemoveUserData = (id: number) => { };
const handleAnimationClick = (animation: string) => {
if (selectedFloorItem) {
@@ -57,14 +57,16 @@ const AssetProperties: React.FC = () => {
{objectPosition && (
{}}
+ disabled={true}
+ onChange={() => { }}
value1={parseFloat(objectPosition.x.toFixed(5))}
value2={parseFloat(objectPosition.z.toFixed(5))}
/>
)}
{objectRotation && (
{}}
+ disabled={true}
+ onChange={() => { }}
value={parseFloat(objectRotation.y.toFixed(5))}
/>
)}
@@ -107,7 +109,7 @@ const AssetProperties: React.FC = () => {
if (asset.modelUuid !== selectedFloorItem.uuid || !asset.animations)
return (
i === 0 && (
-
+
Looks like there are no preset animations yet. Stay tuned for
future additions!
diff --git a/app/src/components/layout/sidebarRight/properties/DecalProperties.tsx b/app/src/components/layout/sidebarRight/properties/DecalProperties.tsx
deleted file mode 100644
index 4163ea5..0000000
--- a/app/src/components/layout/sidebarRight/properties/DecalProperties.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import {
- LayeringBottomIcon,
- LayeringTopIcon,
-} from "../../../icons/ExportCommonIcons";
-import InputRange from "../../../ui/inputs/InputRange";
-import RotationInput from "../customInput/RotationInput";
-import Vector3Input from "../customInput/Vector3Input";
-
-const DecalProperties = () => {
- return (
-
-
Decal Propertis
-
- {}}
- value={10}
- />
- console.log(value)}
- header="Scale"
- value={[0, 0, 0] as [number, number, number]}
- />
-
-
-
- console.log(value)}
- key={"6"}
- />
-
-
-
Layering
-
-
-
-
-
-
-
-
- );
-};
-
-export default DecalProperties;
diff --git a/app/src/components/layout/sidebarRight/properties/SelectedAisleProperties.tsx b/app/src/components/layout/sidebarRight/properties/SelectedAisleProperties.tsx
new file mode 100644
index 0000000..3dc2540
--- /dev/null
+++ b/app/src/components/layout/sidebarRight/properties/SelectedAisleProperties.tsx
@@ -0,0 +1,529 @@
+import React, { useEffect, useState } from "react";
+import { useParams } from "react-router-dom";
+import InputWithDropDown from "../../../ui/inputs/InputWithDropDown";
+import { ArrowIcon } from "../../../icons/ExportCommonIcons";
+
+// image imports
+import Arc from "../../../../assets/image/aisleTypes/Arc.png";
+import Arrow from "../../../../assets/image/aisleTypes/Arrow.png";
+import Arrows from "../../../../assets/image/aisleTypes/Arrows.png";
+import Circle from "../../../../assets/image/aisleTypes/Circle.png";
+import Dashed from "../../../../assets/image/aisleTypes/Dashed.png";
+import Directional from "../../../../assets/image/aisleTypes/Directional.png";
+import Dotted from "../../../../assets/image/aisleTypes/Dotted.png";
+import Solid from "../../../../assets/image/aisleTypes/Solid.png";
+import InputToggle from "../../../ui/inputs/InputToggle";
+import { useBuilderStore } from "../../../../store/builder/useBuilderStore";
+import { useSceneContext } from "../../../../modules/scene/sceneContext";
+import { useVersionContext } from "../../../../modules/builder/version/versionContext";
+import { useSocketStore } from "../../../../store/builder/store";
+import { getUserData } from "../../../../functions/getUserData";
+import { aisleTextureList } from "./AisleProperties";
+
+const SelectedAisleProperties: React.FC = () => {
+ const [collapsePresets, setCollapsePresets] = useState(false);
+ const [collapseTexture, setCollapseTexture] = useState(true);
+ const { aisleStore } = useSceneContext();
+ const { getAisleById, updateAisle, setDashedAisleProperties, setDottedAisleProperties, setArrowsAisleProperties, setArcAisleWidth, setColor } = aisleStore();
+ const { selectedVersionStore } = useVersionContext();
+ const { selectedVersion } = selectedVersionStore();
+ const { socket } = useSocketStore();
+ const { userId, organization } = getUserData();
+ const { projectId } = useParams();
+ const [selectedAisleData, setSelectedAisleData] = useState
();
+
+ const { selectedAisle, setSelectedAisle } = useBuilderStore();
+
+ useEffect(() => {
+ const aisleData = getAisleById(selectedAisle?.aisleMesh?.uuid || "");
+ setSelectedAisleData(aisleData);
+ }, [selectedAisle, getAisleById]);
+
+ if (!selectedAisleData) return null;
+
+ const updateBackend = (updatedAisle: Aisle) => {
+ if (updatedAisle && projectId) {
+
+ // API
+
+ // upsertAisleApi(updatedAisle.aisleUuid, updatedAisle.points, updatedAisle.type, projectId, selectedVersion?.versionId || '');
+
+ // SOCKET
+
+ const data = {
+ projectId: projectId,
+ versionId: selectedVersion?.versionId || '',
+ userId: userId,
+ organization: organization,
+ aisleUuid: updatedAisle.aisleUuid,
+ points: updatedAisle.points,
+ type: updatedAisle.type
+ }
+
+ socket.emit('v1:model-aisle:add', data);
+ }
+ }
+
+ const aisleTypes: {
+ name: string;
+ type: AisleTypes;
+ id: string;
+ thumbnail: string;
+ }[] = [
+ { name: "Solid", type: "solid-aisle", id: "1", thumbnail: Solid },
+ { name: "Dotted", type: "dotted-aisle", id: "2", thumbnail: Dotted },
+ { name: "Dashed", type: "dashed-aisle", id: "3", thumbnail: Dashed },
+ { name: "Arrow", type: "arrow-aisle", id: "4", thumbnail: Arrow },
+ { name: "Continuous Arrows", type: "arrows-aisle", id: "5", thumbnail: Arrows },
+ { name: "Directional", type: "junction-aisle", id: "6", thumbnail: Directional },
+ { name: "Arc", type: "arc-aisle", id: "7", thumbnail: Arc },
+ { name: "Circle", type: "circle-aisle", id: "8", thumbnail: Circle },
+ ];
+
+ const createAisleTypeObject = (newType: AisleTypes, currentType: AisleType): AisleType => {
+ switch (newType) {
+ case 'solid-aisle':
+ return {
+ aisleType: 'solid-aisle',
+ aisleColor: currentType.aisleColor,
+ aisleWidth: 'aisleWidth' in currentType ? currentType.aisleWidth : 0.1
+ } as SolidAisle;
+
+ case 'dashed-aisle':
+ return {
+ aisleType: 'dashed-aisle',
+ aisleColor: currentType.aisleColor,
+ aisleWidth: 'aisleWidth' in currentType ? currentType.aisleWidth : 0.1,
+ dashLength: 'dashLength' in currentType ? (currentType as DashedAisle).dashLength : 0.5,
+ gapLength: 'gapLength' in currentType ? (currentType as DashedAisle).gapLength : 0.3
+ } as DashedAisle;
+
+ case 'dotted-aisle':
+ return {
+ aisleType: 'dotted-aisle',
+ aisleColor: currentType.aisleColor,
+ dotRadius: 'dotRadius' in currentType ? (currentType as DottedAisle).dotRadius : 0.1,
+ gapLength: 'gapLength' in currentType ? (currentType as DottedAisle).gapLength : 0.3
+ } as DottedAisle;
+
+ case 'arrow-aisle':
+ return {
+ aisleType: 'arrow-aisle',
+ aisleColor: currentType.aisleColor,
+ aisleWidth: 'aisleWidth' in currentType ? currentType.aisleWidth : 0.1
+ } as ArrowAisle;
+
+ case 'arrows-aisle':
+ return {
+ aisleType: 'arrows-aisle',
+ aisleColor: currentType.aisleColor,
+ aisleWidth: 'aisleWidth' in currentType ? currentType.aisleWidth : 0.1,
+ aisleLength: 'aisleLength' in currentType ? (currentType as ArrowsAisle).aisleLength : 0.6,
+ gapLength: 'gapLength' in currentType ? (currentType as ArrowsAisle).gapLength : 0.3
+ } as ArrowsAisle;
+
+ case 'arc-aisle':
+ return {
+ aisleType: 'arc-aisle',
+ aisleColor: currentType.aisleColor,
+ aisleWidth: 'aisleWidth' in currentType ? currentType.aisleWidth : 0.1,
+ isFlipped: 'isFlipped' in currentType ? (currentType as ArcAisle).isFlipped : false
+ } as ArcAisle;
+
+ case 'circle-aisle':
+ return {
+ aisleType: 'circle-aisle',
+ aisleColor: currentType.aisleColor,
+ aisleWidth: 'aisleWidth' in currentType ? currentType.aisleWidth : 0.1
+ } as CircleAisle;
+
+ case 'junction-aisle':
+ return {
+ aisleType: 'junction-aisle',
+ aisleColor: currentType.aisleColor,
+ aisleWidth: 'aisleWidth' in currentType ? currentType.aisleWidth : 0.1,
+ isFlipped: 'isFlipped' in currentType ? (currentType as JunctionAisle).isFlipped : false
+ } as JunctionAisle;
+
+ default:
+ return {
+ aisleType: 'solid-aisle',
+ aisleColor: currentType.aisleColor,
+ aisleWidth: 0.1
+ } as SolidAisle;
+ }
+ };
+
+ const handleAisleTypeChange = (newType: AisleTypes) => {
+ if (!selectedAisle?.aisleData) return;
+ const newAisleType = createAisleTypeObject(newType, selectedAisleData.type);
+
+ const updatedAisle = updateAisle(selectedAisleData.aisleUuid, {
+ type: newAisleType
+ });
+
+ setSelectedAisle({ aisleData: selectedAisle.aisleData, aisleMesh: null });
+
+ setSelectedAisleData({
+ ...selectedAisleData,
+ type: newAisleType
+ });
+
+ if (updatedAisle) {
+ updateBackend(updatedAisle);
+ }
+ };
+
+ const handleColorChange = (value: AisleColors) => {
+ const updatedAisle = setColor(selectedAisleData.aisleUuid, value);
+
+ setSelectedAisleData({
+ ...selectedAisleData,
+ type: {
+ ...selectedAisleData.type,
+ aisleColor: value
+ }
+ })
+
+ if (updatedAisle) {
+ updateBackend(updatedAisle);
+ }
+ };
+
+ const handleAisleWidthChange = (value: string) => {
+ const width = parseFloat(value);
+ if (!isNaN(width) && selectedAisleData.type.aisleType !== 'dotted-aisle') {
+ const updatedAisle = updateAisle(selectedAisleData.aisleUuid, {
+ type: {
+ ...selectedAisleData.type,
+ aisleWidth: width
+ }
+ });
+
+ setSelectedAisleData({
+ ...selectedAisleData,
+ type: {
+ ...selectedAisleData.type,
+ aisleWidth: width
+ }
+ })
+
+ if (updatedAisle) {
+ updateBackend(updatedAisle);
+ }
+ }
+ };
+
+ const handleDashLengthChange = (value: string) => {
+ const length = parseFloat(value);
+ if (!isNaN(length) && selectedAisleData.type.aisleType === 'dashed-aisle') {
+ const updatedAisle = setDashedAisleProperties(selectedAisleData.aisleUuid, {
+ dashLength: length
+ });
+
+ setSelectedAisleData({
+ ...selectedAisleData,
+ type: {
+ ...(selectedAisleData.type as DashedAisle),
+ dashLength: length
+ }
+ })
+
+ if (updatedAisle) {
+ updateBackend(updatedAisle);
+ }
+ }
+ };
+
+ const handleGapLengthChange = (value: string) => {
+ const length = parseFloat(value);
+ if (!isNaN(length) && (selectedAisleData.type.aisleType === 'dashed-aisle' || selectedAisleData.type.aisleType === 'dotted-aisle' || selectedAisleData.type.aisleType === 'arrows-aisle')) {
+ if (selectedAisleData.type.aisleType === 'dashed-aisle') {
+ const updatedAisle = setDashedAisleProperties(selectedAisleData.aisleUuid, {
+ gapLength: length
+ });
+
+ setSelectedAisleData({
+ ...selectedAisleData,
+ type: {
+ ...(selectedAisleData.type as DashedAisle),
+ gapLength: length
+ }
+ })
+
+ if (updatedAisle) {
+ updateBackend(updatedAisle);
+ }
+ } else if (selectedAisleData.type.aisleType === 'dotted-aisle') {
+ const updatedAisle = setDottedAisleProperties(selectedAisleData.aisleUuid, {
+ gapLength: length
+ });
+
+ setSelectedAisleData({
+ ...selectedAisleData,
+ type: {
+ ...(selectedAisleData.type as DottedAisle),
+ gapLength: length
+ }
+ })
+
+ if (updatedAisle) {
+ updateBackend(updatedAisle);
+ }
+ } else if (selectedAisleData.type.aisleType === 'arrows-aisle') {
+ const updatedAisle = setArrowsAisleProperties(selectedAisleData.aisleUuid, {
+ gapLength: length
+ });
+
+ setSelectedAisleData({
+ ...selectedAisleData,
+ type: {
+ ...(selectedAisleData.type as ArrowsAisle),
+ gapLength: length
+ }
+ })
+
+ if (updatedAisle) {
+ updateBackend(updatedAisle);
+ }
+ }
+ }
+ };
+
+ const handleDotRadiusChange = (value: string) => {
+ const radius = parseFloat(value);
+ if (!isNaN(radius) && selectedAisleData.type.aisleType === 'dotted-aisle') {
+ const updatedAisle = setDottedAisleProperties(selectedAisleData.aisleUuid, {
+ dotRadius: radius
+ });
+
+ setSelectedAisleData({
+ ...selectedAisleData,
+ type: {
+ ...(selectedAisleData.type as DottedAisle),
+ dotRadius: radius
+ }
+ })
+
+ if (updatedAisle) {
+ updateBackend(updatedAisle);
+ }
+ }
+ };
+
+ const handleAisleLengthChange = (value: string) => {
+ const length = parseFloat(value);
+ if (!isNaN(length) && selectedAisleData.type.aisleType === 'arrows-aisle') {
+ const updatedAisle = setArrowsAisleProperties(selectedAisleData.aisleUuid, {
+ aisleLength: length
+ });
+
+ setSelectedAisleData({
+ ...selectedAisleData,
+ type: {
+ ...(selectedAisleData.type as ArrowsAisle),
+ aisleLength: length
+ }
+ })
+
+ if (updatedAisle) {
+ updateBackend(updatedAisle);
+ }
+ }
+ };
+
+ const handleIsFlippedChange = () => {
+ if (selectedAisleData.type.aisleType === 'arc-aisle' || selectedAisleData.type.aisleType === 'junction-aisle') {
+ const currentType = selectedAisleData.type as ArcAisle | JunctionAisle;
+ const currentFlipped = currentType.isFlipped || false;
+ const updatedAisle = setArcAisleWidth(selectedAisleData.aisleUuid, {
+ isFlipped: !currentFlipped
+ });
+
+ setSelectedAisleData({
+ ...selectedAisleData,
+ type: {
+ ...currentType,
+ isFlipped: !currentFlipped
+ }
+ })
+
+ if (updatedAisle) {
+ updateBackend(updatedAisle);
+ }
+ }
+ };
+
+ const renderAdvancedProperties = () => {
+ switch (selectedAisleData.type.aisleType) {
+ case 'dashed-aisle':
+ const dashedType = selectedAisleData.type as DashedAisle;
+ return (
+ <>
+
+
+ >
+ );
+ case 'dotted-aisle':
+ const dottedType = selectedAisleData.type as DottedAisle;
+ return (
+ <>
+
+
+ >
+ );
+ case 'arrows-aisle':
+ const arrowsType = selectedAisleData.type as ArrowsAisle;
+ return (
+ <>
+
+
+ >
+ );
+ case 'junction-aisle':
+ case 'arc-aisle':
+ const flippedType = selectedAisleData.type as ArcAisle | JunctionAisle;
+ return (
+
+ );
+ default:
+ return null;
+ }
+ };
+
+ return (
+
+
Properties
+
+ {/* Basic Properties */}
+
+ {selectedAisleData.type.aisleType !== 'dotted-aisle' &&
+
+ }
+ {renderAdvancedProperties()}
+
+
+ {/* Presets */}
+
+
+ {!collapsePresets && (
+
+ {aisleTypes.map((val) => (
+
+
+
+ ))}
+
+ )}
+
+
+ {/* Texture */}
+
+
+
+ {collapseTexture && (
+
+ {aisleTextureList.map((val) => (
+
+ ))}
+
+ )}
+
+
+ );
+};
+
+export default SelectedAisleProperties;
\ No newline at end of file
diff --git a/app/src/components/layout/sidebarRight/properties/SelectedDecalProperties.tsx b/app/src/components/layout/sidebarRight/properties/SelectedDecalProperties.tsx
new file mode 100644
index 0000000..3a67865
--- /dev/null
+++ b/app/src/components/layout/sidebarRight/properties/SelectedDecalProperties.tsx
@@ -0,0 +1,188 @@
+import { useParams } from "react-router-dom";
+import { useVersionContext } from "../../../../modules/builder/version/versionContext";
+import { useSceneContext } from "../../../../modules/scene/sceneContext";
+import { useBuilderStore } from "../../../../store/builder/useBuilderStore";
+import { LayeringBottomIcon, LayeringTopIcon } from "../../../icons/ExportCommonIcons";
+import { useSocketStore } from "../../../../store/builder/store";
+import InputRange from "../../../ui/inputs/InputRange";
+
+import { getUserData } from "../../../../functions/getUserData";
+// import { upsertWallApi } from "../../../../services/factoryBuilder/wall/upsertWallApi";
+// import { upsertFloorApi } from "../../../../services/factoryBuilder/floor/upsertFloorApi";
+
+const SelectedDecalProperties = () => {
+ const { selectedDecal, setSelectedDecal } = useBuilderStore();
+ const { wallStore, floorStore } = useSceneContext();
+ const { updateDecal: updateDecalInWall } = wallStore();
+ const { updateDecal: updateDecalInFloor } = floorStore();
+ const { userId, organization } = getUserData();
+ const { selectedVersionStore } = useVersionContext();
+ const { selectedVersion } = selectedVersionStore();
+ const { projectId } = useParams();
+ const { socket } = useSocketStore();
+
+ const updateBackend = (updatedData: Wall | Floor) => {
+ if ('wallUuid' in updatedData) {
+ if (projectId && updatedData) {
+ // API
+
+ // upsertWallApi(projectId, selectedVersion?.versionId || '', updatedData);
+
+ // SOCKET
+
+ const data = {
+ wallData: updatedData,
+ projectId: projectId,
+ versionId: selectedVersion?.versionId || '',
+ userId: userId,
+ organization: organization
+ }
+
+ socket.emit('v1:model-Wall:add', data);
+ }
+ } else if ('floorUuid' in updatedData) {
+ if (projectId && updatedData) {
+ // API
+
+ // upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedData);
+
+ // SOCKET
+
+ const data = {
+ floorData: updatedData,
+ projectId: projectId,
+ versionId: selectedVersion?.versionId || '',
+ userId: userId,
+ organization: organization
+ }
+
+ socket.emit('v1:model-Floor:add', data);
+ }
+ }
+ }
+
+ const handleRotationChange = (value: number) => {
+ if (!selectedDecal) return;
+ const updatedDecal = { ...selectedDecal.decalData, decalRotation: value };
+ setSelectedDecal({ ...selectedDecal, decalData: updatedDecal });
+
+ if ('wallUuid' in selectedDecal.decalData.decalType) {
+ const updatedWall = updateDecalInWall(updatedDecal.decalUuid, updatedDecal);
+ if (updatedWall) updateBackend(updatedWall);
+ } else if ('floorUuid' in selectedDecal.decalData.decalType) {
+ const updatedFloor = updateDecalInFloor(updatedDecal.decalUuid, updatedDecal);
+ if (updatedFloor) updateBackend(updatedFloor);
+ }
+ }
+
+ const handleScaleChange = (value: number) => {
+ if (!selectedDecal) return;
+ const updatedDecal = { ...selectedDecal.decalData, decalScale: value };
+ setSelectedDecal({ ...selectedDecal, decalData: updatedDecal });
+
+ if ('wallUuid' in selectedDecal.decalData.decalType) {
+ const updatedWall = updateDecalInWall(updatedDecal.decalUuid, updatedDecal);
+ if (updatedWall) updateBackend(updatedWall);
+ } else if ('floorUuid' in selectedDecal.decalData.decalType) {
+ const updatedFloor = updateDecalInFloor(updatedDecal.decalUuid, updatedDecal);
+ if (updatedFloor) updateBackend(updatedFloor);
+ }
+ }
+
+ const handleOpacityChange = (value: number) => {
+ if (!selectedDecal) return;
+ const updatedDecal = { ...selectedDecal.decalData, decalOpacity: value };
+ setSelectedDecal({ ...selectedDecal, decalData: updatedDecal });
+
+ if ('wallUuid' in selectedDecal.decalData.decalType) {
+ const updatedWall = updateDecalInWall(updatedDecal.decalUuid, updatedDecal);
+ if (updatedWall) updateBackend(updatedWall);
+ } else if ('floorUuid' in selectedDecal.decalData.decalType) {
+ const updatedFloor = updateDecalInFloor(updatedDecal.decalUuid, updatedDecal);
+ if (updatedFloor) updateBackend(updatedFloor);
+ }
+ }
+
+ const handleLayerChange = (direction: "up" | "down") => {
+ if (!selectedDecal) return;
+
+ const position: [number, number, number] = [...(selectedDecal.decalData.decalPosition || [0, 0, 0]),];
+
+ if (direction === "up") {
+ position[2] = Math.abs(position[2]);
+ } else {
+ position[2] = -Math.abs(position[2]);
+ }
+
+ const updatedDecal: Decal = { ...selectedDecal.decalData, decalPosition: position, };
+
+ setSelectedDecal({ ...selectedDecal, decalData: updatedDecal });
+
+ if ("wallUuid" in selectedDecal.decalData.decalType) {
+ const updatedWall = updateDecalInWall(updatedDecal.decalUuid, updatedDecal);
+ if (updatedWall) updateBackend(updatedWall);
+ } else if ("floorUuid" in selectedDecal.decalData.decalType) {
+ const updatedFloor = updateDecalInFloor(updatedDecal.decalUuid, updatedDecal);
+ if (updatedFloor) updateBackend(updatedFloor);
+ }
+ };
+
+ if (!selectedDecal) return null;
+
+ return (
+
+
Decal Properties
+
+ handleRotationChange(value)}
+ />
+
+ handleScaleChange(value)}
+ />
+
+
+
+ handleOpacityChange(value)}
+ />
+
+
+
Layering
+
+
+
+
+
+
+
+
+ );
+};
+
+export default SelectedDecalProperties;
diff --git a/app/src/components/layout/sidebarRight/resourceManagement/hrm/Hrm.tsx b/app/src/components/layout/sidebarRight/resourceManagement/hrm/Hrm.tsx
index 99828ae..ce17d60 100644
--- a/app/src/components/layout/sidebarRight/resourceManagement/hrm/Hrm.tsx
+++ b/app/src/components/layout/sidebarRight/resourceManagement/hrm/Hrm.tsx
@@ -1,199 +1,225 @@
-import { useEffect, useState } from 'react'
-import { ClockThreeIcon, LocationPinIcon, TargetIcon } from '../../../../icons/ExportCommonIcons'
-import { useSceneContext } from '../../../../../modules/scene/sceneContext';
-import { useProductContext } from '../../../../../modules/simulation/products/productContext';
-import RenameInput from '../../../../ui/inputs/RenameInput';
-import { useResourceManagementId } from '../../../../../store/builder/store';
-import { set } from 'immer/dist/internal';
+import { useEffect, useState } from "react";
+import {
+ ClockThreeIcon,
+ LocationPinIcon,
+ TargetIcon,
+} from "../../../../icons/ExportCommonIcons";
+import { useSceneContext } from "../../../../../modules/scene/sceneContext";
+import RenameInput from "../../../../ui/inputs/RenameInput";
+import { useResourceManagementId } from "../../../../../store/builder/store";
+import { getAssetThumbnail } from "../../../../../services/factoryBuilder/asset/assets/getAssetThumbnail";
// import NavigateCatagory from '../NavigateCatagory'
const Hrm = () => {
- const [selectedCard, setSelectedCard] = useState(0);
- const [workers, setWorkers] = useState([]);
-
- const { productStore } = useSceneContext();
- const { products, getProductById } = productStore();
- const { selectedProductStore } = useProductContext();
- const { selectedProduct } = selectedProductStore();
- const { setResourceManagementId } = useResourceManagementId();
-
- useEffect(() => {
- if (selectedProduct) {
- const productDetails = getProductById(selectedProduct.productUuid);
- const workerDetails = productDetails?.eventDatas || [];
-
- const formattedWorkers = workerDetails
- .filter((worker: any) => worker.type === "human")
- .map((worker: any, index: number) => ({
- employee: {
- image: "",
- name: worker.modelName,
- modelId: worker.modelUuid,
- employee_id: `HR-${204 + index}`,
- status: "Active",
- },
- task: {
- status: "Ongoing",
- title: worker.taskTitle || "No Task Assigned",
- location: {
- floor: worker.floor || 0,
- zone: worker.zone || "N/A"
- },
- planned_time_hours: worker.plannedTime || 0,
- time_spent_hours: worker.timeSpent || 0,
- total_tasks: worker.totalTasks || 0,
- completed_tasks: worker.completedTasks || 0
- },
- actions: [
- "Assign Task",
- "Reassign Task",
- "Pause",
- "Emergency Stop"
- ],
- location: `Floor ${worker.floor || "-"} . Zone ${worker.zone || "-"}`
- }));
-
- setWorkers(formattedWorkers);
- }
- }, [selectedProduct, getProductById]);
-
- useEffect(() => {
- //
- }, [workers]);
-
-
-
-
- // const employee_details = [
- // {
- // "employee": {
- // image: "",
- // "name": "John Doe",
- // "employee_id": "HR-204",
- // "status": "Active",
-
- // },
- // "task": {
- // "status": "Ongoing",
- // "title": "Inspecting Machine X",
- // "location": {
- // "floor": 4,
- // "zone": "B"
- // },
- // "planned_time_hours": 6,
- // "time_spent_hours": 2,
- // "total_tasks": 12,
- // "completed_tasks": 3
- // },
- // "actions": [
- // "Assign Task",
- // "Reassign Task",
- // "Pause",
- // "Emergency Stop"
- // ],
- // "location": "Floor 4 . Zone B"
- // },
- // {
- // "employee": {
- // image: "",
- // "name": "Alice Smith",
- // "employee_id": "HR-205",
- // "status": "Active",
-
- // },
- // "task": {
- // "status": "Ongoing",
- // "title": "Calibrating Sensor Y",
- // "location": {
- // "floor": 2,
- // "zone": "A"
- // },
- // "planned_time_hours": 4,
- // "time_spent_hours": 1.5,
- // "total_tasks": 10,
- // "completed_tasks": 2
- // },
- // "actions": [
- // "Assign Task",
- // "Reassign Task",
- // "Pause",
- // "Emergency Stop"
- // ],
- // "location": "Floor 4 . Zone B"
- // },
- // {
- // "employee": {
- // image: "",
- // "name": "Michael Lee",
- // "employee_id": "HR-206",
- // "status": "Active",
-
- // },
- // "task": {
- // "status": "Ongoing",
- // "title": "Testing Conveyor Belt Z",
- // "location": {
- // "floor": 5,
- // "zone": "C"
- // },
- // "planned_time_hours": 5,
- // "time_spent_hours": 3,
- // "total_tasks": 8,
- // "completed_tasks": 5
- // },
- // "actions": [
- // "Assign Task",
- // "Reassign Task",
- // "Pause",
- // "Emergency Stop"
- // ],
- // "location": "Floor 4 . Zone B"
- // },
- // ]
- function handleRenameWorker(newName: string) {
- //
+ const [selectedCard, setSelectedCard] = useState(0);
+ const [workers, setWorkers] = useState([]);
+ const { setResourceManagementId } = useResourceManagementId();
+ const { assetStore } = useSceneContext();
+ const { assets: allAssets } = assetStore();
+ async function getAsset(assetId: string) {
+ let thumbnail = await getAssetThumbnail(assetId);
+ if (thumbnail.thumbnail) {
+ let assetImage = thumbnail.thumbnail;
+ return assetImage;
}
- function handleHumanClick(employee: any) {
- if (employee.modelId) {
- setResourceManagementId(employee.modelId);
- }
+ }
+
+ useEffect(() => {
+ if (allAssets.length > 0) {
+ const fetchWorkers = async () => {
+ const humans = allAssets.filter(
+ (worker: any) => worker.eventData.type === "Human"
+ );
+
+ const formattedWorkers = await Promise.all(
+ humans.map(async (worker: any, index: number) => {
+ const assetImage = await getAsset(worker.assetId);
+
+ return {
+ employee: {
+ image: assetImage,
+ name: worker.modelName,
+ modelId: worker.modelUuid,
+ employee_id: `HR-${204 + index}`,
+ status: "Active",
+ },
+ task: {
+ status: "Ongoing",
+ title: worker.taskTitle ?? "No Task Assigned",
+ location: {
+ floor: worker.floor ?? 0,
+ zone: worker.zone ?? "N/A",
+ },
+ planned_time_hours: worker.plannedTime ?? 0,
+ time_spent_hours: worker.timeSpent ?? 0,
+ total_tasks: worker.totalTasks ?? 0,
+ completed_tasks: worker.completedTasks ?? 0,
+ },
+ actions: [
+ "Assign Task",
+ "Reassign Task",
+ "Pause",
+ "Emergency Stop",
+ ],
+ location: `Floor ${worker.floor || "-"} . Zone ${
+ worker.zone || "-"
+ }`,
+ };
+ })
+ );
+
+ setWorkers(formattedWorkers);
+ };
+
+ fetchWorkers();
}
+ }, [allAssets]);
+ // const employee_details = [
+ // {
+ // "employee": {
+ // image: "",
+ // "name": "John Doe",
+ // "employee_id": "HR-204",
+ // "status": "Active",
- return (
- <>
- {/*
+ {/* */}
-
- {workers.map((employee, index) => (
-
setSelectedCard(index)}
- key={index}
- >
-
- {activeModule !== "visualization" && (
+ {toggleThreeD && activeModule !== "visualization" && (
<>
diff --git a/app/src/components/ui/features/RenameTooltip.tsx b/app/src/components/ui/features/RenameTooltip.tsx
index 87de122..d55c5d7 100644
--- a/app/src/components/ui/features/RenameTooltip.tsx
+++ b/app/src/components/ui/features/RenameTooltip.tsx
@@ -4,7 +4,6 @@ import {
useLeftData,
useTopData,
} from "../../../store/visualization/useZone3DWidgetStore";
-import { useRenameModeStore } from "../../../store/builder/store";
type RenameTooltipProps = {
name: string;
@@ -20,6 +19,7 @@ const RenameTooltip: React.FC
= ({ name, onSubmit }) => {
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
onSubmit(value.trim());
+ echo.info(`Selected Object has been renamed to ${value.trim()}`);
setTop(0);
setLeft(0);
};
diff --git a/app/src/components/ui/inputs/RenameInput.tsx b/app/src/components/ui/inputs/RenameInput.tsx
index 637dec3..31cc311 100644
--- a/app/src/components/ui/inputs/RenameInput.tsx
+++ b/app/src/components/ui/inputs/RenameInput.tsx
@@ -12,7 +12,12 @@ interface RenameInputProps {
canEdit?: boolean;
}
-const RenameInput: React.FC = ({ value, onRename, checkDuplicate, canEdit = true }) => {
+const RenameInput: React.FC = ({
+ value,
+ onRename,
+ checkDuplicate,
+ canEdit = true,
+}) => {
const [isEditing, setIsEditing] = useState(false);
const [text, setText] = useState(value);
const [isDuplicate, setIsDuplicate] = useState(false);
@@ -36,10 +41,10 @@ const RenameInput: React.FC = ({ value, onRename, checkDuplica
};
const handleBlur = () => {
-
- if (isDuplicate) return
+ if (isDuplicate) return;
setIsEditing(false);
if (onRename && !isDuplicate) {
+ echo.info(`Selected Object has been renamed to ${text}`)
onRename(text);
}
};
@@ -52,6 +57,7 @@ const RenameInput: React.FC = ({ value, onRename, checkDuplica
if (e.key === "Enter" && !isDuplicate) {
setIsEditing(false);
if (onRename) {
+ echo.info(`Selected Object has been renamed to ${text}`)
onRename(text);
}
}
@@ -80,4 +86,4 @@ const RenameInput: React.FC = ({ value, onRename, checkDuplica
>
);
};
-export default RenameInput
+export default RenameInput;
diff --git a/app/src/components/ui/inputs/Search.tsx b/app/src/components/ui/inputs/Search.tsx
index ff9c6ef..73db02a 100644
--- a/app/src/components/ui/inputs/Search.tsx
+++ b/app/src/components/ui/inputs/Search.tsx
@@ -1,8 +1,8 @@
-import React, { ChangeEvent, useState } from "react";
+import React, { ChangeEvent, useEffect, useState } from "react";
import { CloseIcon, SearchIcon } from "../../icons/ExportCommonIcons";
interface SearchProps {
- value?: string; // The current value of the search input
+ value?: string | null; // The current value of the search input
placeholder?: string; // Placeholder text for the input
onChange: (value: string) => void; // Callback function to handle input changes
}
@@ -22,7 +22,15 @@ const Search: React.FC = ({
onChange(newValue); // Call the onChange prop with the new value
};
+ useEffect(() => {
+ if (value === null) {
+ setInputValue("");
+ handleBlur();
+ }
+ }, [value]);
+
const handleClear = () => {
+ echo.warn("Search field cleared.");
setInputValue("");
onChange(""); // Clear the input value
};
@@ -48,7 +56,7 @@ const Search: React.FC = ({
) => {
- const { camera } = useThree();
-
- useEffect(() => {
- const handleKeyDown = (e: KeyboardEvent) => {
- if (!controlsRef.current) return;
-
- // get current distance from camera to target
- const target = new THREE.Vector3();
- controlsRef.current.getTarget(target);
-
- const distance = camera.position.distanceTo(target);
- let pos: THREE.Vector3 | null = null;
-
- switch (e.key) {
- case "1": // Front
- pos = new THREE.Vector3(0, 0, distance).add(target);
- break;
- case "3": // Right
- pos = new THREE.Vector3(distance, 0, 0).add(target);
- break;
- case "7": // Top
- pos = new THREE.Vector3(0, distance, 0).add(target);
- break;
- case "9": // Back
- pos = new THREE.Vector3(0, 0, -distance).add(target);
- break;
- }
-
- if (pos) {
- controlsRef.current.setLookAt(
- pos.x, pos.y, pos.z, // camera position
- target.x, target.y, target.z, // keep same target
- true // smooth transition
- );
- }
- };
-
- window.addEventListener("keydown", handleKeyDown);
- return () => window.removeEventListener("keydown", handleKeyDown);
- }, [controlsRef, camera]);
-};
diff --git a/app/src/modules/builder/Decal/decal.tsx b/app/src/modules/builder/Decal/decal.tsx
new file mode 100644
index 0000000..d14e150
--- /dev/null
+++ b/app/src/modules/builder/Decal/decal.tsx
@@ -0,0 +1,14 @@
+import DecalCreator from './decalCreator/decalCreator'
+
+function Decal() {
+
+ return (
+ <>
+
+
+
+ >
+ )
+}
+
+export default Decal
\ No newline at end of file
diff --git a/app/src/modules/builder/Decal/decalCreator/decalCreator.tsx b/app/src/modules/builder/Decal/decalCreator/decalCreator.tsx
new file mode 100644
index 0000000..44e46e3
--- /dev/null
+++ b/app/src/modules/builder/Decal/decalCreator/decalCreator.tsx
@@ -0,0 +1,152 @@
+import { MathUtils } from 'three';
+import { useEffect } from 'react';
+import { useThree } from '@react-three/fiber';
+import { useParams } from 'react-router-dom';
+import { useDroppedDecal, useSocketStore } from '../../../../store/builder/store';
+import useModuleStore from '../../../../store/useModuleStore';
+import { useSceneContext } from '../../../scene/sceneContext';
+import { useVersionContext } from '../../version/versionContext';
+
+import { getUserData } from '../../../../functions/getUserData';
+
+// import { upsertWallApi } from '../../../../services/factoryBuilder/wall/upsertWallApi';
+// import { upsertFloorApi } from '../../../../services/factoryBuilder/floor/upsertFloorApi';
+
+function DecalCreator() {
+ const { wallStore, floorStore } = useSceneContext();
+ const { addDecal: addDecalOnWall, getWallById } = wallStore();
+ const { addDecal: addDecalOnFloor, getFloorById } = floorStore();
+ const { droppedDecal } = useDroppedDecal();
+ const { activeModule } = useModuleStore();
+ const { userId, organization } = getUserData();
+ const { selectedVersionStore } = useVersionContext();
+ const { selectedVersion } = selectedVersionStore();
+ const { projectId } = useParams();
+ const { socket } = useSocketStore();
+ const { controls, gl, pointer, camera, raycaster, scene } = useThree();
+
+ useEffect(() => {
+ const canvasElement = gl.domElement;
+
+ const onDrop = (event: DragEvent) => {
+ if (droppedDecal) {
+ 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 wallIntersect = intersects.find(i => i.object.userData && i.object.userData.wallUuid);
+ const floorIntersect = intersects.find(i => i.object.userData && i.object.userData.floorUuid);
+ console.log('wallIntersect: ', wallIntersect);
+
+ if (wallIntersect) {
+ const wall = getWallById(wallIntersect.object.userData.wallUuid);
+ if (!wall) return;
+
+ const point = wallIntersect.object.worldToLocal(wallIntersect.point.clone());
+
+ const decal: Decal = {
+ decalUuid: MathUtils.generateUUID(),
+ decalName: droppedDecal.decalName,
+ decalId: droppedDecal.decalId,
+ decalType: {
+ type: 'Wall',
+ wallUuid: wallIntersect.object.userData.wallUuid,
+ },
+ decalPosition: [point.x, point.y, (wall.wallThickness / 2 + 0.001) * (wallIntersect.normal?.z || 1)],
+ decalRotation: 0,
+ decalOpacity: 1,
+ decalScale: 0.5,
+ }
+
+ addDecalOnWall(wallIntersect.object.userData.wallUuid, decal);
+
+ setTimeout(() => {
+ const updatedWall = getWallById(wallIntersect.object.userData.wallUuid);
+ if (updatedWall) {
+ if (projectId && updatedWall) {
+ // API
+
+ // upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall);
+
+ // SOCKET
+
+ const data = {
+ wallData: updatedWall,
+ projectId: projectId,
+ versionId: selectedVersion?.versionId || '',
+ userId: userId,
+ organization: organization
+ }
+
+ socket.emit('v1:model-Wall:add', data);
+ }
+ }
+ }, 0)
+ } else if (floorIntersect) {
+ const floor = getFloorById(floorIntersect.object.userData.floorUuid);
+ if (!floor) return;
+
+ const point = floorIntersect.object.worldToLocal(floorIntersect.point.clone());
+
+ const decal: Decal = {
+ decalUuid: MathUtils.generateUUID(),
+ decalName: droppedDecal.decalName,
+ decalId: droppedDecal.decalId,
+ decalType: {
+ type: 'Floor',
+ floorUuid: floorIntersect.object.userData.floorUuid,
+ },
+ decalPosition: [point.x, point.y, -0.001],
+ decalRotation: 0,
+ decalOpacity: 1,
+ decalScale: 0.5,
+ }
+
+ addDecalOnFloor(floorIntersect.object.userData.floorUuid, decal);
+
+ setTimeout(() => {
+ const updatedFloor = getFloorById(floorIntersect.object.userData.floorUuid);
+ if (projectId && updatedFloor) {
+ // API
+
+ // upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedFloor);
+
+ // SOCKET
+
+ const data = {
+ floorData: updatedFloor,
+ projectId: projectId,
+ versionId: selectedVersion?.versionId || '',
+ userId: userId,
+ organization: organization
+ }
+
+ socket.emit('v1:model-Floor:add', data);
+ }
+ }, 0)
+ }
+ }
+ };
+
+ const onDragOver = (event: any) => {
+ event.preventDefault();
+ };
+
+ if (activeModule === "builder") {
+ canvasElement.addEventListener("drop", onDrop);
+ canvasElement.addEventListener("dragover", onDragOver);
+ }
+
+ return () => {
+ canvasElement.removeEventListener("drop", onDrop);
+ canvasElement.removeEventListener("dragover", onDragOver);
+ };
+ }, [droppedDecal, camera, activeModule, controls]);
+
+ return (
+ <>
+ >
+ )
+}
+
+export default DecalCreator
\ No newline at end of file
diff --git a/app/src/modules/builder/Decal/decalInstance.tsx b/app/src/modules/builder/Decal/decalInstance.tsx
deleted file mode 100644
index 1b68f3d..0000000
--- a/app/src/modules/builder/Decal/decalInstance.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import * as THREE from 'three';
-import { Decal } from '@react-three/drei'
-import { useLoader } from '@react-three/fiber';
-import { useToggleView } from '../../../store/builder/store';
-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, zPosition = decal.decalPosition[2] }: { visible?: boolean, decal: Decal, zPosition?: number }) {
- const { setSelectedWall, setSelectedFloor, selectedDecal, setSelectedDecal } = useBuilderStore();
- const { togglView } = useToggleView();
- const { activeModule } = useModuleStore();
- const material = useLoader(THREE.TextureLoader, defaultMaterial);
-
- return (
- {
- if (visible && !togglView && activeModule === 'builder') {
- if (e.object.userData.decalUuid) {
- e.stopPropagation();
- setSelectedDecal(e.object);
- setSelectedWall(null);
- setSelectedFloor(null);
- }
- }
- }}
- onPointerMissed={() => {
- if (selectedDecal && selectedDecal.userData.decalUuid === decal.decalUuid) {
- setSelectedDecal(null);
- }
- }}
- >
-
-
- )
-}
-
-export default DecalInstance
\ No newline at end of file
diff --git a/app/src/modules/builder/Decal/decalInstance/decalInstance.tsx b/app/src/modules/builder/Decal/decalInstance/decalInstance.tsx
new file mode 100644
index 0000000..f31dddd
--- /dev/null
+++ b/app/src/modules/builder/Decal/decalInstance/decalInstance.tsx
@@ -0,0 +1,172 @@
+import * as THREE from 'three';
+import { Decal } from '@react-three/drei'
+import { useToggleView, useToolMode } from '../../../../store/builder/store';
+import { useBuilderStore } from '../../../../store/builder/useBuilderStore';
+import { retrieveImage, storeImage } from '../../../../utils/indexDB/idbUtils';
+
+import defaultMaterial from '../../../../assets/image/fallback/fallback decal 1.png';
+import useModuleStore from '../../../../store/useModuleStore';
+import { useEffect, useRef, useState } from 'react';
+
+import { useDecalEventHandlers } from '../eventHandler/useDecalEventHandlers';
+
+// import { upsertWallApi } from '../../../../services/factoryBuilder/wall/upsertWallApi';
+// import { upsertFloorApi } from '../../../../services/factoryBuilder/floor/upsertFloorApi';
+
+function DecalInstance({ parent, visible = true, decal, zPosition = decal.decalPosition[2] }: { parent: Wall | Floor; visible?: boolean, decal: Decal, zPosition?: number }) {
+ const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
+ const { selectedDecal, deletableDecal, setSelectedDecal, setDeletableDecal } = useBuilderStore();
+ const { toolMode } = useToolMode();
+ const { toggleView } = useToggleView();
+ const { activeModule } = useModuleStore();
+ const decalRef = useRef(null);
+
+ useEffect(() => {
+ if (selectedDecal?.decalData.decalUuid === decal.decalUuid && !selectedDecal.decalMesh) {
+ setSelectedDecal({ decalData: selectedDecal.decalData, decalMesh: decalRef.current });
+ }
+ }, [selectedDecal])
+
+ const { handlePointerMissed, handlePointerLeave, handleClick, handlePointerDown, handlePointerEnter } = useDecalEventHandlers({ parent, decal, visible });
+
+ const [texture, setTexture] = useState(null);
+
+ const logDecalStatus = (decalId: string, status: string) => {
+ // console.log(decalId, status);
+ }
+
+ const loadDefaultTexture = () => {
+ const textureLoader = new THREE.TextureLoader();
+ textureLoader.load(
+ defaultMaterial,
+ (fallbackTex) => {
+ fallbackTex.name = "default-decal";
+ setTexture(fallbackTex);
+ logDecalStatus(decal.decalId, 'default-loaded');
+ },
+ undefined,
+ (error) => {
+ console.error("Error loading default decal texture:", error);
+ }
+ );
+ };
+
+ const loadDecalTexture = async (decalId: string) => {
+
+ try {
+ const cachedTexture = THREE.Cache.get(decalId);
+ if (cachedTexture) {
+ setTexture(cachedTexture);
+ logDecalStatus(decalId, 'cache-loaded');
+ return;
+ }
+
+ const indexedDBTexture = await retrieveImage(decalId);
+ if (indexedDBTexture) {
+ const blobUrl = URL.createObjectURL(indexedDBTexture);
+ const textureLoader = new THREE.TextureLoader();
+ textureLoader.load(
+ blobUrl,
+ (tex) => {
+ URL.revokeObjectURL(blobUrl);
+ tex.name = decalId;
+ THREE.Cache.add(decalId, tex);
+ setTexture(tex);
+ logDecalStatus(decalId, 'indexedDB-loaded');
+ },
+ undefined,
+ (error) => {
+ console.error(`Error loading texture from IndexedDB:`, error);
+ URL.revokeObjectURL(blobUrl);
+ loadFromBackend(decalId);
+ }
+ );
+ return;
+ }
+
+ loadFromBackend(decalId);
+ } catch (error) {
+ console.error("Error loading decal texture:", error);
+ loadDefaultTexture();
+ }
+ };
+
+ const loadFromBackend = (decalId: string) => {
+
+ const textureUrl = `${url_Backend_dwinzo}/api/v1/DecalImage/${decalId}`;
+ const textureLoader = new THREE.TextureLoader();
+
+ textureLoader.load(
+ textureUrl,
+ async (tex) => {
+ tex.name = decalId;
+ THREE.Cache.add(decalId, tex);
+ setTexture(tex);
+ logDecalStatus(decalId, 'backend-loaded');
+
+ try {
+ const response = await fetch(textureUrl);
+ const blob = await response.blob();
+ await storeImage(decalId, blob);
+ } catch (error) {
+ console.error("Error storing texture in IndexedDB:", error);
+ }
+ },
+ undefined,
+ (error) => {
+ echo.error(`Error loading texture from backend: ${decal.decalName}`);
+ loadDefaultTexture();
+ }
+ );
+ };
+
+ useEffect(() => {
+ if (decal.decalId) {
+ loadDecalTexture(decal.decalId);
+ } else {
+ loadDefaultTexture();
+ }
+ }, [decal.decalId]);
+
+ useEffect(() => {
+ if (!toggleView && activeModule === 'builder') {
+ if (toolMode !== 'cursor') {
+ if (selectedDecal) setSelectedDecal(null);
+ }
+ if (toolMode !== '3D-Delete') {
+ if (deletableDecal) setDeletableDecal(null);
+ }
+ } else {
+ if (selectedDecal) setSelectedDecal(null);
+ if (deletableDecal) setDeletableDecal(null);
+ }
+ }, [toggleView, toolMode, activeModule, selectedDecal, deletableDecal]);
+
+ return (
+ { if (e.button === 0) handlePointerDown(e) }}
+ onClick={(e) => { handleClick(e) }}
+ onPointerEnter={(e) => { handlePointerEnter(e) }}
+ onPointerLeave={(e) => { handlePointerLeave(e) }}
+ onPointerMissed={() => handlePointerMissed()}
+ >
+
+
+ )
+}
+
+export default DecalInstance
\ No newline at end of file
diff --git a/app/src/modules/builder/Decal/eventHandler/useDecalEventHandlers.ts b/app/src/modules/builder/Decal/eventHandler/useDecalEventHandlers.ts
new file mode 100644
index 0000000..c0c5161
--- /dev/null
+++ b/app/src/modules/builder/Decal/eventHandler/useDecalEventHandlers.ts
@@ -0,0 +1,301 @@
+import * as THREE from 'three';
+import { CameraControls } from '@react-three/drei';
+import { ThreeEvent, useFrame, useThree } from '@react-three/fiber';
+import { useEffect, useRef } from 'react';
+import { useSocketStore, useToggleView, useToolMode } from '../../../../store/builder/store';
+import { useBuilderStore } from '../../../../store/builder/useBuilderStore';
+import useModuleStore from '../../../../store/useModuleStore';
+import { getUserData } from '../../../../functions/getUserData';
+import { useVersionContext } from '../../version/versionContext';
+import { useParams } from 'react-router-dom';
+import { useSceneContext } from '../../../scene/sceneContext';
+
+// import { upsertWallApi } from '../../../../services/factoryBuilder/wall/upsertWallApi';
+// import { upsertFloorApi } from '../../../../services/factoryBuilder/floor/upsertFloorApi';
+
+export function useDecalEventHandlers({
+ parent,
+ decal,
+ visible,
+}: {
+ parent: Wall | Floor;
+ decal: Decal;
+ visible: boolean;
+}) {
+ const { wallStore, floorStore } = useSceneContext();
+ const { removeDecal: removeDecalInWall, updateDecalPosition: updateDecalPositionInWall, getWallById, addDecal: addDecalToWall } = wallStore();
+ const { removeDecal: removeDecalInFloor, updateDecalPosition: updateDecalPositionInFloor, getFloorById, addDecal: addDecalToFloor } = floorStore();
+ const { setSelectedWall, setSelectedFloor, setSelectedDecal, setDeletableDecal, deletableDecal, selectedDecal, setDecalDragState, decalDragState } = useBuilderStore();
+ const { toolMode } = useToolMode();
+ const { toggleView } = useToggleView();
+ const { activeModule } = useModuleStore();
+ const { userId, organization } = getUserData();
+ const { selectedVersionStore } = useVersionContext();
+ const { selectedVersion } = selectedVersionStore();
+ const { projectId } = useParams();
+ const { socket } = useSocketStore();
+ const { raycaster, pointer, camera, scene, gl, controls } = useThree();
+
+ useFrame(() => {
+ if (activeModule !== 'builder' || toggleView || !decalDragState.isDragging || !selectedDecal || selectedDecal.decalData.decalUuid !== decal.decalUuid) return;
+
+ raycaster.setFromCamera(pointer, camera);
+ const intersects = raycaster.intersectObjects(scene.children, true);
+
+ const wallIntersect = intersects.find(i => i.object.userData?.wallUuid);
+ const floorIntersect = intersects.find(i => i.object.userData?.floorUuid);
+
+ let offset = decalDragState.dragOffset || new THREE.Vector3(0, 0, 0);
+
+ if (wallIntersect) {
+ const wallUuid = wallIntersect.object.userData.wallUuid;
+ const point = wallIntersect.object.worldToLocal(wallIntersect.point.clone());
+
+ if ("wallUuid" in parent && parent.wallUuid === wallUuid && decal.decalType.type === 'Wall') {
+ updateDecalPositionInWall(decal.decalUuid, [point.x + offset.x, point.y + offset.y, decal.decalPosition[2]]);
+ } else if (decal.decalType.type === 'Wall' && wallUuid) {
+ deleteDecal(decal.decalUuid, parent);
+
+ const addedDecal = addDecalToWall(wallUuid, {
+ ...decal,
+ decalPosition: [point.x + offset.x, point.y + offset.y, decal.decalPosition[2]],
+ decalType: { type: 'Wall', wallUuid: wallUuid }
+ });
+
+ if (addedDecal) {
+ setSelectedDecal({ decalMesh: null, decalData: addedDecal })
+ }
+ } else if (decal.decalType.type === 'Floor' && wallUuid) {
+ deleteDecal(decal.decalUuid, parent);
+ const wall = getWallById(wallUuid);
+ if (!wall) return;
+
+ const addedDecal = addDecalToWall(wallUuid, {
+ ...decal,
+ decalPosition: [point.x + offset.x, point.y + offset.y, wall.wallThickness / 2 + 0.001],
+ decalType: { type: 'Wall', wallUuid: wallUuid }
+ });
+
+ if (addedDecal) {
+ setSelectedDecal({ decalMesh: null, decalData: addedDecal })
+ }
+ }
+ } else if (floorIntersect) {
+ const floorUuid = floorIntersect.object.userData.floorUuid;
+ const point = floorIntersect.object.worldToLocal(floorIntersect.point.clone());
+
+ if ("floorUuid" in parent && parent.floorUuid === floorUuid && decal.decalType.type === 'Floor') {
+ updateDecalPositionInFloor(decal.decalUuid, [point.x + offset.x, point.y + offset.y, decal.decalPosition[2]]);
+ } else if (decal.decalType.type === 'Floor' && floorUuid) {
+ deleteDecal(decal.decalUuid, parent);
+
+ const addedDecal = addDecalToFloor(floorUuid, {
+ ...decal,
+ decalPosition: [point.x + offset.x, point.y + offset.y, decal.decalPosition[2]],
+ decalType: { type: 'Floor', floorUuid: floorUuid }
+ });
+
+ if (addedDecal) {
+ setSelectedDecal({ decalMesh: null, decalData: addedDecal })
+ }
+ } else if (decal.decalType.type === 'Wall' && floorUuid) {
+ deleteDecal(decal.decalUuid, parent);
+ const floor = getFloorById(floorUuid);
+ if (!floor) return;
+
+ const addedDecal = addDecalToFloor(floorUuid, {
+ ...decal,
+ decalPosition: [point.x + offset.x, point.y + offset.y, -0.001],
+ decalType: { type: 'Floor', floorUuid: floorUuid }
+ });
+
+ if (addedDecal) {
+ setSelectedDecal({ decalMesh: null, decalData: addedDecal })
+ }
+ }
+ }
+ });
+
+ const handlePointerUp = (e: PointerEvent) => {
+ if (controls) {
+ (controls as CameraControls).enabled = true;
+ }
+ if (decalDragState.isDragging) {
+ setDecalDragState(false, null, null);
+
+ if ('wallUuid' in parent) {
+ setTimeout(() => {
+ const updatedWall = getWallById(parent.wallUuid);
+ if (updatedWall) {
+ if (projectId && updatedWall) {
+ // API
+
+ // upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall);
+
+ // SOCKET
+
+ const data = {
+ wallData: updatedWall,
+ projectId: projectId,
+ versionId: selectedVersion?.versionId || '',
+ userId: userId,
+ organization: organization
+ }
+
+ socket.emit('v1:model-Wall:add', data);
+ }
+ }
+ }, 0)
+ } else if ('floorUuid' in parent) {
+ setTimeout(() => {
+ const updatedFloor = parent;
+
+ if (projectId && updatedFloor) {
+ // API
+
+ // upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedFloor);
+
+ // SOCKET
+
+ const data = {
+ floorData: updatedFloor,
+ projectId: projectId,
+ versionId: selectedVersion?.versionId || '',
+ userId: userId,
+ organization: organization
+ }
+
+ socket.emit('v1:model-Floor:add', data);
+ }
+ }, 0)
+ }
+ }
+ };
+
+ const deleteDecal = (decalUuid: string, parent: Wall | Floor) => {
+ if ('wallUuid' in parent) {
+ const updatedWall = removeDecalInWall(decalUuid);
+
+ if (projectId && updatedWall) {
+ // API
+
+ // upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall);
+
+ // SOCKET
+
+ const data = {
+ wallData: updatedWall,
+ projectId: projectId,
+ versionId: selectedVersion?.versionId || '',
+ userId: userId,
+ organization: organization
+ }
+
+ socket.emit('v1:model-Wall:add', data);
+ }
+ } else if ('floorUuid' in parent) {
+ const updatedFloor = removeDecalInFloor(decalUuid);
+
+ if (projectId && updatedFloor) {
+ // API
+
+ // upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedFloor);
+
+ // SOCKET
+
+ const data = {
+ floorData: updatedFloor,
+ projectId: projectId,
+ versionId: selectedVersion?.versionId || '',
+ userId: userId,
+ organization: organization
+ }
+
+ socket.emit('v1:model-Floor:add', data);
+ }
+ }
+ }
+
+ const handlePointerDown = (e: ThreeEvent) => {
+ if (visible && !toggleView && activeModule === 'builder') {
+ if (e.object.userData.decalUuid && toolMode === 'cursor') {
+ e.stopPropagation();
+ setDecalDragState(true, decal.decalUuid, null);
+ if (controls) {
+ (controls as CameraControls).enabled = false;
+ }
+ setSelectedDecal({ decalMesh: e.object, decalData: decal });
+ setSelectedWall(null);
+ setSelectedFloor(null);
+
+ const localIntersect = e.object.worldToLocal(e.point.clone());
+ let dragOffset = new THREE.Vector3(decal.decalPosition[0] - localIntersect.x, decal.decalPosition[1] - localIntersect.y, 0);
+ setDecalDragState(true, decal.decalUuid, dragOffset);
+ }
+ }
+ };
+
+ const handleClick = (e: ThreeEvent) => {
+ if (visible && !toggleView && activeModule === 'builder') {
+ if (e.object.userData.decalUuid) {
+ e.stopPropagation();
+ if (toolMode === 'cursor') {
+ setSelectedDecal({ decalMesh: e.object, decalData: decal });
+ setSelectedWall(null);
+ setSelectedFloor(null);
+ } else if (toolMode === '3D-Delete') {
+ deleteDecal(e.object.userData.decalUuid, parent);
+ }
+ }
+ }
+ };
+
+ const handlePointerEnter = (e: ThreeEvent) => {
+ if (visible && !toggleView && activeModule === 'builder') {
+ if (e.object.userData.decalUuid) {
+ e.stopPropagation();
+ if (toolMode === '3D-Delete') {
+ setDeletableDecal(e.object);
+ }
+ }
+ }
+ };
+
+ const handlePointerLeave = (e: ThreeEvent) => {
+ if (visible && !toggleView && activeModule === 'builder') {
+ if (e.object.userData.decalUuid) {
+ e.stopPropagation();
+ if (toolMode === '3D-Delete' && deletableDecal && deletableDecal?.userData.decalUuid === e.object.userData.decalUuid) {
+ setDeletableDecal(null);
+ }
+ }
+ }
+ };
+
+ const handlePointerMissed = () => {
+ if (selectedDecal && selectedDecal.decalMesh && selectedDecal.decalMesh.userData.decalUuid === decal.decalUuid) {
+ setSelectedDecal(null);
+ }
+ };
+
+ useEffect(() => {
+ const canvasElement = gl.domElement;
+
+ if (activeModule === 'builder' && !toggleView && selectedDecal && selectedDecal.decalData.decalUuid === decal.decalUuid) {
+ canvasElement.addEventListener('pointerup', handlePointerUp);
+ }
+
+ return () => {
+ canvasElement.removeEventListener('pointerup', handlePointerUp);
+ };
+ }, [gl, activeModule, toggleView, selectedDecal, camera, controls, visible, parent, decal, decalDragState]);
+
+ return {
+ handlePointerDown,
+ handleClick,
+ handlePointerEnter,
+ handlePointerLeave,
+ handlePointerMissed,
+ deleteDecal
+ };
+}
\ No newline at end of file
diff --git a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/arcAisle.tsx b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/arcAisle.tsx
index bc09b68..d791f8f 100644
--- a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/arcAisle.tsx
+++ b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/arcAisle.tsx
@@ -1,5 +1,5 @@
import * as THREE from 'three';
-import { useMemo, useRef } from 'react';
+import { useEffect, useMemo, useRef } from 'react';
import { Extrude } from '@react-three/drei';
import * as Constants from '../../../../../../types/world/worldConstants';
import { useToolMode } from '../../../../../../store/builder/store';
@@ -8,7 +8,13 @@ import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore
function ArcAisle({ aisle }: { readonly aisle: Aisle }) {
const aisleRef = useRef(null);
const { toolMode } = useToolMode();
- const { setSelectedAisle, hoveredPoint } = useBuilderStore();
+ const { setSelectedAisle, hoveredPoint, selectedAisle } = useBuilderStore();
+
+ useEffect(() => {
+ if (selectedAisle?.aisleData.aisleUuid === aisle.aisleUuid && !selectedAisle.aisleMesh) {
+ setSelectedAisle({ aisleData: selectedAisle.aisleData, aisleMesh: aisleRef.current });
+ }
+ }, [selectedAisle])
const arc = useMemo(() => {
if (aisle.points.length < 2 || aisle.type.aisleType !== 'arc-aisle') return null;
@@ -63,8 +69,8 @@ function ArcAisle({ aisle }: { readonly aisle: Aisle }) {
}, [aisle]);
const handleClick = () => {
- if (toolMode === 'move' && !hoveredPoint) {
- setSelectedAisle(aisleRef.current);
+ if ((toolMode === 'move' || toolMode === 'cursor') && !hoveredPoint) {
+ setSelectedAisle({ aisleMesh: aisleRef.current, aisleData: aisle });
}
}
diff --git a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/arrowAisle.tsx b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/arrowAisle.tsx
index 49bf666..115edc6 100644
--- a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/arrowAisle.tsx
+++ b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/arrowAisle.tsx
@@ -1,5 +1,5 @@
import * as THREE from 'three';
-import { useMemo, useRef } from 'react';
+import { useEffect, useMemo, useRef } from 'react';
import { Extrude } from '@react-three/drei';
import * as Constants from '../../../../../../types/world/worldConstants';
import { useToolMode } from '../../../../../../store/builder/store';
@@ -8,7 +8,13 @@ import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore
function ArrowAisle({ aisle }: { readonly aisle: Aisle }) {
const aisleRef = useRef(null);
const { toolMode } = useToolMode();
- const { setSelectedAisle, hoveredPoint } = useBuilderStore();
+ const { setSelectedAisle, hoveredPoint, selectedAisle } = useBuilderStore();
+
+ useEffect(() => {
+ if (selectedAisle?.aisleData.aisleUuid === aisle.aisleUuid && !selectedAisle.aisleMesh) {
+ setSelectedAisle({ aisleData: selectedAisle.aisleData, aisleMesh: aisleRef.current });
+ }
+ }, [selectedAisle])
const arrow = useMemo(() => {
if (aisle.points.length < 2 || aisle.type.aisleType !== 'arrow-aisle') return null;
@@ -50,8 +56,8 @@ function ArrowAisle({ aisle }: { readonly aisle: Aisle }) {
}, [aisle]);
const handleClick = () => {
- if (toolMode === 'move' && !hoveredPoint) {
- setSelectedAisle(aisleRef.current);
+ if ((toolMode === 'move' || toolMode === 'cursor') && !hoveredPoint) {
+ setSelectedAisle({ aisleMesh: aisleRef.current, aisleData: aisle });
}
}
diff --git a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/arrowsAisle.tsx b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/arrowsAisle.tsx
index 6908cf3..3700d55 100644
--- a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/arrowsAisle.tsx
+++ b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/arrowsAisle.tsx
@@ -1,5 +1,5 @@
import * as THREE from 'three';
-import { useMemo, useRef } from 'react';
+import { useEffect, useMemo, useRef } from 'react';
import { Instances, Instance } from '@react-three/drei';
import * as Constants from '../../../../../../types/world/worldConstants';
import { useToolMode } from '../../../../../../store/builder/store';
@@ -8,7 +8,13 @@ import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore
function ArrowsAisle({ aisle }: { readonly aisle: Aisle }) {
const aisleRef = useRef(null);
const { toolMode } = useToolMode();
- const { setSelectedAisle, hoveredPoint } = useBuilderStore();
+ const { setSelectedAisle, hoveredPoint, selectedAisle } = useBuilderStore();
+
+ useEffect(() => {
+ if (selectedAisle?.aisleData.aisleUuid === aisle.aisleUuid && !selectedAisle.aisleMesh) {
+ setSelectedAisle({ aisleData: selectedAisle.aisleData, aisleMesh: aisleRef.current });
+ }
+ }, [selectedAisle])
const { arrowGeometry, arrowInstances } = useMemo(() => {
const result = {
@@ -68,8 +74,8 @@ function ArrowsAisle({ aisle }: { readonly aisle: Aisle }) {
}, [aisle]);
const handleClick = () => {
- if (toolMode === 'move' && !hoveredPoint) {
- setSelectedAisle(aisleRef.current);
+ if ((toolMode === 'move' || toolMode === 'cursor') && !hoveredPoint) {
+ setSelectedAisle({ aisleMesh: aisleRef.current, aisleData: aisle });
}
};
diff --git a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/circleAisle.tsx b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/circleAisle.tsx
index af852e8..d9bc2d2 100644
--- a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/circleAisle.tsx
+++ b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/circleAisle.tsx
@@ -1,5 +1,5 @@
import * as THREE from 'three';
-import { useMemo, useRef } from 'react';
+import { useEffect, useMemo, useRef } from 'react';
import { Extrude } from '@react-three/drei';
import * as Constants from '../../../../../../types/world/worldConstants';
import { useToolMode } from '../../../../../../store/builder/store';
@@ -8,7 +8,13 @@ import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore
function CircleAisle({ aisle }: { readonly aisle: Aisle }) {
const aisleRef = useRef(null);
const { toolMode } = useToolMode();
- const { setSelectedAisle, hoveredPoint } = useBuilderStore();
+ const { setSelectedAisle, hoveredPoint, selectedAisle } = useBuilderStore();
+
+ useEffect(() => {
+ if (selectedAisle?.aisleData.aisleUuid === aisle.aisleUuid && !selectedAisle.aisleMesh) {
+ setSelectedAisle({ aisleData: selectedAisle.aisleData, aisleMesh: aisleRef.current });
+ }
+ }, [selectedAisle])
const circle = useMemo(() => {
if (aisle.points.length < 2 || aisle.type.aisleType !== 'circle-aisle') return null;
@@ -38,8 +44,8 @@ function CircleAisle({ aisle }: { readonly aisle: Aisle }) {
}, [aisle]);
const handleClick = () => {
- if (toolMode === 'move' && !hoveredPoint) {
- setSelectedAisle(aisleRef.current);
+ if ((toolMode === 'move' || toolMode === 'cursor') && !hoveredPoint) {
+ setSelectedAisle({ aisleMesh: aisleRef.current, aisleData: aisle });
}
}
diff --git a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/dashedAisle.tsx b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/dashedAisle.tsx
index 72fb3c7..b00c87b 100644
--- a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/dashedAisle.tsx
+++ b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/dashedAisle.tsx
@@ -1,5 +1,5 @@
import * as THREE from 'three';
-import { useMemo, useRef } from 'react';
+import { useEffect, useMemo, useRef } from 'react';
import { Instances, Instance } from '@react-three/drei';
import * as Constants from '../../../../../../types/world/worldConstants';
import { useToolMode } from '../../../../../../store/builder/store';
@@ -8,7 +8,13 @@ import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore
function DashedAisle({ aisle }: { readonly aisle: Aisle }) {
const aisleRef = useRef(null);
const { toolMode } = useToolMode();
- const { setSelectedAisle, hoveredPoint } = useBuilderStore();
+ const { setSelectedAisle, hoveredPoint, selectedAisle } = useBuilderStore();
+
+ useEffect(() => {
+ if (selectedAisle?.aisleData.aisleUuid === aisle.aisleUuid && !selectedAisle.aisleMesh) {
+ setSelectedAisle({ aisleData: selectedAisle.aisleData, aisleMesh: aisleRef.current });
+ }
+ }, [selectedAisle])
const dashInstances = useMemo(() => {
if (aisle.points.length < 2 || aisle.type.aisleType !== 'dashed-aisle') return [];
@@ -43,8 +49,8 @@ function DashedAisle({ aisle }: { readonly aisle: Aisle }) {
}, [aisle]);
const handleClick = () => {
- if (toolMode === 'move' && !hoveredPoint) {
- setSelectedAisle(aisleRef.current);
+ if ((toolMode === 'move' || toolMode === 'cursor') && !hoveredPoint) {
+ setSelectedAisle({ aisleMesh: aisleRef.current, aisleData: aisle });
}
};
diff --git a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/dottedAisle.tsx b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/dottedAisle.tsx
index e2b264a..09557aa 100644
--- a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/dottedAisle.tsx
+++ b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/dottedAisle.tsx
@@ -1,5 +1,5 @@
import * as THREE from 'three';
-import { useMemo, useRef } from 'react';
+import { useEffect, useMemo, useRef } from 'react';
import { Instance, Instances } from '@react-three/drei';
import * as Constants from '../../../../../../types/world/worldConstants';
import { useToolMode } from '../../../../../../store/builder/store';
@@ -8,7 +8,13 @@ import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore
function DottedAisle({ aisle }: { readonly aisle: Aisle }) {
const aisleRef = useRef(null);
const { toolMode } = useToolMode();
- const { setSelectedAisle, hoveredPoint } = useBuilderStore();
+ const { setSelectedAisle, hoveredPoint, selectedAisle } = useBuilderStore();
+
+ useEffect(() => {
+ if (selectedAisle?.aisleData.aisleUuid === aisle.aisleUuid && !selectedAisle.aisleMesh) {
+ setSelectedAisle({ aisleData: selectedAisle.aisleData, aisleMesh: aisleRef.current });
+ }
+ }, [selectedAisle])
const dotPositions = useMemo(() => {
if (aisle.points.length < 2 || aisle.type.aisleType !== 'dotted-aisle') return [];
@@ -27,8 +33,8 @@ function DottedAisle({ aisle }: { readonly aisle: Aisle }) {
}, [aisle]);
const handleClick = () => {
- if (toolMode === 'move' && !hoveredPoint) {
- setSelectedAisle(aisleRef.current);
+ if ((toolMode === 'move' || toolMode === 'cursor') && !hoveredPoint) {
+ setSelectedAisle({ aisleMesh: aisleRef.current, aisleData: aisle });
}
}
diff --git a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/junctionAisle.tsx b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/junctionAisle.tsx
index a3f8bdf..8d45763 100644
--- a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/junctionAisle.tsx
+++ b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/junctionAisle.tsx
@@ -1,5 +1,5 @@
import * as THREE from 'three';
-import { useMemo, useRef } from 'react';
+import { useEffect, useMemo, useRef } from 'react';
import { Extrude } from '@react-three/drei';
import * as Constants from '../../../../../../types/world/worldConstants';
import { useToolMode } from '../../../../../../store/builder/store';
@@ -8,7 +8,13 @@ import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore
function JunctionAisle({ aisle }: { readonly aisle: Aisle }) {
const aisleRef = useRef(null);
const { toolMode } = useToolMode();
- const { setSelectedAisle, hoveredPoint } = useBuilderStore();
+ const { setSelectedAisle, hoveredPoint, selectedAisle } = useBuilderStore();
+
+ useEffect(() => {
+ if (selectedAisle?.aisleData.aisleUuid === aisle.aisleUuid && !selectedAisle.aisleMesh) {
+ setSelectedAisle({ aisleData: selectedAisle.aisleData, aisleMesh: aisleRef.current });
+ }
+ }, [selectedAisle])
const arrows = useMemo(() => {
if (aisle.points.length < 2 || aisle.type.aisleType !== 'junction-aisle') return null;
@@ -85,8 +91,8 @@ function JunctionAisle({ aisle }: { readonly aisle: Aisle }) {
}, [aisle]);
const handleClick = () => {
- if (toolMode === 'move' && !hoveredPoint) {
- setSelectedAisle(aisleRef.current);
+ if ((toolMode === 'move' || toolMode === 'cursor') && !hoveredPoint) {
+ setSelectedAisle({ aisleMesh: aisleRef.current, aisleData: aisle });
}
}
diff --git a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/solidAisle.tsx b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/solidAisle.tsx
index e9321b0..e43f717 100644
--- a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/solidAisle.tsx
+++ b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/solidAisle.tsx
@@ -1,5 +1,5 @@
import * as THREE from 'three';
-import { useMemo, useRef } from 'react';
+import { useEffect, useMemo, useRef } from 'react';
import { Extrude } from '@react-three/drei';
import * as Constants from '../../../../../../types/world/worldConstants';
import { useToolMode } from '../../../../../../store/builder/store';
@@ -8,7 +8,14 @@ import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore
function SolidAisle({ aisle }: { readonly aisle: Aisle }) {
const aisleRef = useRef(null);
const { toolMode } = useToolMode();
- const { setSelectedAisle, hoveredPoint } = useBuilderStore();
+ const { setSelectedAisle, hoveredPoint, selectedAisle } = useBuilderStore();
+
+ useEffect(() => {
+ if (selectedAisle?.aisleData.aisleUuid === aisle.aisleUuid && !selectedAisle.aisleMesh) {
+ setSelectedAisle({ aisleData: selectedAisle.aisleData, aisleMesh: aisleRef.current });
+ }
+ }, [selectedAisle])
+
const shape = useMemo(() => {
if (aisle.points.length < 2 || aisle.type.aisleType !== 'solid-aisle') return null;
@@ -35,8 +42,8 @@ function SolidAisle({ aisle }: { readonly aisle: Aisle }) {
}, [aisle]);
const handleClick = () => {
- if (toolMode === 'move' && !hoveredPoint) {
- setSelectedAisle(aisleRef.current);
+ if ((toolMode === 'move' || toolMode === 'cursor') && !hoveredPoint) {
+ setSelectedAisle({ aisleMesh: aisleRef.current, aisleData: aisle });
}
}
diff --git a/app/src/modules/builder/asset/assetsGroup.tsx b/app/src/modules/builder/asset/assetsGroup.tsx
index 9d36efc..0205a4c 100644
--- a/app/src/modules/builder/asset/assetsGroup.tsx
+++ b/app/src/modules/builder/asset/assetsGroup.tsx
@@ -363,6 +363,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) {
setLeft(relativeX);
}
};
+
const onMouseUp = (evt: any) => {
setIsRenameMode(false);
}
diff --git a/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts b/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts
index 360c513..85b5b4b 100644
--- a/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts
+++ b/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts
@@ -34,7 +34,7 @@ export function useModelEventHandlers({
const { socket } = useSocketStore();
const { eventStore, productStore, assetStore, undoRedo3DStore } = useSceneContext();
const { push3D } = undoRedo3DStore();
- const { getAssetById, removeAsset } = assetStore();
+ const { removeAsset } = assetStore();
const { zoneAssetId, setZoneAssetId } = useZoneAssetId();
const { resourceManagementId, setResourceManagementId } = useResourceManagementId();
const { removeEvent, getEventByModelUuid } = eventStore();
@@ -77,11 +77,12 @@ export function useModelEventHandlers({
}
}, [zoneAssetId])
+
useEffect(() => {
if (!resourceManagementId) return
if (resourceManagementId === asset.modelUuid) {
-
-
+
+
handleDblClick(asset);
}
diff --git a/app/src/modules/builder/asset/models/model/model.tsx b/app/src/modules/builder/asset/models/model/model.tsx
index ecce40e..a7502db 100644
--- a/app/src/modules/builder/asset/models/model/model.tsx
+++ b/app/src/modules/builder/asset/models/model/model.tsx
@@ -89,6 +89,10 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere
}
}, [gltfScene]);
+ const logModelStatus = (modelId: string, status: string) => {
+ // console.log(modelId, status);
+ }
+
useEffect(() => {
// Calculate Bounding Box
const calculateBoundingBox = (scene: THREE.Object3D) => {
@@ -104,6 +108,7 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere
clone.animations = cachedModel.animations || [];
setGltfScene(clone);
calculateBoundingBox(clone);
+ logModelStatus(assetId, 'cache-loaded');
return;
}
@@ -119,6 +124,7 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere
THREE.Cache.add(assetId, gltf);
setGltfScene(gltf.scene.clone());
calculateBoundingBox(gltf.scene);
+ logModelStatus(assetId, 'indexedDB-loaded');
},
undefined,
(error) => {
@@ -141,6 +147,7 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere
THREE.Cache.add(assetId, gltf);
setGltfScene(gltf.scene.clone());
calculateBoundingBox(gltf.scene);
+ logModelStatus(assetId, 'backend-loaded');
})
.catch((error) => {
console.error(
diff --git a/app/src/modules/builder/builder.tsx b/app/src/modules/builder/builder.tsx
index ef310fc..d7b8d34 100644
--- a/app/src/modules/builder/builder.tsx
+++ b/app/src/modules/builder/builder.tsx
@@ -33,13 +33,14 @@ import AssetsGroup from "./asset/assetsGroup";
import DxfFile from "./dfx/LoadBlueprint";
import AislesGroup from "./aisle/aislesGroup";
import WallGroup from "./wall/wallGroup";
+import WallAssetGroup from "./wallAsset/wallAssetGroup";
import FloorGroup from "./floor/floorGroup";
import ZoneGroup from "./zone/zoneGroup";
+import Decal from "./Decal/decal";
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();
@@ -106,6 +107,8 @@ export default function Builder() {
+
+
diff --git a/app/src/modules/builder/floor/Instances/Instance/floorInstance.tsx b/app/src/modules/builder/floor/Instances/Instance/floorInstance.tsx
index aa60691..4ea5b43 100644
--- a/app/src/modules/builder/floor/Instances/Instance/floorInstance.tsx
+++ b/app/src/modules/builder/floor/Instances/Instance/floorInstance.tsx
@@ -1,12 +1,13 @@
import { useMemo } from "react";
-import { Shape, Vector2, DoubleSide, TextureLoader, RepeatWrapping, SRGBColorSpace, NoColorSpace, } from "three";
+import { Shape, Vector2, DoubleSide, TextureLoader, RepeatWrapping, SRGBColorSpace, NoColorSpace, ExtrudeGeometry, Vector3, Euler, } from "three";
import { useLoader } from "@react-three/fiber";
-import { Extrude } from "@react-three/drei";
import useModuleStore from "../../../../../store/useModuleStore";
import { useBuilderStore } from "../../../../../store/builder/useBuilderStore";
import { useToggleView } from "../../../../../store/builder/store";
import * as Constants from "../../../../../types/world/worldConstants";
+import DecalInstance from "../../../Decal/decalInstance/decalInstance";
+
import texturePath from "../../../../../assets/textures/floor/white.png";
import texturePathDark from "../../../../../assets/textures/floor/black.png";
import material1 from "../../../../../assets/textures/floor/factory wall texture.jpg";
@@ -28,7 +29,7 @@ import material4MetalicMap from "../../../../../assets/textures/floor/tex3/metal
import material4NormalMap from "../../../../../assets/textures/floor/tex3/metal_plate_nor_gl_1k.png";
function FloorInstance({ floor }: { floor: Floor }) {
- const { togglView } = useToggleView();
+ const { toggleView } = useToggleView();
const { activeModule } = useModuleStore();
const { selectedFloor, setSelectedFloor, setSelectedDecal } = useBuilderStore();
const savedTheme = localStorage.getItem("theme");
@@ -67,20 +68,26 @@ function FloorInstance({ floor }: { floor: Floor }) {
},
};
- const shape = useMemo(() => {
- const shape = new Shape();
+ const shapeData = useMemo(() => {
const points = floor.points.map((p) => new Vector2(p.position[0], p.position[2]));
if (points.length < 3) return null;
- shape.moveTo(points[0].x, points[0].y);
- for (let i = 1; i < points.length; i++) {
- shape.lineTo(points[i].x, points[i].y);
+
+ const centroidX = points.reduce((sum, p) => sum + p.x, 0) / points.length;
+ const centroidY = points.reduce((sum, p) => sum + p.y, 0) / points.length;
+
+ const relativePoints = points.map((p) => new Vector2(p.x - centroidX, p.y - centroidY));
+
+ const shape = new Shape();
+ shape.moveTo(relativePoints[0].x, relativePoints[0].y);
+ for (let i = 1; i < relativePoints.length; i++) {
+ shape.lineTo(relativePoints[i].x, relativePoints[i].y);
}
- return shape;
+
+ return { shape, center: [centroidX, centroidY] };
}, [floor]);
const textureScale = Constants.floorConfig.textureScale;
- // Helper function to handle texture maps and filter out null values
function getMaterialMaps(material: any, defaultMap: any) {
const materialMap = material.map || defaultMap;
const normalMap = material.normalMap || null;
@@ -90,26 +97,18 @@ function FloorInstance({ floor }: { floor: Floor }) {
return [materialMap, normalMap, roughnessMap, metalnessMap].filter((texture): texture is string => texture !== null);
}
- // Default material map
const defaultMaterialMap = materials["Default Material"].map;
- // Get top and side material maps
const topMaterial = materials[floor.topMaterial];
const sideMaterial = materials[floor.sideMaterial];
- // Get the filtered lists for top and side textures
const topTexturesList = getMaterialMaps(topMaterial, defaultMaterialMap);
const sideTexturesList = getMaterialMaps(sideMaterial, defaultMaterialMap);
- // Use loader to load top and side textures
const [topTexture, topNormalTexture, topRoughnessTexture, topMetalicTexture] = useLoader(TextureLoader, topTexturesList);
const [sideTexture, sideNormalTexture, sideRoughnessTexture, sideMetalicTexture] = useLoader(TextureLoader, sideTexturesList);
- // Early exit if materials are missing
- if (!materials[floor.topMaterial] || !materials[floor.sideMaterial]) return null;
-
- // Combine and pair textures with their corresponding material
const textureMaterialMap = [
{
textures: [
@@ -131,7 +130,6 @@ function FloorInstance({ floor }: { floor: Floor }) {
},
];
- // Apply texture settings
textureMaterialMap.forEach(({ textures, materialKey }) => {
const tileScale = materials[materialKey]?.textureTileScale ?? [
textureScale,
@@ -143,23 +141,39 @@ function FloorInstance({ floor }: { floor: Floor }) {
tex.wrapS = tex.wrapT = RepeatWrapping;
tex.repeat.set(tileScale[0], tileScale[1]);
tex.anisotropy = 16;
- // First texture is always the color map (use SRGB), others should be linear
tex.colorSpace = idx < 1 ? SRGBColorSpace : NoColorSpace;
});
});
- if (!shape) return null;
+ const geometry = useMemo(() => {
+ if (!shapeData) return null;
+ return new ExtrudeGeometry(shapeData.shape, {
+ depth: !floor.isBeveled ? floor.floorDepth : floor.floorDepth - 0.1,
+ bevelEnabled: floor.isBeveled,
+ bevelSegments: floor.bevelStrength,
+ bevelOffset: -0.1,
+ bevelSize: 0.1,
+ bevelThickness: 0.1,
+ });
+ }, [shapeData, floor]);
+
+ if (!geometry) return null;
return (
{
- if (!togglView && activeModule === "builder") {
+ if (!toggleView && activeModule === "builder") {
if (e.object.userData.floorUuid) {
e.stopPropagation();
setSelectedFloor(e.object);
@@ -173,41 +187,32 @@ function FloorInstance({ floor }: { floor: Floor }) {
}
}}
>
-
-
-
-
+
+
+
+ {floor.decals.map((decal) => (
+
+ ))}
);
}
diff --git a/app/src/modules/builder/floor/Instances/floorInstances.tsx b/app/src/modules/builder/floor/Instances/floorInstances.tsx
index a578800..2af5787 100644
--- a/app/src/modules/builder/floor/Instances/floorInstances.tsx
+++ b/app/src/modules/builder/floor/Instances/floorInstances.tsx
@@ -2,21 +2,36 @@ import React, { useEffect, useMemo } from 'react';
import { Vector3 } from 'three';
import { Html } from '@react-three/drei';
import { useSceneContext } from '../../../scene/sceneContext';
-import { useToggleView } from '../../../../store/builder/store';
+import { useToggleView, useToolMode } from '../../../../store/builder/store';
import Line from '../../line/line';
import Point from '../../point/point';
import FloorInstance from './Instance/floorInstance';
import Floor2DInstance from './Instance/floor2DInstance';
+import useModuleStore from '../../../../store/useModuleStore';
+import { useBuilderStore } from '../../../../store/builder/useBuilderStore';
function FloorInstances() {
const { floorStore } = useSceneContext();
const { floors } = floorStore();
+ const { setSelectedFloor, selectedFloor } = useBuilderStore();
+ const { toolMode } = useToolMode();
const { toggleView } = useToggleView();
+ const { activeModule } = useModuleStore();
useEffect(() => {
// console.log('floors: ', floors);
}, [floors])
+ useEffect(() => {
+ if (!toggleView && activeModule === 'builder') {
+ if (toolMode !== 'cursor') {
+ if (selectedFloor) setSelectedFloor(null);
+ }
+ } else {
+ if (selectedFloor) setSelectedFloor(null);
+ }
+ }, [toggleView, toolMode, activeModule, selectedFloor]);
+
const allPoints = useMemo(() => {
const points: Point[] = [];
const seenUuids = new Set();
diff --git a/app/src/modules/builder/floor/floorGroup.tsx b/app/src/modules/builder/floor/floorGroup.tsx
index 47ea3b5..d782e1a 100644
--- a/app/src/modules/builder/floor/floorGroup.tsx
+++ b/app/src/modules/builder/floor/floorGroup.tsx
@@ -10,7 +10,7 @@ import FloorInstances from './Instances/floorInstances';
import { getFloorsApi } from '../../../services/factoryBuilder/floor/getFloorsApi';
function FloorGroup() {
- const { togglView } = useToggleView();
+ const { toggleView } = useToggleView();
const { setSelectedFloor, setSelectedDecal } = useBuilderStore();
const { activeModule } = useModuleStore();
const { activeTool } = useActiveTool();
@@ -21,11 +21,11 @@ function FloorGroup() {
const { projectId } = useParams();
useEffect(() => {
- if (togglView || activeModule !== 'builder') {
+ if (toggleView || activeModule !== 'builder') {
setSelectedFloor(null);
setSelectedDecal(null);
}
- }, [togglView, activeModule, activeTool])
+ }, [toggleView, activeModule, activeTool])
useEffect(() => {
if (projectId && selectedVersion) {
diff --git a/app/src/modules/builder/wall/Instances/instance/wall.tsx b/app/src/modules/builder/wall/Instances/instance/wall.tsx
index 9d68d73..67d5b8a 100644
--- a/app/src/modules/builder/wall/Instances/instance/wall.tsx
+++ b/app/src/modules/builder/wall/Instances/instance/wall.tsx
@@ -10,7 +10,7 @@ import { useToggleView, useWallVisibility } from '../../../../../store/builder/s
import { useBuilderStore } from '../../../../../store/builder/useBuilderStore';
import * as Constants from '../../../../../types/world/worldConstants';
-import DecalInstance from '../../../Decal/decalInstance';
+import DecalInstance from '../../../Decal/decalInstance/decalInstance';
import defaultMaterial from '../../../../../assets/textures/floor/wall-tex.png';
import material1 from '../../../../../assets/textures/floor/factory wall texture.jpg';
@@ -21,7 +21,7 @@ function Wall({ wall }: { readonly wall: Wall }) {
const { wallAssets, getWallAssetsByWall, setVisibility } = wallAssetStore();
const assets = getWallAssetsByWall(wall.wallUuid);
const { selectedWall, setSelectedWall, setSelectedDecal } = useBuilderStore();
- const { togglView } = useToggleView();
+ const { toggleView } = useToggleView();
const { activeModule } = useModuleStore();
const { camera } = useThree();
const { wallVisibility } = useWallVisibility();
@@ -118,10 +118,11 @@ function Wall({ wall }: { readonly wall: Wall }) {
>
{(assets.length > 0 || (walls[0].wallUuid === wall.wallUuid && wallAssets.length > 0)) ?
{
- if (visible && !togglView && activeModule === 'builder') {
+ if (visible && !toggleView && activeModule === 'builder') {
if (e.object.userData.wallUuid) {
e.stopPropagation();
setSelectedWall(e.object);
@@ -171,7 +172,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 254705a..a67e3a3 100644
--- a/app/src/modules/builder/wall/Instances/wallInstances.tsx
+++ b/app/src/modules/builder/wall/Instances/wallInstances.tsx
@@ -2,8 +2,9 @@ import React, { useEffect, useMemo } from 'react';
import { DoubleSide, RepeatWrapping, Shape, SRGBColorSpace, TextureLoader, Vector2, Vector3 } from 'three';
import { Html, Extrude } from '@react-three/drei';
import { useLoader } from '@react-three/fiber';
+import { useBuilderStore } from '../../../../store/builder/useBuilderStore';
import { useSceneContext } from '../../../scene/sceneContext';
-import { useToggleView } from '../../../../store/builder/store';
+import { useToggleView, useToolMode } from '../../../../store/builder/store';
import { useWallClassification } from './instance/helpers/useWallClassification';
import Line from '../../line/line';
import Point from '../../point/point';
@@ -12,17 +13,31 @@ import * as Constants from '../../../../types/world/worldConstants';
import texturePath from "../../../../assets/textures/floor/white.png";
import texturePathDark from "../../../../assets/textures/floor/black.png";
+import useModuleStore from '../../../../store/useModuleStore';
function WallInstances() {
const { wallStore } = useSceneContext();
+ const { setSelectedWall, selectedWall } = useBuilderStore();
+ const { toolMode } = useToolMode();
+ const { toggleView } = useToggleView();
+ const { activeModule } = useModuleStore();
const { walls } = wallStore();
const { rooms } = useWallClassification(walls);
- const { toggleView } = useToggleView();
useEffect(() => {
// console.log('walls: ', walls);
}, [walls])
+ useEffect(() => {
+ if (!toggleView && activeModule === 'builder') {
+ if (toolMode !== 'cursor') {
+ if (selectedWall) setSelectedWall(null);
+ }
+ } else {
+ if (selectedWall) setSelectedWall(null);
+ }
+ }, [toggleView, toolMode, activeModule, selectedWall]);
+
const allPoints = useMemo(() => {
const points: Point[] = [];
const seenUuids = new Set();
diff --git a/app/src/modules/builder/wall/wallGroup.tsx b/app/src/modules/builder/wall/wallGroup.tsx
index 084b969..3561751 100644
--- a/app/src/modules/builder/wall/wallGroup.tsx
+++ b/app/src/modules/builder/wall/wallGroup.tsx
@@ -11,7 +11,7 @@ import WallInstances from './Instances/wallInstances';
import { getWallsApi } from '../../../services/factoryBuilder/wall/getWallsApi';
function WallGroup() {
- const { togglView } = useToggleView();
+ const { toggleView } = useToggleView();
const { setSelectedWall, setSelectedDecal } = useBuilderStore();
const { activeModule } = useModuleStore();
const { activeTool } = useActiveTool();
@@ -22,11 +22,11 @@ function WallGroup() {
const { projectId } = useParams();
useEffect(() => {
- if (togglView || activeModule !== 'builder') {
+ if (toggleView || activeModule !== 'builder') {
setSelectedWall(null);
setSelectedDecal(null);
}
- }, [togglView, activeModule, activeTool])
+ }, [toggleView, activeModule, activeTool])
useEffect(() => {
if (projectId && selectedVersion) {
diff --git a/app/src/modules/builder/wallAsset/Instances/Instance/functions/calculateAssetTransformationOnWall.ts b/app/src/modules/builder/wallAsset/Instances/Instance/functions/calculateAssetTransformationOnWall.ts
index aa881c8..89142d5 100644
--- a/app/src/modules/builder/wallAsset/Instances/Instance/functions/calculateAssetTransformationOnWall.ts
+++ b/app/src/modules/builder/wallAsset/Instances/Instance/functions/calculateAssetTransformationOnWall.ts
@@ -18,10 +18,10 @@ const calculateAssetTransformationOnWall = (
const projection = initialWallNormalized.clone().multiplyScalar(dotProduct);
const perpendicular = new THREE.Vector3().subVectors(assetVector, projection);
- const distanceFromWall = perpendicular.length();
+ const distanceInWall = perpendicular.length();
const crossProduct = new THREE.Vector3().crossVectors(initialWallNormalized, perpendicular).y;
- const signedDistance = distanceFromWall * (crossProduct >= 0 ? 1 : -1);
+ const signedDistance = distanceInWall * (crossProduct >= 0 ? 1 : -1);
const percentage = Math.max(0, Math.min(1, dotProduct / initialWallLength));
diff --git a/app/src/modules/builder/wallAsset/Instances/Instance/wallAssetInstance.tsx b/app/src/modules/builder/wallAsset/Instances/Instance/wallAssetInstance.tsx
index 76b3ad7..3919f65 100644
--- a/app/src/modules/builder/wallAsset/Instances/Instance/wallAssetInstance.tsx
+++ b/app/src/modules/builder/wallAsset/Instances/Instance/wallAssetInstance.tsx
@@ -23,7 +23,7 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) {
const { raycaster, pointer, camera, scene, controls, gl } = useThree();
const { wallStore, wallAssetStore } = useSceneContext();
const { walls, getWallById } = wallStore();
- const { updateWallAsset, removeWallAsset } = wallAssetStore();
+ const { updateWallAsset, removeWallAsset, getWallAssetById } = wallAssetStore();
const { toggleView } = useToggleView();
const { activeTool } = useActiveTool();
const { activeModule } = useModuleStore();
@@ -116,33 +116,14 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) {
const canvasElement = gl.domElement;
const onPointerUp = (e: PointerEvent) => {
- draggingRef.current = false;
- if (controls) {
- (controls as any).enabled = true;
- }
+ if (draggingRef.current) {
+ draggingRef.current = false;
+ if (controls) {
+ (controls as any).enabled = true;
+ }
- if (selectedWallAsset) {
- pointer.x = (e.clientX / window.innerWidth) * 2 - 1;
- pointer.y = -(e.clientY / window.innerHeight) * 2 + 1;
-
- raycaster.setFromCamera(pointer, camera);
- const intersects = raycaster.intersectObjects(scene.children, true);
- const intersect = intersects.find((i: any) => i.object.name.includes('WallReference'));
-
- if (intersect && intersect.object.userData.wallUuid && selectedWallAsset.userData.modelUuid === wallAsset.modelUuid) {
- const newPoint = closestPointOnLineSegment(
- new THREE.Vector3(intersect.point.x, 0, intersect.point.z),
- new THREE.Vector3(...intersect.object.userData.points[0].position),
- new THREE.Vector3(...intersect.object.userData.points[1].position)
- );
-
- const wallRotation = intersect.object.rotation.clone();
-
- const updatedWallAsset = updateWallAsset(wallAsset.modelUuid, {
- wallUuid: intersect.object.userData.wallUuid,
- position: [newPoint.x, wallAsset.wallAssetType === 'fixedMove' ? 0 : intersect.point.y, newPoint.z],
- rotation: [wallRotation.x, wallRotation.y, wallRotation.z],
- });
+ if (selectedWallAsset) {
+ const updatedWallAsset = getWallAssetById(wallAsset.modelUuid);
if (projectId && updatedWallAsset) {
diff --git a/app/src/modules/builder/wallAsset/wallAssetCreator.tsx b/app/src/modules/builder/wallAsset/wallAssetCreator.tsx
index fb95236..13438c2 100644
--- a/app/src/modules/builder/wallAsset/wallAssetCreator.tsx
+++ b/app/src/modules/builder/wallAsset/wallAssetCreator.tsx
@@ -14,7 +14,7 @@ import closestPointOnLineSegment from '../line/helpers/getClosestPointOnLineSegm
function WallAssetCreator() {
const { socket } = useSocketStore();
const { pointer, camera, raycaster, scene, gl } = useThree();
- const { togglView } = useToggleView();
+ const { toggleView } = useToggleView();
const { activeModule } = useModuleStore();
const { wallAssetStore } = useSceneContext();
const { addWallAsset } = wallAssetStore();
@@ -84,7 +84,7 @@ function WallAssetCreator() {
}
};
- if (!togglView && activeModule === 'builder') {
+ if (!toggleView && activeModule === 'builder') {
canvasElement.addEventListener('drop', onDrop);
}
@@ -92,7 +92,7 @@ function WallAssetCreator() {
canvasElement.removeEventListener('drop', onDrop);
};
- }, [gl, camera, togglView, activeModule, socket, selectedItem, setSelectedItem]);
+ }, [gl, camera, toggleView, activeModule, socket, selectedItem, setSelectedItem]);
return (
<>
diff --git a/app/src/modules/builder/wallAsset/wallAssetGroup.tsx b/app/src/modules/builder/wallAsset/wallAssetGroup.tsx
index bae9f87..95e2e90 100644
--- a/app/src/modules/builder/wallAsset/wallAssetGroup.tsx
+++ b/app/src/modules/builder/wallAsset/wallAssetGroup.tsx
@@ -1,5 +1,5 @@
import { useEffect } from 'react';
-import { useActiveTool, useToggleView } from '../../../store/builder/store';
+import { useToggleView, useToolMode } from '../../../store/builder/store';
import { useBuilderStore } from '../../../store/builder/useBuilderStore';
import { useVersionContext } from '../version/versionContext';
import { useSceneContext } from '../../scene/sceneContext';
@@ -10,10 +10,10 @@ import WallAssetInstances from './Instances/wallAssetInstances'
import { getWallAssetsApi } from '../../../services/factoryBuilder/asset/wallAsset/getWallAssetsApi';
function WallAssetGroup() {
- const { togglView } = useToggleView();
- const { setSelectedFloorAsset, setDeletableWallAsset } = useBuilderStore();
+ const { toggleView } = useToggleView();
+ const { setSelectedWallAsset, setDeletableWallAsset } = useBuilderStore();
const { activeModule } = useModuleStore();
- const { activeTool } = useActiveTool();
+ const { toolMode } = useToolMode();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { wallAssetStore } = useSceneContext();
@@ -21,11 +21,11 @@ function WallAssetGroup() {
const { projectId } = useParams();
useEffect(() => {
- if (togglView || activeModule !== 'builder') {
- setSelectedFloorAsset(null);
+ if (toggleView || activeModule !== 'builder' || toolMode !== 'cursor') {
+ setSelectedWallAsset(null);
}
setDeletableWallAsset(null);
- }, [togglView, activeModule, activeTool])
+ }, [toggleView, activeModule, toolMode])
useEffect(() => {
if (projectId && selectedVersion) {
diff --git a/app/src/modules/builder/zone/zoneGroup.tsx b/app/src/modules/builder/zone/zoneGroup.tsx
index c44bf09..08d3e9c 100644
--- a/app/src/modules/builder/zone/zoneGroup.tsx
+++ b/app/src/modules/builder/zone/zoneGroup.tsx
@@ -11,7 +11,7 @@ import ZoneInstances from './Instances/zoneInstances';
import { getZonesApi } from '../../../services/factoryBuilder/zone/getZonesApi';
function ZoneGroup() {
- const { togglView } = useToggleView();
+ const { toggleView } = useToggleView();
const { setSelectedZone } = useBuilderStore();
const { activeModule } = useModuleStore();
const { activeTool } = useActiveTool();
@@ -22,10 +22,10 @@ function ZoneGroup() {
const { projectId } = useParams();
useEffect(() => {
- if (togglView || activeModule !== 'builder') {
+ if (toggleView || activeModule !== 'builder') {
setSelectedZone(null);
}
- }, [togglView, activeModule, activeTool])
+ }, [toggleView, activeModule, activeTool])
useEffect(() => {
if (projectId && selectedVersion) {
diff --git a/app/src/modules/scene/camera/camMode.tsx b/app/src/modules/scene/camera/camMode.tsx
index 77e2f31..60d0798 100644
--- a/app/src/modules/scene/camera/camMode.tsx
+++ b/app/src/modules/scene/camera/camMode.tsx
@@ -1,12 +1,13 @@
import { useFrame, useThree } from "@react-three/fiber";
import React, { useEffect, useState } from "react";
+import { useKeyboardControls } from "@react-three/drei";
import * as CONSTANTS from "../../../types/world/worldConstants";
import { useCamMode, useToggleView } from "../../../store/builder/store";
-import { useKeyboardControls } from "@react-three/drei";
-import switchToThirdPerson from "./switchToThirdPerson";
-import switchToFirstPerson from "./switchToFirstPerson";
+
+import switchToThirdPerson from "./functions/switchToThirdPerson";
+import switchToFirstPerson from "./functions/switchToFirstPerson";
import { detectModifierKeys } from "../../../utils/shortcutkeys/detectModifierKeys";
-import { firstPersonCamera } from "./firstPersonCamera";
+import { firstPersonCamera } from "./functions/firstPersonCamera";
const CamMode: React.FC = () => {
const { camMode, setCamMode } = useCamMode();
diff --git a/app/src/modules/scene/camera/firstPersonCamera.ts b/app/src/modules/scene/camera/functions/firstPersonCamera.ts
similarity index 92%
rename from app/src/modules/scene/camera/firstPersonCamera.ts
rename to app/src/modules/scene/camera/functions/firstPersonCamera.ts
index 85aacb3..27620f3 100644
--- a/app/src/modules/scene/camera/firstPersonCamera.ts
+++ b/app/src/modules/scene/camera/functions/firstPersonCamera.ts
@@ -1,39 +1,39 @@
-import * as CONSTANTS from "../../../types/world/worldConstants";
-
-interface FirstPersonCameraProps {
- setIsTransitioning?: (value: boolean) => void;
- state: any;
-}
-
-interface FirstPersonCameraParams extends FirstPersonCameraProps {
- camMode: string;
- setCamMode: (mode: string) => void;
- switchToFirstPerson: (controls: any, camera: any) => Promise;
- switchToThirdPerson: (controls: any, camera: any) => Promise;
-}
-
-export async function firstPersonCamera({
- setIsTransitioning,
- state,
- camMode,
- setCamMode,
- switchToFirstPerson,
- switchToThirdPerson
-}: FirstPersonCameraParams): Promise {
- setIsTransitioning && setIsTransitioning(true);
-
- state.controls.mouseButtons.left = CONSTANTS.controlsTransition.leftMouse;
- state.controls.mouseButtons.right = CONSTANTS.controlsTransition.rightMouse;
- state.controls.mouseButtons.wheel = CONSTANTS.controlsTransition.wheelMouse;
- state.controls.mouseButtons.middle = CONSTANTS.controlsTransition.middleMouse;
-
- if (camMode === "ThirdPerson") {
- setCamMode("FirstPerson");
- await switchToFirstPerson(state.controls, state.camera);
- } else if (camMode === "FirstPerson") {
- setCamMode("ThirdPerson");
- await switchToThirdPerson(state.controls, state.camera);
- }
-
- setIsTransitioning && setIsTransitioning(false);
-}
+import * as CONSTANTS from "../../../../types/world/worldConstants";
+
+interface FirstPersonCameraProps {
+ setIsTransitioning?: (value: boolean) => void;
+ state: any;
+}
+
+interface FirstPersonCameraParams extends FirstPersonCameraProps {
+ camMode: string;
+ setCamMode: (mode: string) => void;
+ switchToFirstPerson: (controls: any, camera: any) => Promise;
+ switchToThirdPerson: (controls: any, camera: any) => Promise;
+}
+
+export async function firstPersonCamera({
+ setIsTransitioning,
+ state,
+ camMode,
+ setCamMode,
+ switchToFirstPerson,
+ switchToThirdPerson
+}: FirstPersonCameraParams): Promise {
+ setIsTransitioning && setIsTransitioning(true);
+
+ state.controls.mouseButtons.left = CONSTANTS.controlsTransition.leftMouse;
+ state.controls.mouseButtons.right = CONSTANTS.controlsTransition.rightMouse;
+ state.controls.mouseButtons.wheel = CONSTANTS.controlsTransition.wheelMouse;
+ state.controls.mouseButtons.middle = CONSTANTS.controlsTransition.middleMouse;
+
+ if (camMode === "ThirdPerson") {
+ setCamMode("FirstPerson");
+ await switchToFirstPerson(state.controls, state.camera);
+ } else if (camMode === "FirstPerson") {
+ setCamMode("ThirdPerson");
+ await switchToThirdPerson(state.controls, state.camera);
+ }
+
+ setIsTransitioning && setIsTransitioning(false);
+}
diff --git a/app/src/modules/scene/camera/switchToFirstPerson.ts b/app/src/modules/scene/camera/functions/switchToFirstPerson.ts
similarity index 91%
rename from app/src/modules/scene/camera/switchToFirstPerson.ts
rename to app/src/modules/scene/camera/functions/switchToFirstPerson.ts
index a5371c4..a8a35de 100644
--- a/app/src/modules/scene/camera/switchToFirstPerson.ts
+++ b/app/src/modules/scene/camera/functions/switchToFirstPerson.ts
@@ -1,25 +1,25 @@
-import * as THREE from 'three';
-import * as CONSTANTS from '../../../types/world/worldConstants';
-
-export default async function switchToFirstPerson(
- controls: any,
- camera: any
-) {
- if (!controls) return;
-
- const cameraDirection = new THREE.Vector3();
- camera.getWorldDirection(cameraDirection);
- cameraDirection.normalize();
-
- await controls.setPosition(camera.position.x, 2, camera.position.z, true);
- controls.setTarget(camera.position.x, 2, camera.position.z, true);
- controls.mouseButtons.left = CONSTANTS.firstPersonControls.leftMouse;
- controls.lockPointer();
-
- controls.azimuthRotateSpeed = CONSTANTS.firstPersonControls.azimuthRotateSpeed;
- controls.polarRotateSpeed = CONSTANTS.firstPersonControls.polarRotateSpeed;
- controls.truckSpeed = CONSTANTS.firstPersonControls.truckSpeed;
- controls.minDistance = CONSTANTS.firstPersonControls.minDistance;
- controls.maxDistance = CONSTANTS.firstPersonControls.maxDistance;
- controls.maxPolarAngle = CONSTANTS.firstPersonControls.maxPolarAngle;
+import * as THREE from 'three';
+import * as CONSTANTS from '../../../../types/world/worldConstants';
+
+export default async function switchToFirstPerson(
+ controls: any,
+ camera: any
+) {
+ if (!controls) return;
+
+ const cameraDirection = new THREE.Vector3();
+ camera.getWorldDirection(cameraDirection);
+ cameraDirection.normalize();
+
+ await controls.setPosition(camera.position.x, 2, camera.position.z, true);
+ controls.setTarget(camera.position.x, 2, camera.position.z, true);
+ controls.mouseButtons.left = CONSTANTS.firstPersonControls.leftMouse;
+ controls.lockPointer();
+
+ controls.azimuthRotateSpeed = CONSTANTS.firstPersonControls.azimuthRotateSpeed;
+ controls.polarRotateSpeed = CONSTANTS.firstPersonControls.polarRotateSpeed;
+ controls.truckSpeed = CONSTANTS.firstPersonControls.truckSpeed;
+ controls.minDistance = CONSTANTS.firstPersonControls.minDistance;
+ controls.maxDistance = CONSTANTS.firstPersonControls.maxDistance;
+ controls.maxPolarAngle = CONSTANTS.firstPersonControls.maxPolarAngle;
}
\ No newline at end of file
diff --git a/app/src/modules/scene/camera/switchToThirdPerson.ts b/app/src/modules/scene/camera/functions/switchToThirdPerson.ts
similarity index 93%
rename from app/src/modules/scene/camera/switchToThirdPerson.ts
rename to app/src/modules/scene/camera/functions/switchToThirdPerson.ts
index 1e59749..b66adc5 100644
--- a/app/src/modules/scene/camera/switchToThirdPerson.ts
+++ b/app/src/modules/scene/camera/functions/switchToThirdPerson.ts
@@ -1,29 +1,29 @@
-import * as THREE from 'three';
-import * as CONSTANTS from '../../../types/world/worldConstants';
-
-export default async function switchToThirdPerson(
- controls: any,
- camera: any
-) {
- if (!controls) return;
- controls.mouseButtons.left = CONSTANTS.thirdPersonControls.leftMouse;
- controls.mouseButtons.right = CONSTANTS.thirdPersonControls.rightMouse;
- controls.mouseButtons.middle = CONSTANTS.thirdPersonControls.middleMouse;
- controls.mouseButtons.wheel = CONSTANTS.thirdPersonControls.wheelMouse;
- controls.unlockPointer();
-
- const cameraDirection = new THREE.Vector3();
- camera.getWorldDirection(cameraDirection);
- const targetOffset = cameraDirection.multiplyScalar(CONSTANTS.thirdPersonControls.targetOffset);
- const targetPosition = new THREE.Vector3(camera.position.x, camera.position.y, camera.position.z).add(targetOffset);
-
- controls.setPosition(camera.position.x, CONSTANTS.thirdPersonControls.cameraHeight, camera.position.z, true);
- controls.setTarget(targetPosition.x, 0, targetPosition.z, true);
-
- controls.azimuthRotateSpeed = CONSTANTS.thirdPersonControls.azimuthRotateSpeed;
- controls.polarRotateSpeed = CONSTANTS.thirdPersonControls.polarRotateSpeed;
- controls.truckSpeed = CONSTANTS.thirdPersonControls.truckSpeed;
- controls.minDistance = CONSTANTS.threeDimension.minDistance;
- controls.maxDistance = CONSTANTS.thirdPersonControls.maxDistance;
- controls.maxPolarAngle = CONSTANTS.thirdPersonControls.maxPolarAngle;
+import * as THREE from 'three';
+import * as CONSTANTS from '../../../../types/world/worldConstants';
+
+export default async function switchToThirdPerson(
+ controls: any,
+ camera: any
+) {
+ if (!controls) return;
+ controls.mouseButtons.left = CONSTANTS.thirdPersonControls.leftMouse;
+ controls.mouseButtons.right = CONSTANTS.thirdPersonControls.rightMouse;
+ controls.mouseButtons.middle = CONSTANTS.thirdPersonControls.middleMouse;
+ controls.mouseButtons.wheel = CONSTANTS.thirdPersonControls.wheelMouse;
+ controls.unlockPointer();
+
+ const cameraDirection = new THREE.Vector3();
+ camera.getWorldDirection(cameraDirection);
+ const targetOffset = cameraDirection.multiplyScalar(CONSTANTS.thirdPersonControls.targetOffset);
+ const targetPosition = new THREE.Vector3(camera.position.x, camera.position.y, camera.position.z).add(targetOffset);
+
+ controls.setPosition(camera.position.x, CONSTANTS.thirdPersonControls.cameraHeight, camera.position.z, true);
+ controls.setTarget(targetPosition.x, 0, targetPosition.z, true);
+
+ controls.azimuthRotateSpeed = CONSTANTS.thirdPersonControls.azimuthRotateSpeed;
+ controls.polarRotateSpeed = CONSTANTS.thirdPersonControls.polarRotateSpeed;
+ controls.truckSpeed = CONSTANTS.thirdPersonControls.truckSpeed;
+ controls.minDistance = CONSTANTS.threeDimension.minDistance;
+ controls.maxDistance = CONSTANTS.thirdPersonControls.maxDistance;
+ controls.maxPolarAngle = CONSTANTS.thirdPersonControls.maxPolarAngle;
}
\ No newline at end of file
diff --git a/app/src/modules/scene/camera/updateCameraPosition.ts b/app/src/modules/scene/camera/functions/updateCameraPosition.ts
similarity index 88%
rename from app/src/modules/scene/camera/updateCameraPosition.ts
rename to app/src/modules/scene/camera/functions/updateCameraPosition.ts
index 26e22ed..fb8c956 100644
--- a/app/src/modules/scene/camera/updateCameraPosition.ts
+++ b/app/src/modules/scene/camera/functions/updateCameraPosition.ts
@@ -1,26 +1,26 @@
-import { Socket } from "socket.io-client";
-import * as THREE from "three";
-import { getUserData } from "../../../functions/getUserData";
-
-export default function updateCamPosition(
- controls: any,
- socket: Socket,
- position: THREE.Vector3,
- rotation: THREE.Euler,
- projectId?: string
-) {
- const { userId, organization } = getUserData();
- if (!controls.current) return;
- const target = controls.current.getTarget(new THREE.Vector3());
-
- const camData = {
- organization,
- userId: userId,
- position: position,
- target: new THREE.Vector3(target.x, 0, target.z),
- rotation: new THREE.Vector3(rotation.x, rotation.y, rotation.z),
- socketId: socket.id,
- projectId,
- };
- socket.emit("v1:Camera:set", camData);
-}
+import { Socket } from "socket.io-client";
+import * as THREE from "three";
+import { getUserData } from "../../../../functions/getUserData";
+
+export default function updateCamPosition(
+ controls: any,
+ socket: Socket,
+ position: THREE.Vector3,
+ rotation: THREE.Euler,
+ projectId?: string
+) {
+ const { userId, organization } = getUserData();
+ if (!controls.current) return;
+ const target = controls.current.getTarget(new THREE.Vector3());
+
+ const camData = {
+ organization,
+ userId: userId,
+ position: position,
+ target: new THREE.Vector3(target.x, 0, target.z),
+ rotation: new THREE.Vector3(rotation.x, rotation.y, rotation.z),
+ socketId: socket.id,
+ projectId,
+ };
+ socket.emit("v1:Camera:set", camData);
+}
diff --git a/app/src/modules/scene/camera/shortcutsControls/cameraShortcutsControls.tsx b/app/src/modules/scene/camera/shortcutsControls/cameraShortcutsControls.tsx
new file mode 100644
index 0000000..573584c
--- /dev/null
+++ b/app/src/modules/scene/camera/shortcutsControls/cameraShortcutsControls.tsx
@@ -0,0 +1,73 @@
+import { useEffect } from "react";
+import { useThree } from "@react-three/fiber";
+import * as THREE from "three";
+import type { CameraControls } from "@react-three/drei";
+
+const CameraShortcutsControls = () => {
+ const { camera, controls } = useThree();
+
+ const isTextInput = (element: Element | null): boolean =>
+ element instanceof HTMLInputElement ||
+ element instanceof HTMLTextAreaElement ||
+ element?.getAttribute("contenteditable") === "true";
+
+ useEffect(() => {
+ const handleKeyDown = (e: KeyboardEvent) => {
+ if (!controls) return;
+
+ const cc = controls as CameraControls;
+
+ // get current target
+ const target = new THREE.Vector3();
+ cc.getTarget(target);
+
+ const distance = camera.position.distanceTo(target);
+ let pos: THREE.Vector3 | null = null;
+
+ const dir = new THREE.Vector3().subVectors(camera.position, target).normalize();
+
+ if (isTextInput(document.activeElement)) return;
+
+ switch (e.key) {
+ case "1": // Front
+ pos = new THREE.Vector3(0, 0, distance).add(target);
+ break;
+ case "3": // Right
+ pos = new THREE.Vector3(distance, 0, 0).add(target);
+ break;
+ case "7": // Top
+ pos = new THREE.Vector3(0, distance, 0).add(target);
+ break;
+ case "9": {
+ // Opposite view logic
+ if (Math.abs(dir.z) > Math.abs(dir.x) && Math.abs(dir.z) > Math.abs(dir.y)) {
+ // Currently looking Front/Back → flip Z
+ pos = new THREE.Vector3(0, 0, -Math.sign(dir.z) * distance).add(target);
+ } else if (Math.abs(dir.x) > Math.abs(dir.z) && Math.abs(dir.x) > Math.abs(dir.y)) {
+ // Currently looking Right/Left → flip X
+ pos = new THREE.Vector3(-Math.sign(dir.x) * distance, 0, 0).add(target);
+ } else {
+ // Currently looking Top/Bottom → stay Top
+ pos = new THREE.Vector3(0, distance, 0).add(target);
+ }
+ break;
+ }
+ }
+
+ if (pos) {
+ cc.setLookAt(
+ pos.x, pos.y, pos.z, // camera position
+ target.x, target.y, target.z, // keep same target
+ true // smooth transition
+ );
+ }
+ };
+
+ window.addEventListener("keydown", handleKeyDown);
+ return () => window.removeEventListener("keydown", handleKeyDown);
+ }, [controls, camera]);
+
+ return null;
+};
+
+export default CameraShortcutsControls;
diff --git a/app/src/modules/scene/controls/contextControls/contextControls.tsx b/app/src/modules/scene/controls/contextControls/contextControls.tsx
index 142f8d1..46801c1 100644
--- a/app/src/modules/scene/controls/contextControls/contextControls.tsx
+++ b/app/src/modules/scene/controls/contextControls/contextControls.tsx
@@ -1,41 +1,14 @@
import { useEffect, useRef, useState } from "react";
import { useThree } from "@react-three/fiber";
import { CameraControls, Html, ScreenSpace } from "@react-three/drei";
-import {
- useContextActionStore,
- useRenameModeStore,
- useSelectedAssets,
-} from "../../../../store/builder/store";
+import { useContextActionStore, useRenameModeStore, useSelectedAssets, useToggleView, useToolMode, } from "../../../../store/builder/store";
import ContextMenu from "../../../../components/ui/menu/contextMenu";
+import useModuleStore from "../../../../store/useModuleStore";
function ContextControls() {
- const { gl, controls } = useThree();
- const [canRender, setCanRender] = useState(false);
- const [visibility, setVisibility] = useState({
- rename: true,
- focus: true,
- flipX: true,
- flipZ: true,
- move: true,
- rotate: true,
- duplicate: true,
- copy: true,
- paste: true,
- modifier: false,
- group: false,
- array: false,
- delete: true,
- });
- const [menuPosition, setMenuPosition] = useState({ x: 0, y: 0 });
- const { selectedAssets } = useSelectedAssets();
- const { setContextAction } = useContextActionStore();
- const { setIsRenameMode } = useRenameModeStore();
- const rightDrag = useRef(false);
- const isRightMouseDown = useRef(false);
-
- useEffect(() => {
- if (selectedAssets.length === 1) {
- setVisibility({
+ const { gl, controls } = useThree();
+ const [canRender, setCanRender] = useState(false);
+ const [visibility, setVisibility] = useState({
rename: true,
focus: true,
flipX: true,
@@ -49,195 +22,219 @@ function ContextControls() {
group: false,
array: false,
delete: true,
- });
- } else if (selectedAssets.length > 1) {
- setVisibility({
- rename: false,
- focus: true,
- flipX: true,
- flipZ: true,
- move: true,
- rotate: true,
- duplicate: true,
- copy: true,
- paste: true,
- modifier: false,
- group: true,
- array: false,
- delete: true,
- });
- } else {
- setVisibility({
- rename: false,
- focus: false,
- flipX: false,
- flipZ: false,
- move: false,
- rotate: false,
- duplicate: false,
- copy: false,
- paste: false,
- modifier: false,
- group: false,
- array: false,
- delete: false,
- });
- }
- }, [selectedAssets]);
+ });
+ const [menuPosition, setMenuPosition] = useState({ x: 0, y: 0 });
+ const { toggleView } = useToggleView();
+ const { activeModule } = useModuleStore();
+ const { toolMode } = useToolMode();
+ const { selectedAssets } = useSelectedAssets();
+ const { setContextAction } = useContextActionStore();
+ const { setIsRenameMode } = useRenameModeStore();
+ const rightDrag = useRef(false);
+ const isRightMouseDown = useRef(false);
- useEffect(() => {
- const canvasElement = gl.domElement;
-
- const onPointerDown = (evt: any) => {
- if (evt.button === 2) {
- isRightMouseDown.current = true;
- rightDrag.current = false;
- }
- };
-
- const onPointerMove = () => {
- if (isRightMouseDown.current) {
- rightDrag.current = true;
- }
- };
-
- const onPointerUp = (evt: any) => {
- if (evt.button === 2) {
- isRightMouseDown.current = false;
- }
- };
-
- const handleContextClick = (event: MouseEvent) => {
- event.preventDefault();
- if (rightDrag.current) return;
- if (selectedAssets.length > 0) {
- setMenuPosition({
- x: event.clientX - gl.domElement.width / 2,
- y: event.clientY - gl.domElement.height / 2,
- });
- setCanRender(true);
- if (controls) {
- (controls as CameraControls).enabled = false;
+ useEffect(() => {
+ if (selectedAssets.length === 1) {
+ setVisibility({
+ rename: true,
+ focus: true,
+ flipX: true,
+ flipZ: true,
+ move: true,
+ rotate: true,
+ duplicate: true,
+ copy: true,
+ paste: true,
+ modifier: false,
+ group: false,
+ array: false,
+ delete: true,
+ });
+ } else if (selectedAssets.length > 1) {
+ setVisibility({
+ rename: false,
+ focus: true,
+ flipX: true,
+ flipZ: true,
+ move: true,
+ rotate: true,
+ duplicate: true,
+ copy: true,
+ paste: true,
+ modifier: false,
+ group: true,
+ array: false,
+ delete: true,
+ });
+ } else {
+ setVisibility({
+ rename: false,
+ focus: false,
+ flipX: false,
+ flipZ: false,
+ move: false,
+ rotate: false,
+ duplicate: false,
+ copy: false,
+ paste: false,
+ modifier: false,
+ group: false,
+ array: false,
+ delete: false,
+ });
}
- } else {
+ }, [selectedAssets]);
+
+ useEffect(() => {
+ const canvasElement = gl.domElement;
+
+ const onPointerDown = (evt: any) => {
+ if (evt.button === 2) {
+ isRightMouseDown.current = true;
+ rightDrag.current = false;
+ }
+ };
+
+ const onPointerMove = () => {
+ if (isRightMouseDown.current) {
+ rightDrag.current = true;
+ }
+ };
+
+ const onPointerUp = (evt: any) => {
+ if (evt.button === 2) {
+ isRightMouseDown.current = false;
+ }
+ };
+
+ const handleContextClick = (event: MouseEvent) => {
+ event.preventDefault();
+ if (rightDrag.current) return;
+ if (selectedAssets.length > 0) {
+ setMenuPosition({ x: event.clientX - gl.domElement.width / 2, y: event.clientY - gl.domElement.height / 2, });
+ setCanRender(true);
+ if (controls) {
+ (controls as CameraControls).enabled = false;
+ }
+ } else {
+ setCanRender(false);
+ if (controls) {
+ (controls as CameraControls).enabled = true;
+ }
+ }
+ };
+
+ if (selectedAssets.length > 0 && !toggleView && activeModule === "builder" && toolMode === 'cursor') {
+ canvasElement.addEventListener("pointerdown", onPointerDown);
+ canvasElement.addEventListener("pointermove", onPointerMove);
+ canvasElement.addEventListener("pointerup", onPointerUp);
+ canvasElement.addEventListener("contextmenu", handleContextClick);
+ } else {
+ setCanRender(false);
+ if (controls) {
+ (controls as CameraControls).enabled = true;
+ }
+ setMenuPosition({ x: 0, y: 0 });
+ }
+
+ return () => {
+ canvasElement.removeEventListener("pointerdown", onPointerDown);
+ canvasElement.removeEventListener("pointermove", onPointerMove);
+ canvasElement.removeEventListener("pointerup", onPointerUp);
+ canvasElement.removeEventListener("contextmenu", handleContextClick);
+ };
+ }, [controls, gl, selectedAssets, toggleView, activeModule, toolMode]);
+
+ const handleAssetRename = () => {
setCanRender(false);
if (controls) {
- (controls as CameraControls).enabled = true;
+ (controls as CameraControls).enabled = true;
}
- }
+ setContextAction("renameAsset");
+ setIsRenameMode(true);
+ };
+ const handleAssetFocus = () => {
+ setCanRender(false);
+ if (controls) {
+ (controls as CameraControls).enabled = true;
+ }
+ setContextAction("focusAsset");
+ };
+ const handleAssetMove = () => {
+ setCanRender(false);
+ if (controls) {
+ (controls as CameraControls).enabled = true;
+ }
+ setContextAction("moveAsset");
+ };
+ const handleAssetRotate = () => {
+ setCanRender(false);
+ if (controls) {
+ (controls as CameraControls).enabled = true;
+ }
+ setContextAction("rotateAsset");
+ };
+ const handleAssetCopy = () => {
+ setCanRender(false);
+ if (controls) {
+ (controls as CameraControls).enabled = true;
+ }
+ setContextAction("copyAsset");
+ };
+ const handleAssetPaste = () => {
+ setCanRender(false);
+ if (controls) {
+ (controls as CameraControls).enabled = true;
+ }
+ setContextAction("pasteAsset");
+ };
+ const handleAssetDelete = () => {
+ setCanRender(false);
+ if (controls) {
+ (controls as CameraControls).enabled = true;
+ }
+ setContextAction("deleteAsset");
+ };
+ const handleAssetDuplicate = () => {
+ setCanRender(false);
+ if (controls) {
+ (controls as CameraControls).enabled = true;
+ }
+ setContextAction("duplicateAsset");
};
- if (selectedAssets.length > 0) {
- canvasElement.addEventListener("pointerdown", onPointerDown);
- canvasElement.addEventListener("pointermove", onPointerMove);
- canvasElement.addEventListener("pointerup", onPointerUp);
- canvasElement.addEventListener("contextmenu", handleContextClick);
- } else {
- setCanRender(false);
- if (controls) {
- (controls as CameraControls).enabled = true;
- }
- setMenuPosition({ x: 0, y: 0 });
- }
-
- return () => {
- canvasElement.removeEventListener("pointerdown", onPointerDown);
- canvasElement.removeEventListener("pointermove", onPointerMove);
- canvasElement.removeEventListener("pointerup", onPointerUp);
- canvasElement.removeEventListener("contextmenu", handleContextClick);
- };
- }, [controls, gl, selectedAssets]);
-
- const handleAssetRename = () => {
- setCanRender(false);
- if (controls) {
- (controls as CameraControls).enabled = true;
- }
- setContextAction("renameAsset");
- setIsRenameMode(true);
- };
- const handleAssetFocus = () => {
- setCanRender(false);
- if (controls) {
- (controls as CameraControls).enabled = true;
- }
- setContextAction("focusAsset");
- };
- const handleAssetMove = () => {
- setCanRender(false);
- if (controls) {
- (controls as CameraControls).enabled = true;
- }
- setContextAction("moveAsset");
- };
- const handleAssetRotate = () => {
- setCanRender(false);
- if (controls) {
- (controls as CameraControls).enabled = true;
- }
- setContextAction("rotateAsset");
- };
- const handleAssetCopy = () => {
- setCanRender(false);
- if (controls) {
- (controls as CameraControls).enabled = true;
- }
- setContextAction("copyAsset");
- };
- const handleAssetPaste = () => {
- setCanRender(false);
- if (controls) {
- (controls as CameraControls).enabled = true;
- }
- setContextAction("pasteAsset");
- };
- const handleAssetDelete = () => {
- setCanRender(false);
- if (controls) {
- (controls as CameraControls).enabled = true;
- }
- setContextAction("deleteAsset");
- };
- const handleAssetDuplicate = () => {
- setCanRender(false);
- if (controls) {
- (controls as CameraControls).enabled = true;
- }
- setContextAction("duplicateAsset");
- };
-
- return (
- <>
- {canRender && (
-
-
- handleAssetRename()}
- onFocus={() => handleAssetFocus()}
- onFlipX={() => console.log("Flip to X")}
- onFlipZ={() => console.log("Flip to Z")}
- onMove={() => handleAssetMove()}
- onRotate={() => handleAssetRotate()}
- onDuplicate={() => handleAssetDuplicate()}
- onCopy={() => handleAssetCopy()}
- onPaste={() => handleAssetPaste()}
- onGroup={() => console.log("Group")}
- onArray={() => console.log("Array")}
- onDelete={() => handleAssetDelete()}
- />
-
-
- )}
- >
- );
+ return (
+ <>
+ {canRender && (
+
+
+ handleAssetRename()}
+ onFocus={() => handleAssetFocus()}
+ onFlipX={() => console.log("Flip to X")}
+ onFlipZ={() => console.log("Flip to Z")}
+ onMove={() => handleAssetMove()}
+ onRotate={() => handleAssetRotate()}
+ onDuplicate={() => handleAssetDuplicate()}
+ onCopy={() => handleAssetCopy()}
+ onPaste={() => handleAssetPaste()}
+ onGroup={() => console.log("Group")}
+ onArray={() => console.log("Array")}
+ onDelete={() => handleAssetDelete()}
+ />
+
+
+ )}
+ >
+ );
}
export default ContextControls;
diff --git a/app/src/modules/scene/controls/controls.tsx b/app/src/modules/scene/controls/controls.tsx
index d63f0ac..a3c9638 100644
--- a/app/src/modules/scene/controls/controls.tsx
+++ b/app/src/modules/scene/controls/controls.tsx
@@ -3,22 +3,22 @@ import { useRef, useEffect } from "react";
import { useThree } from "@react-three/fiber";
import * as THREE from "three";
import * as CONSTANTS from '../../../types/world/worldConstants';
-
import { useSocketStore, useToggleView, useResetCamera } from "../../../store/builder/store";
-import { getCamera } from "../../../services/factoryBuilder/camera/getCameraApi";
-import updateCamPosition from "../camera/updateCameraPosition";
+
import CamMode from "../camera/camMode";
import SwitchView from "../camera/switchView";
-import SelectionControls3D from "./selectionControls/selection3D/selectionControls3D";
-import TransformControl from "./transformControls/transformControls";
-import { useParams } from "react-router-dom";
-import { getUserData } from "../../../functions/getUserData";
-
import ContextControls from "./contextControls/contextControls";
+import TransformControl from "./transformControls/transformControls";
import SelectionControls2D from "./selectionControls/selection2D/selectionControls2D";
+import SelectionControls3D from "./selectionControls/selection3D/selectionControls3D";
import UndoRedo2DControls from "./undoRedoControls/undoRedo2D/undoRedo2DControls";
import UndoRedo3DControls from "./undoRedoControls/undoRedo3D/undoRedo3DControls";
-import { useCameraShortcuts } from "../../../hooks/useCameraShortcuts";
+import CameraShortcutsControls from "../camera/shortcutsControls/cameraShortcutsControls";
+
+import { useParams } from "react-router-dom";
+import { getUserData } from "../../../functions/getUserData";
+import { getCamera } from "../../../services/factoryBuilder/camera/getCameraApi";
+import updateCamPosition from "../camera/functions/updateCameraPosition";
export default function Controls() {
const controlsRef = useRef(null);
@@ -117,7 +117,6 @@ export default function Controls() {
stopInterval();
};
}, [toggleView, state, socket]);
- useCameraShortcuts(controlsRef);
return (
<>
@@ -140,6 +139,8 @@ export default function Controls() {
+
+
diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/boundingBoxHelper3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/boundingBoxHelper3D.tsx
index dde668e..3345a2d 100644
--- a/app/src/modules/scene/controls/selectionControls/selection3D/boundingBoxHelper3D.tsx
+++ b/app/src/modules/scene/controls/selectionControls/selection3D/boundingBoxHelper3D.tsx
@@ -91,9 +91,9 @@ const BoundingBox = ({ boundingBoxRef, isPerAsset = true }: BoundingBoxProps) =>
>
new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
const { toggleView } = useToggleView();
+ const { toolMode } = useToolMode();
+ const { activeModule } = useModuleStore();
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore();
@@ -162,7 +165,7 @@ function MoveControls3D({ boundingBoxRef }: any) {
}
};
- if (!toggleView) {
+ if (!toggleView && toolMode === 'cursor') {
canvasElement.addEventListener("pointerdown", onPointerDown);
canvasElement.addEventListener("pointermove", onPointerMove);
canvasElement.addEventListener("pointerup", onPointerUp);
@@ -178,7 +181,14 @@ function MoveControls3D({ boundingBoxRef }: any) {
canvasElement?.removeEventListener("keyup", onKeyUp);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [camera, controls, scene, toggleView, selectedAssets, socket, pastedObjects, duplicatedObjects, movedObjects, rotatedObjects, keyEvent, initialStates]);
+ }, [camera, controls, scene, toggleView, toolMode, selectedAssets, socket, pastedObjects, duplicatedObjects, movedObjects, rotatedObjects, keyEvent, initialStates]);
+
+ useEffect(() => {
+ if (activeModule !== "builder" || toolMode !== 'cursor' || toggleView) {
+ resetToInitialPositions();
+ setMovedObjects([]);
+ }
+ }, [activeModule, toolMode, toggleView]);
const calculateDragOffset = useCallback((point: THREE.Object3D, hitPoint: THREE.Vector3) => {
const pointPosition = new THREE.Vector3().copy(point.position);
diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx
index 5af7a3e..4f2319f 100644
--- a/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx
+++ b/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx
@@ -1,7 +1,7 @@
import * as THREE from "three";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useFrame, useThree } from "@react-three/fiber";
-import { useContextActionStore, useSelectedAssets, useSocketStore, useToggleView } from "../../../../../store/builder/store";
+import { useContextActionStore, useSelectedAssets, useSocketStore, useToggleView, useToolMode } from "../../../../../store/builder/store";
import * as Types from "../../../../../types/world/worldTypes";
import { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi";
import { useParams } from "react-router-dom";
@@ -11,6 +11,7 @@ import { useSceneContext } from "../../../sceneContext";
import { useVersionContext } from "../../../../builder/version/versionContext";
import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys";
import { handleAssetRotationSnap } from "./functions/handleAssetRotationSnap";
+import useModuleStore from "../../../../../store/useModuleStore";
// import { setAssetsApi } from '../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi';
@@ -18,6 +19,8 @@ function RotateControls3D() {
const { camera, gl, scene, pointer, raycaster } = useThree();
const { toggleView } = useToggleView();
+ const { toolMode } = useToolMode();
+ const { activeModule } = useModuleStore();
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore();
@@ -34,6 +37,7 @@ function RotateControls3D() {
const [initialRotations, setInitialRotations] = useState>({});
const [initialPositions, setInitialPositions] = useState>({});
const [isRotating, setIsRotating] = useState(false);
+ const [isIndividualRotating, setIsIndividualRotating] = useState(false);
const prevPointerPosition = useRef(null);
const rotationCenter = useRef(null);
const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ left: false, right: false, });
@@ -117,6 +121,12 @@ function RotateControls3D() {
}
}
+ if (event.key.toLowerCase() === 'i') {
+ if (rotatedObjects.length > 0) {
+ setIsIndividualRotating(!isIndividualRotating);
+ }
+ }
+
if (event.key.toLowerCase() === "escape") {
event.preventDefault();
resetToInitialRotations();
@@ -137,7 +147,7 @@ function RotateControls3D() {
prevPointerPosition.current = currentPointer.clone();
};
- if (!toggleView) {
+ if (!toggleView && toolMode === 'cursor') {
canvasElement.addEventListener("pointerdown", onPointerDown);
canvasElement.addEventListener("pointermove", onPointerMove);
canvasElement.addEventListener("pointerup", onPointerUp);
@@ -152,7 +162,14 @@ function RotateControls3D() {
canvasElement.removeEventListener("keydown", onKeyDown);
canvasElement?.removeEventListener("keyup", onKeyUp);
};
- }, [camera, scene, toggleView, selectedAssets, rotatedObjects, pastedObjects, duplicatedObjects, movedObjects, keyEvent, initialPositions, initialRotations]);
+ }, [camera, scene, toggleView, toolMode, selectedAssets, rotatedObjects, pastedObjects, duplicatedObjects, movedObjects, keyEvent, initialPositions, initialRotations, isIndividualRotating]);
+
+ useEffect(() => {
+ if (activeModule !== "builder" || toolMode !== 'cursor' || toggleView) {
+ resetToInitialRotations();
+ setRotatedObjects([]);
+ }
+ }, [activeModule, toolMode, toggleView]);
const resetToInitialRotations = useCallback(() => {
setTimeout(() => {
@@ -204,10 +221,12 @@ function RotateControls3D() {
wasShiftHeldRef
});
- const relativePosition = new THREE.Vector3().subVectors(obj.position, center);
- const rotationMatrix = new THREE.Matrix4().makeRotationY(angleDelta);
- relativePosition.applyMatrix4(rotationMatrix);
- obj.position.copy(center).add(relativePosition);
+ if (!isIndividualRotating) {
+ const relativePosition = new THREE.Vector3().subVectors(obj.position, center);
+ const rotationMatrix = new THREE.Matrix4().makeRotationY(angleDelta);
+ relativePosition.applyMatrix4(rotationMatrix);
+ obj.position.copy(center).add(relativePosition);
+ }
const rotationQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), angleDelta);
obj.quaternion.multiply(rotationQuat);
@@ -384,8 +403,9 @@ function RotateControls3D() {
}
setIsRotating(false);
+ setIsIndividualRotating(false);
clearSelection();
- }, [rotatedObjects, eventStore, productStore, selectedProduct, updateBackend, projectId, updateAsset, organization, socket, selectedVersion, userId]);
+ }, [rotatedObjects, eventStore, productStore, selectedProduct, updateBackend, projectId, updateAsset, organization, socket, selectedVersion, userId, initialPositions, initialRotations]);
const clearSelection = () => {
setPastedObjects([]);
@@ -393,6 +413,7 @@ function RotateControls3D() {
setMovedObjects([]);
setRotatedObjects([]);
setSelectedAssets([]);
+ setIsIndividualRotating(false);
};
return null;
diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx
index ed458c2..512b80f 100644
--- a/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx
+++ b/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx
@@ -16,22 +16,23 @@ import DuplicationControls3D from "./duplicationControls3D";
import CopyPasteControls3D from "./copyPasteControls3D";
import MoveControls3D from "./moveControls3D";
import RotateControls3D from "./rotateControls3D";
+import TransformControls3D from "./transformControls3D";
// import { deleteFloorItem } from '../../../../../services/factoryBuilder/asset/floorAsset/deleteFloorItemApi';
const SelectionControls3D: React.FC = () => {
const { camera, controls, gl, scene, raycaster, pointer } = useThree();
const { toggleView } = useToggleView();
+ const { activeModule } = useModuleStore();
+ const { toolMode } = useToolMode();
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
const boundingBoxRef = useRef();
- const { activeModule } = useModuleStore();
const { socket } = useSocketStore();
const { contextAction, setContextAction } = useContextActionStore()
const { assetStore, eventStore, productStore, undoRedo3DStore } = useSceneContext();
const { push3D } = undoRedo3DStore();
const { removeAsset, getAssetById, movedObjects, rotatedObjects, copiedObjects, pastedObjects, duplicatedObjects, setPastedObjects, setDuplicatedObjects } = assetStore();
const selectionBox = useMemo(() => new SelectionBox(camera, scene), [camera, scene]);
- const { toolMode } = useToolMode();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { selectedProductStore } = useProductContext();
@@ -202,7 +203,7 @@ const SelectionControls3D: React.FC = () => {
rightClickMoved.current = false;
};
- if (!toggleView && activeModule === "builder" && toolMode === 'cursor') {
+ if (!toggleView && activeModule === "builder" && (toolMode === 'cursor' || toolMode === 'Move-Asset' || toolMode === 'Rotate-Asset')) {
helper.enabled = true;
canvasElement.addEventListener("pointermove", onPointerMove);
canvasElement.addEventListener("pointerup", onPointerUp);
@@ -227,7 +228,7 @@ const SelectionControls3D: React.FC = () => {
}, [camera, controls, scene, toggleView, selectedAssets, copiedObjects, pastedObjects, duplicatedObjects, movedObjects, socket, rotatedObjects, activeModule, toolMode]);
useEffect(() => {
- if (activeModule !== "builder" || toolMode !== 'cursor' || toggleView) {
+ if (activeModule !== "builder" || (toolMode !== 'cursor' && toolMode !== 'Move-Asset' && toolMode !== 'Rotate-Asset') || toggleView) {
clearSelection();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -381,6 +382,8 @@ const SelectionControls3D: React.FC = () => {
+
+
>
);
};
diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/transformControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/transformControls3D.tsx
new file mode 100644
index 0000000..3f3162b
--- /dev/null
+++ b/app/src/modules/scene/controls/selectionControls/selection3D/transformControls3D.tsx
@@ -0,0 +1,380 @@
+import * as THREE from 'three';
+import { useRef, useEffect, useState, useCallback } from 'react';
+import { useThree } from '@react-three/fiber';
+import { TransformControls } from '@react-three/drei';
+import { useSelectedAssets, useSocketStore, useToolMode } from '../../../../../store/builder/store';
+import { useProductContext } from '../../../../simulation/products/productContext';
+import { useVersionContext } from '../../../../builder/version/versionContext';
+import { useSceneContext } from '../../../sceneContext';
+import { useParams } from 'react-router-dom';
+
+import { getUserData } from '../../../../../functions/getUserData';
+import { upsertProductOrEventApi } from '../../../../../services/simulation/products/UpsertProductOrEventApi';
+import { detectModifierKeys } from '../../../../../utils/shortcutkeys/detectModifierKeys';
+// import { setAssetsApi } from '../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi';
+
+function TransformControls3D() {
+ const { selectedAssets, setSelectedAssets } = useSelectedAssets();
+ const { toolMode } = useToolMode();
+ const { camera, scene, gl } = useThree();
+ const transformControlsRef = useRef(null);
+ const [visible, setVisible] = useState(false);
+ const [keyEvent, setKeyEvent] = useState<"Ctrl" | "Shift" | "Ctrl+Shift" | "">("");
+ const { socket } = useSocketStore();
+ const { selectedProductStore } = useProductContext();
+ const { selectedProduct } = selectedProductStore();
+ const { assetStore, eventStore, productStore, undoRedo3DStore } = useSceneContext();
+ const { push3D, subscribeUndoRedo } = undoRedo3DStore();
+ const { updateAsset, getAssetById } = assetStore();
+ const { userId, organization } = getUserData();
+ const { selectedVersionStore } = useVersionContext();
+ const { selectedVersion } = selectedVersionStore();
+ const { projectId } = useParams();
+ const pivotPointRef = useRef(new THREE.Vector3());
+ const initialPositionsRef = useRef