added crane mechanics ui and dblclick zoom effect chnage
This commit is contained in:
@@ -15,6 +15,7 @@ import { useProductContext } from "../../../../../modules/simulation/products/pr
|
|||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { useVersionContext } from "../../../../../modules/builder/version/versionContext";
|
import { useVersionContext } from "../../../../../modules/builder/version/versionContext";
|
||||||
import { useSceneContext } from "../../../../../modules/scene/sceneContext";
|
import { useSceneContext } from "../../../../../modules/scene/sceneContext";
|
||||||
|
import CraneMechanics from "./mechanics/craneMechanics";
|
||||||
|
|
||||||
const EventProperties: React.FC = () => {
|
const EventProperties: React.FC = () => {
|
||||||
const { selectedEventData } = useSelectedEventData();
|
const { selectedEventData } = useSelectedEventData();
|
||||||
@@ -63,6 +64,8 @@ const EventProperties: React.FC = () => {
|
|||||||
return "storageUnit";
|
return "storageUnit";
|
||||||
case "human":
|
case "human":
|
||||||
return "human";
|
return "human";
|
||||||
|
case "crane":
|
||||||
|
return "crane";
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -83,6 +86,7 @@ const EventProperties: React.FC = () => {
|
|||||||
{assetType === "machine" && <MachineMechanics />}
|
{assetType === "machine" && <MachineMechanics />}
|
||||||
{assetType === "storageUnit" && <StorageMechanics />}
|
{assetType === "storageUnit" && <StorageMechanics />}
|
||||||
{assetType === "human" && <HumanMechanics />}
|
{assetType === "human" && <HumanMechanics />}
|
||||||
|
{assetType === "crane" && <CraneMechanics />}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{!currentEventData && selectedEventSphere && (
|
{!currentEventData && selectedEventSphere && (
|
||||||
|
|||||||
@@ -0,0 +1,210 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { MathUtils } from "three";
|
||||||
|
import RenameInput from "../../../../../ui/inputs/RenameInput";
|
||||||
|
import LabledDropdown from "../../../../../ui/inputs/LabledDropdown";
|
||||||
|
import Trigger from "../trigger/Trigger";
|
||||||
|
import { useSelectedEventData, useSelectedAction } from "../../../../../../store/simulation/useSimulationStore";
|
||||||
|
import ActionsList from "../components/ActionsList";
|
||||||
|
import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi";
|
||||||
|
import { useProductContext } from "../../../../../../modules/simulation/products/productContext";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import { useVersionContext } from "../../../../../../modules/builder/version/versionContext";
|
||||||
|
import { useSceneContext } from "../../../../../../modules/scene/sceneContext";
|
||||||
|
|
||||||
|
function CraneMechanics() {
|
||||||
|
const [activeOption, setActiveOption] = useState<"pickAndDrop">("pickAndDrop");
|
||||||
|
const [selectedPointData, setSelectedPointData] = useState<CranePointSchema | undefined>();
|
||||||
|
|
||||||
|
const { selectedEventData } = useSelectedEventData();
|
||||||
|
const { productStore } = useSceneContext();
|
||||||
|
const { getPointByUuid, updateAction, addAction, removeAction } = productStore();
|
||||||
|
const { selectedProductStore } = useProductContext();
|
||||||
|
const { selectedProduct } = selectedProductStore();
|
||||||
|
const { selectedAction, setSelectedAction, clearSelectedAction } = useSelectedAction();
|
||||||
|
const { selectedVersionStore } = useVersionContext();
|
||||||
|
const { selectedVersion } = selectedVersionStore();
|
||||||
|
const { projectId } = useParams();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedEventData) {
|
||||||
|
const point = getPointByUuid(
|
||||||
|
selectedProduct.productUuid,
|
||||||
|
selectedEventData.data.modelUuid,
|
||||||
|
selectedEventData.selectedPoint
|
||||||
|
) as CranePointSchema | undefined;
|
||||||
|
|
||||||
|
if (point?.actions) {
|
||||||
|
setSelectedPointData(point);
|
||||||
|
|
||||||
|
if (point.actions.length > 0) {
|
||||||
|
const firstAction = point.actions[0];
|
||||||
|
setActiveOption(firstAction.actionType);
|
||||||
|
setSelectedAction(firstAction.actionUuid, firstAction.actionName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
clearSelectedAction();
|
||||||
|
}
|
||||||
|
}, [selectedEventData, selectedProduct]);
|
||||||
|
|
||||||
|
const updateBackend = (
|
||||||
|
productName: string,
|
||||||
|
productUuid: string,
|
||||||
|
projectId: string,
|
||||||
|
eventData: EventsSchema
|
||||||
|
) => {
|
||||||
|
upsertProductOrEventApi({
|
||||||
|
productName,
|
||||||
|
productUuid,
|
||||||
|
projectId,
|
||||||
|
eventDatas: eventData,
|
||||||
|
versionId: selectedVersion?.versionId || "",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRenameAction = (newName: string) => {
|
||||||
|
if (!selectedAction.actionId || !selectedPointData) return;
|
||||||
|
|
||||||
|
const event = updateAction(
|
||||||
|
selectedProduct.productUuid,
|
||||||
|
selectedAction.actionId,
|
||||||
|
{ actionName: newName }
|
||||||
|
);
|
||||||
|
|
||||||
|
const updatedActions = selectedPointData.actions.map(action =>
|
||||||
|
action.actionUuid === selectedAction.actionId
|
||||||
|
? { ...action, actionName: newName }
|
||||||
|
: action
|
||||||
|
);
|
||||||
|
|
||||||
|
setSelectedPointData({
|
||||||
|
...selectedPointData,
|
||||||
|
actions: updatedActions,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
updateBackend(
|
||||||
|
selectedProduct.productName,
|
||||||
|
selectedProduct.productUuid,
|
||||||
|
projectId || '',
|
||||||
|
event
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddAction = () => {
|
||||||
|
if (!selectedEventData || !selectedPointData) return;
|
||||||
|
|
||||||
|
const newAction = {
|
||||||
|
actionUuid: MathUtils.generateUUID(),
|
||||||
|
actionName: `Action ${selectedPointData.actions.length + 1}`,
|
||||||
|
actionType: "pickAndDrop" as const,
|
||||||
|
maxPickUpCount: 1,
|
||||||
|
triggers: [] as TriggerSchema[],
|
||||||
|
};
|
||||||
|
|
||||||
|
const event = addAction(
|
||||||
|
selectedProduct.productUuid,
|
||||||
|
selectedEventData.data.modelUuid,
|
||||||
|
selectedEventData.selectedPoint,
|
||||||
|
newAction
|
||||||
|
);
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
updateBackend(
|
||||||
|
selectedProduct.productName,
|
||||||
|
selectedProduct.productUuid,
|
||||||
|
projectId || '',
|
||||||
|
event
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectedPointData({
|
||||||
|
...selectedPointData,
|
||||||
|
actions: [...selectedPointData.actions, newAction],
|
||||||
|
});
|
||||||
|
setSelectedAction(newAction.actionUuid, newAction.actionName);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteAction = (actionUuid: string) => {
|
||||||
|
if (!selectedPointData) return;
|
||||||
|
|
||||||
|
const event = removeAction(
|
||||||
|
selectedProduct.productUuid,
|
||||||
|
actionUuid
|
||||||
|
);
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
updateBackend(
|
||||||
|
selectedProduct.productName,
|
||||||
|
selectedProduct.productUuid,
|
||||||
|
projectId || '',
|
||||||
|
event
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = selectedPointData.actions.findIndex(a => a.actionUuid === actionUuid);
|
||||||
|
const newActions = selectedPointData.actions.filter(a => a.actionUuid !== actionUuid);
|
||||||
|
|
||||||
|
setSelectedPointData({
|
||||||
|
...selectedPointData,
|
||||||
|
actions: newActions,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (selectedAction.actionId === actionUuid) {
|
||||||
|
const nextAction = newActions[index] || newActions[index - 1];
|
||||||
|
if (nextAction) {
|
||||||
|
setSelectedAction(nextAction.actionUuid, nextAction.actionName);
|
||||||
|
} else {
|
||||||
|
clearSelectedAction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const availableActions = {
|
||||||
|
defaultOption: "pickAndDrop",
|
||||||
|
options: ["pickAndDrop"],
|
||||||
|
};
|
||||||
|
|
||||||
|
const currentAction = selectedPointData?.actions.find(a => a.actionUuid === selectedAction.actionId);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<section>
|
||||||
|
<ActionsList
|
||||||
|
selectedPointData={selectedPointData}
|
||||||
|
multipleAction
|
||||||
|
handleAddAction={handleAddAction}
|
||||||
|
handleDeleteAction={handleDeleteAction}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{selectedAction.actionId && currentAction && (
|
||||||
|
<div className="selected-actions-details">
|
||||||
|
<div className="selected-actions-header">
|
||||||
|
<RenameInput
|
||||||
|
value={selectedAction.actionName || ""}
|
||||||
|
canEdit={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="selected-actions-list">
|
||||||
|
<LabledDropdown
|
||||||
|
defaultOption={activeOption}
|
||||||
|
options={availableActions.options}
|
||||||
|
onSelect={() => { }}
|
||||||
|
disabled={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="tirgger">
|
||||||
|
<Trigger
|
||||||
|
selectedPointData={selectedPointData as any}
|
||||||
|
type={"RoboticArm"}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CraneMechanics
|
||||||
@@ -24,7 +24,7 @@ export function useModelEventHandlers({
|
|||||||
boundingBox: THREE.Box3 | null,
|
boundingBox: THREE.Box3 | null,
|
||||||
groupRef: React.RefObject<THREE.Group>,
|
groupRef: React.RefObject<THREE.Group>,
|
||||||
}) {
|
}) {
|
||||||
const { controls, gl } = useThree();
|
const { controls, gl, camera } = useThree();
|
||||||
const { activeTool } = useActiveTool();
|
const { activeTool } = useActiveTool();
|
||||||
const { activeModule } = useModuleStore();
|
const { activeModule } = useModuleStore();
|
||||||
const { toggleView } = useToggleView();
|
const { toggleView } = useToggleView();
|
||||||
@@ -68,6 +68,10 @@ export function useModelEventHandlers({
|
|||||||
|
|
||||||
const handleDblClick = (asset: Asset) => {
|
const handleDblClick = (asset: Asset) => {
|
||||||
if (asset && activeTool === "cursor" && boundingBox && groupRef.current && activeModule === 'builder') {
|
if (asset && activeTool === "cursor" && boundingBox && groupRef.current && activeModule === 'builder') {
|
||||||
|
|
||||||
|
const frontView = false;
|
||||||
|
|
||||||
|
if (frontView) {
|
||||||
const size = boundingBox.getSize(new THREE.Vector3());
|
const size = boundingBox.getSize(new THREE.Vector3());
|
||||||
const center = boundingBox.getCenter(new THREE.Vector3());
|
const center = boundingBox.getCenter(new THREE.Vector3());
|
||||||
|
|
||||||
@@ -87,6 +91,24 @@ export function useModelEventHandlers({
|
|||||||
paddingBottom: 5,
|
paddingBottom: 5,
|
||||||
paddingRight: 5,
|
paddingRight: 5,
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
|
||||||
|
const collisionPos = new THREE.Vector3();
|
||||||
|
groupRef.current.getWorldPosition(collisionPos);
|
||||||
|
|
||||||
|
const currentPos = new THREE.Vector3().copy(camera.position);
|
||||||
|
|
||||||
|
const target = new THREE.Vector3();
|
||||||
|
if (!controls) return;
|
||||||
|
(controls as CameraControls).getTarget(target);
|
||||||
|
const direction = new THREE.Vector3().subVectors(target, currentPos).normalize();
|
||||||
|
|
||||||
|
const offsetDistance = 5;
|
||||||
|
const newCameraPos = new THREE.Vector3().copy(collisionPos).sub(direction.multiplyScalar(offsetDistance));
|
||||||
|
|
||||||
|
camera.position.copy(newCameraPos);
|
||||||
|
(controls as CameraControls).setLookAt(newCameraPos.x, newCameraPos.y, newCameraPos.z, collisionPos.x, 0, collisionPos.z, true);
|
||||||
|
}
|
||||||
|
|
||||||
setSelectedFloorItem(groupRef.current);
|
setSelectedFloorItem(groupRef.current);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import { useFrame, useThree } from '@react-three/fiber';
|
import { useFrame, useThree } from '@react-three/fiber';
|
||||||
import { Sphere, Box } from '@react-three/drei';
|
|
||||||
import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore } from '../../../../../store/usePlayButtonStore';
|
import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore } from '../../../../../store/usePlayButtonStore';
|
||||||
import { useSceneContext } from '../../../../scene/sceneContext';
|
import { useSceneContext } from '../../../../scene/sceneContext';
|
||||||
|
|
||||||
@@ -26,7 +25,6 @@ function PillarJibAnimator({
|
|||||||
const { speed } = useAnimationPlaySpeed();
|
const { speed } = useAnimationPlaySpeed();
|
||||||
|
|
||||||
const [clampedPoints, setClampedPoints] = useState<[THREE.Vector3, THREE.Vector3]>();
|
const [clampedPoints, setClampedPoints] = useState<[THREE.Vector3, THREE.Vector3]>();
|
||||||
const [isInside, setIsInside] = useState<[boolean, boolean]>([false, false]);
|
|
||||||
const [currentTargetIndex, setCurrentTargetIndex] = useState<number>(0);
|
const [currentTargetIndex, setCurrentTargetIndex] = useState<number>(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -101,7 +99,6 @@ function PillarJibAnimator({
|
|||||||
});
|
});
|
||||||
|
|
||||||
setClampedPoints(newClampedPoints);
|
setClampedPoints(newClampedPoints);
|
||||||
setIsInside(newIsInside);
|
|
||||||
}, [crane.modelUuid]);
|
}, [crane.modelUuid]);
|
||||||
|
|
||||||
useFrame(() => {
|
useFrame(() => {
|
||||||
@@ -227,29 +224,8 @@ function PillarJibAnimator({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!clampedPoints) return null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{points.map((point, i) => (
|
|
||||||
<Box
|
|
||||||
key={`original-${i}`}
|
|
||||||
position={point}
|
|
||||||
args={[0.15, 0.15, 0.15]}
|
|
||||||
>
|
|
||||||
<meshStandardMaterial color="yellow" />
|
|
||||||
</Box>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{clampedPoints.map((point, i) => (
|
|
||||||
<Sphere
|
|
||||||
key={`clamped-${i}`}
|
|
||||||
position={point}
|
|
||||||
args={[0.1, 16, 16]}
|
|
||||||
>
|
|
||||||
<meshStandardMaterial color={isInside[i] ? 'lightgreen' : 'orange'} />
|
|
||||||
</Sphere>
|
|
||||||
))}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,18 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
import { useThree } from "@react-three/fiber";
|
import { useThree } from "@react-three/fiber";
|
||||||
|
import { Box, Sphere } from "@react-three/drei";
|
||||||
|
|
||||||
function PillarJibHelper({ crane }: { crane: CraneStatus }) {
|
function PillarJibHelper({
|
||||||
|
crane,
|
||||||
|
points
|
||||||
|
}: {
|
||||||
|
crane: CraneStatus,
|
||||||
|
points: [THREE.Vector3, THREE.Vector3];
|
||||||
|
}) {
|
||||||
const { scene } = useThree();
|
const { scene } = useThree();
|
||||||
|
const [clampedPoints, setClampedPoints] = useState<[THREE.Vector3, THREE.Vector3]>();
|
||||||
|
const [isInside, setIsInside] = useState<[boolean, boolean]>([false, false]);
|
||||||
|
|
||||||
const { geometry, position } = useMemo(() => {
|
const { geometry, position } = useMemo(() => {
|
||||||
const model = scene.getObjectByProperty('uuid', crane.modelUuid);
|
const model = scene.getObjectByProperty('uuid', crane.modelUuid);
|
||||||
@@ -51,12 +60,46 @@ function PillarJibHelper({ crane }: { crane: CraneStatus }) {
|
|||||||
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
|
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
|
||||||
const position: [number, number, number] = [baseWorld.x, cylinderYPosition, baseWorld.z];
|
const position: [number, number, number] = [baseWorld.x, cylinderYPosition, baseWorld.z];
|
||||||
|
|
||||||
|
const yMin = hookWorld.y + hookMaxOffset;
|
||||||
|
const yMax = hookWorld.y + hookMinOffset;
|
||||||
|
|
||||||
|
function clampToCylinder(pos: THREE.Vector3) {
|
||||||
|
const xzDist = new THREE.Vector2(pos.x - baseWorld.x, pos.z - baseWorld.z);
|
||||||
|
const distance = xzDist.length();
|
||||||
|
|
||||||
|
let clampedDistance = THREE.MathUtils.clamp(distance, innerRadius, outerRadius);
|
||||||
|
if (distance > 0) {
|
||||||
|
clampedDistance = Math.max(innerRadius, Math.min(distance, outerRadius));
|
||||||
|
}
|
||||||
|
|
||||||
|
const clampedXZ = xzDist.normalize().multiplyScalar(clampedDistance);
|
||||||
|
const y = THREE.MathUtils.clamp(pos.y, yMin, yMax);
|
||||||
|
|
||||||
|
return new THREE.Vector3(baseWorld.x + clampedXZ.x, y, baseWorld.z + clampedXZ.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newClampedPoints: [THREE.Vector3, THREE.Vector3] = [new THREE.Vector3(), new THREE.Vector3()];
|
||||||
|
const newIsInside: [boolean, boolean] = [false, false];
|
||||||
|
|
||||||
|
points.forEach((point, i) => {
|
||||||
|
const xzDist = new THREE.Vector2(point.x - baseWorld.x, point.z - baseWorld.z).length();
|
||||||
|
const insideXZ = xzDist >= innerRadius && xzDist <= outerRadius;
|
||||||
|
const insideY = point.y >= yMin && point.y <= yMax;
|
||||||
|
newIsInside[i] = insideXZ && insideY;
|
||||||
|
|
||||||
|
newClampedPoints[i] = newIsInside[i] ? point.clone() : clampToCylinder(point);
|
||||||
|
});
|
||||||
|
|
||||||
|
setClampedPoints(newClampedPoints);
|
||||||
|
setIsInside(newIsInside);
|
||||||
|
|
||||||
return { geometry, position };
|
return { geometry, position };
|
||||||
}, [scene, crane.modelUuid]);
|
}, [scene, crane.modelUuid]);
|
||||||
|
|
||||||
if (!geometry || !position) return null;
|
if (!geometry || !position) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<mesh
|
<mesh
|
||||||
geometry={geometry}
|
geometry={geometry}
|
||||||
position={position}
|
position={position}
|
||||||
@@ -71,6 +114,27 @@ function PillarJibHelper({ crane }: { crane: CraneStatus }) {
|
|||||||
opacity={0.3}
|
opacity={0.3}
|
||||||
/>
|
/>
|
||||||
</mesh>
|
</mesh>
|
||||||
|
|
||||||
|
{points.map((point, i) => (
|
||||||
|
<Box
|
||||||
|
key={`original-${i}`}
|
||||||
|
position={point}
|
||||||
|
args={[0.15, 0.15, 0.15]}
|
||||||
|
>
|
||||||
|
<meshStandardMaterial color="yellow" />
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{clampedPoints && clampedPoints.map((point, i) => (
|
||||||
|
<Sphere
|
||||||
|
key={`clamped-${i}`}
|
||||||
|
position={point}
|
||||||
|
args={[0.1, 16, 16]}
|
||||||
|
>
|
||||||
|
<meshStandardMaterial color={isInside[i] ? 'lightgreen' : 'orange'} />
|
||||||
|
</Sphere>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,13 @@ function PillarJibInstance({ crane }: { crane: CraneStatus }) {
|
|||||||
onAnimationComplete={handleAnimationComplete}
|
onAnimationComplete={handleAnimationComplete}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PillarJibHelper crane={crane} />
|
{/* <PillarJibHelper
|
||||||
|
crane={crane}
|
||||||
|
points={[
|
||||||
|
new THREE.Vector3(...position1),
|
||||||
|
new THREE.Vector3(...position2)]
|
||||||
|
}
|
||||||
|
/> */}
|
||||||
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user