From 182c364e3728ca57be316ffd49af8fa46a95ed33 Mon Sep 17 00:00:00 2001 From: arDTDev Date: Sun, 21 Dec 2025 23:02:25 +0900 Subject: [PATCH] Feat: Add panning, grid snap, and view control buttons --- App.tsx | 73 +++++++++++++++++++-- components/SceneObjects.tsx | 124 ++++++++++++++++++------------------ 2 files changed, 132 insertions(+), 65 deletions(-) diff --git a/App.tsx b/App.tsx index 8a2c015..246976a 100644 --- a/App.tsx +++ b/App.tsx @@ -1,7 +1,8 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import { Canvas, useFrame } from '@react-three/fiber'; -import { OrbitControls, Grid } from '@react-three/drei'; +import { MOUSE } from 'three'; +import { OrbitControls, Grid, GizmoHelper, GizmoViewcube } from '@react-three/drei'; import { Sidebar } from './components/Sidebar'; import { IOPanel } from './components/IOPanel'; import { LadderEditor } from './components/LadderEditor'; @@ -10,7 +11,7 @@ import { IOLogicRule, LogicCondition, LogicAction, ProjectData } from './types'; import { EditableObject } from './components/SceneObjects'; -import { Layout as LayoutIcon, Cpu, Play, Pause, Square, Download, Upload } from 'lucide-react'; +import { Layout as LayoutIcon, Cpu, Play, Pause, Square, Download, Upload, Magnet } from 'lucide-react'; // --- Simulation Manager (PLC Scan Cycle) --- const SimulationLoop = ({ @@ -137,6 +138,24 @@ export default function App() { const [outputNames, setOutputNames] = useState(new Array(16).fill('')); const [selectedId, setSelectedId] = useState(null); const [isPlaying, setIsPlaying] = useState(false); + const [isSnapEnabled, setIsSnapEnabled] = useState(false); + const controlsRef = useRef(null); + + const handleSetView = (view: 'TOP' | 'BOTTOM' | 'FRONT' | 'BACK' | 'LEFT' | 'RIGHT') => { + const ctrl = controlsRef.current; + if (!ctrl) return; + const dist = 15; + ctrl.target.set(0, 0, 0); + switch (view) { + case 'TOP': ctrl.object.position.set(0, dist, 0); break; + case 'BOTTOM': ctrl.object.position.set(0, -dist, 0); break; + case 'FRONT': ctrl.object.position.set(0, 0, dist); break; + case 'BACK': ctrl.object.position.set(0, 0, -dist); break; + case 'RIGHT': ctrl.object.position.set(dist, 0, 0); break; + case 'LEFT': ctrl.object.position.set(-dist, 0, 0); break; + } + ctrl.update(); + }; useEffect(() => { if (!isPlaying) { @@ -267,6 +286,16 @@ export default function App() {
+ + +
+
+); diff --git a/components/SceneObjects.tsx b/components/SceneObjects.tsx index 4d77452..f3b6943 100644 --- a/components/SceneObjects.tsx +++ b/components/SceneObjects.tsx @@ -2,13 +2,13 @@ import React, { useRef } from 'react'; import { TransformControls, Text } from '@react-three/drei'; import * as THREE from 'three'; -import { - SimObject, - ObjectType, - AxisObject, - CylinderObject, - LedObject, - SwitchObject +import { + SimObject, + ObjectType, + AxisObject, + CylinderObject, + LedObject, + SwitchObject } from '../types'; interface ObjectProps { @@ -31,8 +31,8 @@ export const LinearAxis: React.FC = ({ data, isSelected, isPlaying, const safePos = isNaN(normalizedPos) ? 0 : Math.max(0, Math.min(railLength, normalizedPos)); return ( - { e.stopPropagation(); if (!isPlaying) onSelect(axis.id); }} > @@ -44,7 +44,7 @@ export const LinearAxis: React.FC = ({ data, isSelected, isPlaying, - + @@ -66,12 +66,12 @@ export const RotaryAxis: React.FC = ({ data, isSelected, isPlaying, const rotationAngle = (axis.currentValue % 360) * (Math.PI / 180); return ( - { e.stopPropagation(); if (!isPlaying) onSelect(axis.id); }} > - + {axis.name} ({axis.currentValue.toFixed(0)}°) @@ -91,10 +91,10 @@ export const RotaryAxis: React.FC = ({ data, isSelected, isPlaying, - {isSelected && !isPlaying && ( + {isSelected && !isPlaying && ( - + )} @@ -108,34 +108,34 @@ export const Cylinder: React.FC = ({ data, isSelected, isPlaying, o const extension = Math.min(cyl.stroke, Math.max(0, cyl.currentPosition)); return ( - { e.stopPropagation(); if (!isPlaying) onSelect(cyl.id); }} > - + {cyl.name} - + - - - - - + + + + {isSelected && !isPlaying && ( - + @@ -147,21 +147,21 @@ export const Cylinder: React.FC = ({ data, isSelected, isPlaying, o // -- Switch Component -- export const Switch: React.FC = ({ data, isSelected, isPlaying, onSelect, onInteract }) => { const sw = data as SwitchObject; - + return ( - { - e.stopPropagation(); - if (!isPlaying) onSelect(sw.id); - if(onInteract) onInteract(sw.id); + onClick={(e) => { + e.stopPropagation(); + if (!isPlaying) onSelect(sw.id); + if (onInteract) onInteract(sw.id); }} > {sw.name} - + @@ -185,17 +185,17 @@ export const Switch: React.FC = ({ data, isSelected, isPlaying, onS // -- LED Component -- export const Led: React.FC = ({ data, isSelected, isPlaying, onSelect }) => { const led = data as LedObject; - + return ( - { e.stopPropagation(); if (!isPlaying) onSelect(led.id); }} > {led.name} - + @@ -203,14 +203,14 @@ export const Led: React.FC = ({ data, isSelected, isPlaying, onSele - - {isSelected && !isPlaying && ( + {isSelected && !isPlaying && ( @@ -220,37 +220,39 @@ export const Led: React.FC = ({ data, isSelected, isPlaying, onSele ); }; -export const EditableObject: React.FC = (props) => { - const { data, enableTransform, isPlaying, onUpdate } = props; +export const EditableObject: React.FC = (props) => { + const { data, enableTransform, isPlaying, onUpdate, snap } = props; const handleTransformChange = (e: any) => { if (e.target.object) { - const o = e.target.object; - onUpdate(data.id, { - position: { x: o.position.x, y: o.position.y, z: o.position.z }, - rotation: { x: o.rotation.x, y: o.rotation.y, z: o.rotation.z } - }); + const o = e.target.object; + onUpdate(data.id, { + position: { x: o.position.x, y: o.position.y, z: o.position.z }, + rotation: { x: o.rotation.x, y: o.rotation.y, z: o.rotation.z } + }); } }; - const Component = + const Component = data.type === ObjectType.AXIS_LINEAR ? LinearAxis : - data.type === ObjectType.AXIS_ROTARY ? RotaryAxis : - data.type === ObjectType.CYLINDER ? Cylinder : - data.type === ObjectType.SWITCH ? Switch : - Led; + data.type === ObjectType.AXIS_ROTARY ? RotaryAxis : + data.type === ObjectType.CYLINDER ? Cylinder : + data.type === ObjectType.SWITCH ? Switch : + Led; return ( <> {props.isSelected && enableTransform && !isPlaying && ( - )}