fix: 3D viewer position scaling and stage coloring
Some checks are pending
Deploy to Production / deploy (push) Waiting to run
Test / backend-test (push) Waiting to run
Test / frontend-test (push) Waiting to run

- Add SCALE constant (0.1) to convert pixel coords to world units
- Fix stage coloring: use FLOWERING/DRYING/CURING instead of FLOWER/DRY/CURE
- Apply scaling to FacilityScene, RoomObject, SmartRack consistently
- Plants now render inside their proper rooms
This commit is contained in:
fullsizemalt 2025-12-18 15:52:05 -08:00
parent 71f1e23ff3
commit d56b7f0b11
4 changed files with 62 additions and 26 deletions

View file

@ -6,6 +6,9 @@ import { RoomObject } from './RoomObject';
import { Beacon } from './Beacon';
import { PlantPosition, VisMode } from './types';
// Convert pixel coordinates to world units (same as RoomObject/SmartRack)
const SCALE = 0.1;
interface FacilitySceneProps {
data: Floor3DData;
visMode: VisMode;
@ -30,6 +33,12 @@ export function FacilityScene({
const controlsRef = useRef<CameraControls>(null);
const [keysPressed, setKeysPressed] = useState<Record<string, boolean>>({});
// Scale floor dimensions for centering
const scaledFloor = {
width: data.floor.width * SCALE,
height: data.floor.height * SCALE,
};
useEffect(() => {
if (controlsRef.current) onControlsReady(controlsRef.current);
}, [controlsRef.current, onControlsReady]);
@ -88,7 +97,7 @@ export function FacilityScene({
shadow-mapSize={[2048, 2048]}
/>
<group position={[-data.floor.width / 2, 0, -data.floor.height / 2]}>
<group position={[-scaledFloor.width / 2, 0, -scaledFloor.height / 2]}>
{data.rooms.map((room: Room3D) => (
<RoomObject
key={room.id}

View file

@ -50,9 +50,13 @@ export function PlantSystem({
let color: string;
switch (visMode) {
case 'STANDARD':
color = pos.plant?.stage === 'FLOWER' ? COLORS.FLOWER :
pos.plant?.stage === 'DRYING' ? COLORS.DRY :
pos.plant?.stage === 'CURE' ? COLORS.CURE : COLORS.VEG;
// Match actual BatchStage enum values from schema
const stage = pos.plant?.stage;
color = (stage === 'FLOWERING') ? COLORS.FLOWER :
(stage === 'DRYING') ? COLORS.DRY :
(stage === 'CURING') ? COLORS.CURE :
(stage === 'CLONE_IN' || stage === 'VEGETATIVE') ? COLORS.VEG :
COLORS.VEG; // Default fallback
break;
case 'HEALTH':
const status = getMockPlantHealth(pos.plant!.id);

View file

@ -4,6 +4,9 @@ import type { Room3D } from '../../lib/layoutApi';
import { PlantPosition, VisMode, COLORS } from './types';
import { SmartRack } from './SmartRack';
// Convert pixel coordinates to world units (same as SmartRack)
const SCALE = 0.1;
// Mock environment data
const getMockRoomEnv = (roomName: string) => {
const hash = roomName.split('').reduce((a, b) => a + b.charCodeAt(0), 0);
@ -30,6 +33,14 @@ interface RoomObjectProps {
export function RoomObject({ room, visMode, onPlantClick, highlightedTags, dimMode }: RoomObjectProps) {
const env = getMockRoomEnv(room.name);
// Scale room dimensions to world units
const scaledRoom = {
posX: room.posX * SCALE,
posY: room.posY * SCALE,
width: room.width * SCALE,
height: room.height * SCALE,
};
let floorColor: string = COLORS.ROOM_FLOOR;
if (visMode === 'TEMP') {
const t = (env.temp - 65) / 20;
@ -44,11 +55,11 @@ export function RoomObject({ room, visMode, onPlantClick, highlightedTags, dimMo
}
return (
<group position={[room.posX, 0, room.posY]}>
<group position={[scaledRoom.posX, 0, scaledRoom.posY]}>
<Text
position={[room.width / 2, 0.05, room.height / 2]}
position={[scaledRoom.width / 2, 0.05, scaledRoom.height / 2]}
rotation={[-Math.PI / 2, 0, 0]}
fontSize={Math.min(room.width, room.height) / 6}
fontSize={Math.min(scaledRoom.width, scaledRoom.height) / 6}
color="#f8fafc"
fillOpacity={0.7}
anchorX="center"
@ -59,9 +70,9 @@ export function RoomObject({ room, visMode, onPlantClick, highlightedTags, dimMo
{(visMode === 'TEMP' || visMode === 'HUMIDITY') && (
<Text
position={[room.width / 2, 4, room.height / 2]}
position={[scaledRoom.width / 2, 4, scaledRoom.height / 2]}
rotation={[-Math.PI / 4, 0, 0]}
fontSize={4}
fontSize={Math.min(4, scaledRoom.width / 4)}
color="#fff"
outlineColor="#000"
outlineWidth={0.15}
@ -71,8 +82,8 @@ export function RoomObject({ room, visMode, onPlantClick, highlightedTags, dimMo
</Text>
)}
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[room.width / 2, 0, room.height / 2]} receiveShadow>
<planeGeometry args={[room.width, room.height]} />
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[scaledRoom.width / 2, 0, scaledRoom.height / 2]} receiveShadow>
<planeGeometry args={[scaledRoom.width, scaledRoom.height]} />
<meshStandardMaterial
color={floorColor}
roughness={0.95}
@ -82,8 +93,8 @@ export function RoomObject({ room, visMode, onPlantClick, highlightedTags, dimMo
/>
</mesh>
<lineSegments position={[room.width / 2, 0.02, room.height / 2]} rotation={[-Math.PI / 2, 0, 0]}>
<edgesGeometry args={[new THREE.PlaneGeometry(room.width, room.height)]} />
<lineSegments position={[scaledRoom.width / 2, 0.02, scaledRoom.height / 2]} rotation={[-Math.PI / 2, 0, 0]}>
<edgesGeometry args={[new THREE.PlaneGeometry(scaledRoom.width, scaledRoom.height)]} />
<lineBasicMaterial color="#64748b" />
</lineSegments>

View file

@ -5,6 +5,9 @@ import type { Section3D, Position3D } from '../../lib/layoutApi';
import { PlantPosition, VisMode } from './types';
import { PlantSystem } from './PlantSystem';
// Convert pixel coordinates to world units (DB stores pixels, 3D uses meters/units)
const SCALE = 0.1; // 1 pixel = 0.1 world units (so 700px = 70 units)
interface SmartRackProps {
section: Section3D;
visMode: VisMode;
@ -14,27 +17,36 @@ interface SmartRackProps {
}
export function SmartRack({ section, visMode, onPlantClick, highlightedTags, dimMode }: SmartRackProps) {
// Scale section dimensions to world units
const scaledSection = {
posX: section.posX * SCALE,
posY: section.posY * SCALE,
width: section.width * SCALE,
height: section.height * SCALE,
};
const positions: PlantPosition[] = useMemo(() => {
const spacing = 0.5;
const plantSpacing = 0.5; // Spacing between plants in world units
return section.positions.map((pos: Position3D) => ({
...pos,
x: section.posX + (pos.column * spacing),
z: section.posY + (pos.row * spacing),
// Position plants within the scaled section bounds
x: scaledSection.posX + (pos.column * plantSpacing),
z: scaledSection.posY + (pos.row * plantSpacing),
y: 0.4 + (pos.tier * 0.6),
}));
}, [section]);
}, [section, scaledSection]);
const distinctTiers = [...new Set(positions.map(p => p.tier))].sort((a, b) => a - b);
const distinctRows = [...new Set(positions.map(p => p.row))].sort((a, b) => a - b);
const distinctCols = [...new Set(positions.map(p => p.column))].sort((a, b) => a - b);
const spacing = 0.5;
const plantSpacing = 0.5;
return (
<group>
{visMode === 'STANDARD' && (
<Text
position={[section.posX + section.width / 2, 3, section.posY + section.height / 2]}
position={[scaledSection.posX + scaledSection.width / 2, 3, scaledSection.posY + scaledSection.height / 2]}
fontSize={0.8}
color="#94a3b8"
anchorX="center"
@ -50,14 +62,14 @@ export function SmartRack({ section, visMode, onPlantClick, highlightedTags, dim
<mesh
key={`shelf-${tier}`}
position={[
section.posX + section.width / 2,
scaledSection.posX + scaledSection.width / 2,
0.35 + (tier * 0.6),
section.posY + section.height / 2
scaledSection.posY + scaledSection.height / 2
]}
rotation={[-Math.PI / 2, 0, 0]}
receiveShadow
>
<planeGeometry args={[section.width * 0.95, section.height * 0.95]} />
<planeGeometry args={[scaledSection.width * 0.95, scaledSection.height * 0.95]} />
<meshStandardMaterial
color="#64748b"
roughness={0.3}
@ -72,9 +84,9 @@ export function SmartRack({ section, visMode, onPlantClick, highlightedTags, dim
<mesh
key={`support-${xOffset}-${zOffset}`}
position={[
section.posX + (xOffset * section.width),
scaledSection.posX + (xOffset * scaledSection.width),
(distinctTiers.length * 0.6) / 2 + 0.2,
section.posY + (zOffset * section.height)
scaledSection.posY + (zOffset * scaledSection.height)
]}
>
<boxGeometry args={[0.08, distinctTiers.length * 0.6 + 0.3, 0.08]} />
@ -86,7 +98,7 @@ export function SmartRack({ section, visMode, onPlantClick, highlightedTags, dim
{visMode === 'STANDARD' && distinctRows.map(row => (
<Text
key={`row-${row}`}
position={[section.posX - 0.3, 0.8, section.posY + row * spacing]}
position={[scaledSection.posX - 0.3, 0.8, scaledSection.posY + row * plantSpacing]}
rotation={[-Math.PI / 2, 0, 0]}
fontSize={0.18}
color="#64748b"
@ -99,7 +111,7 @@ export function SmartRack({ section, visMode, onPlantClick, highlightedTags, dim
{visMode === 'STANDARD' && distinctCols.map(col => (
<Text
key={`col-${col}`}
position={[section.posX + col * spacing, 0.15, section.posY + section.height + 0.2]}
position={[scaledSection.posX + col * plantSpacing, 0.15, scaledSection.posY + scaledSection.height + 0.2]}
rotation={[-Math.PI / 2, 0, 0]}
fontSize={0.18}
color="#64748b"