Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/shared/src/schemas/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export const MachineSettingsSchema = z.object({
toolSpinup: ToolSpinupSchema.optional(),
spindleWarmup: SpindleWarmupSchema.optional(),
autoSwitchToMonitor: z.boolean().default(true),
visualizerMode: z.enum(['machine', 'wcs']).default('machine'),
});

// =============================================================================
Expand Down
52 changes: 17 additions & 35 deletions apps/web/src/routes/Monitor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import {
useFeedrate,
} from '@/store/hooks'
import { machineStateSync } from '@/services/machineStateSync'
import { processGCode } from '@/lib/gcodeVisualizer'
import { Vector3 } from 'three'
import { machineToThree, type MachineLimits } from '@/lib/coordinates'
import type { HomingCorner } from '@/lib/machineLimits'
Expand Down Expand Up @@ -253,11 +252,12 @@ function VisualizerCameraView({ machinePosition, processedLines }: VisualizerCam
const [viewMode, setViewMode] = useState<ViewMode>('side-by-side')
const { data: settings } = useGetSettingsQuery()
const dispatch = useAppDispatch()

const vizMode = settings?.machine?.visualizerMode ?? 'machine'

// Get shared machine state for positions
const workPosition = useWorkPosition()
const connectedPort = useConnectedPort() // Use Redux state instead of settings

// G-code state for visualizer
const [loadedGcode, setLoadedGcode] = useState<{ name: string; gcode: string } | null>(null)
const [modelOffset, setModelOffset] = useState<{ x: number; y: number; z: number } | null>(null)
Expand Down Expand Up @@ -401,43 +401,23 @@ function VisualizerCameraView({ machinePosition, processedLines }: VisualizerCam
return
}

const result = processGCode(loadedGcode.gcode)

if (!result?.firstVertex) {
return
}

const limits: MachineLimits = settings.machine.limits
const homingCorner: HomingCorner = settings.machine.homingCorner ?? 'front-left'

// Calculate work offset: WorkOffset = MPos - WPos
const workOffset = {
x: machinePosition.x - workPosition.x,
y: machinePosition.y - workPosition.y,
z: machinePosition.z - workPosition.z
}

// WCS origin (0,0,0) in machine coordinates is the work offset
// Convert WCS origin to Three.js coordinates
// G-code coordinates are in WCS, so the offset to map them into Three.js space
// is simply the Three.js position of WCS (0,0,0)
const wcsOriginThree = machineToThree(workOffset, limits, homingCorner)

// G-code coordinates from gcode-toolpath are in WCS coordinates
// They are currently being rendered directly as Three.js coordinates (no conversion)
// So the G-code origin location in Three.js is just the firstVertex value
const gcodeOriginThree = {
x: result.firstVertex.x,
y: result.firstVertex.y,
z: result.firstVertex.z
}

// Calculate offset to move G-code origin to WCS origin location
const offset = new Vector3(
wcsOriginThree.x - gcodeOriginThree.x,
wcsOriginThree.y - gcodeOriginThree.y,
wcsOriginThree.z - gcodeOriginThree.z
)

const offsetValue = { x: offset.x, y: offset.y, z: offset.z }

const offsetValue = { x: wcsOriginThree.x, y: wcsOriginThree.y, z: wcsOriginThree.z }
setModelOffset(offsetValue)
placedGcodeRef.current = loadedGcode.name
// Save offset to localStorage for persistence across views
Expand Down Expand Up @@ -467,14 +447,15 @@ function VisualizerCameraView({ machinePosition, processedLines }: VisualizerCam
${viewMode === 'side-by-side' ? 'w-1/2' : 'w-full'}
flex-1 relative
`}>
<VisualizerScene
gcode={loadedGcode?.gcode}
<VisualizerScene
gcode={loadedGcode?.gcode}
limits={settings?.machine?.limits}
view={view}
viewKey={viewKey}
machinePosition={machinePosition}
modelOffset={modelOffset ? new Vector3(modelOffset.x, modelOffset.y, modelOffset.z) : undefined}
modelOffset={vizMode === 'machine' && modelOffset ? new Vector3(modelOffset.x, modelOffset.y, modelOffset.z) : undefined}
processedLines={processedLines}
vizMode={vizMode}
/>
{/* PiP camera overlay when visualizer is full screen */}
{viewMode === 'pip-visual' && (
Expand Down Expand Up @@ -504,14 +485,15 @@ function VisualizerCameraView({ machinePosition, processedLines }: VisualizerCam
{t('3D View')}
</div>
<div className="w-full h-full">
<VisualizerScene
gcode={loadedGcode?.gcode}
<VisualizerScene
gcode={loadedGcode?.gcode}
limits={settings?.machine?.limits}
view={view}
viewKey={viewKey}
machinePosition={machinePosition}
modelOffset={modelOffset ? new Vector3(modelOffset.x, modelOffset.y, modelOffset.z) : undefined}
modelOffset={vizMode === 'machine' && modelOffset ? new Vector3(modelOffset.x, modelOffset.y, modelOffset.z) : undefined}
processedLines={processedLines}
vizMode={vizMode}
/>
</div>
</div>
Expand Down
14 changes: 13 additions & 1 deletion apps/web/src/routes/Settings/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ const DEFAULT_MACHINE_CONFIG: MachineConfig = {
zmax: 0,
},
homingCorner: 'front-left', // Most common homing position
visualizerMode: 'machine' as const,
autoSwitchToMonitorEnabled: true, // Enabled by default
toolSpinupDelayEnabled: true, // Enabled by default
toolSpinupDelaySeconds: 5, // 5 seconds default delay
Expand Down Expand Up @@ -427,6 +428,9 @@ export default function Settings() {
}

// Controller settings
if (settings.machine?.visualizerMode !== undefined) {
setMachineConfig(prev => ({ ...prev, visualizerMode: settings.machine!.visualizerMode! }))
}
if (settings.machine?.autoSwitchToMonitor !== undefined) {
setMachineConfig(prev => ({ ...prev, autoSwitchToMonitorEnabled: settings.machine!.autoSwitchToMonitor! }))
}
Expand Down Expand Up @@ -940,6 +944,7 @@ export default function Settings() {
name: importedSettings.machine?.name ?? prev.name,
limits: importedSettings.machine?.limits ?? prev.limits,
homingCorner: importedSettings.machine?.homingCorner ?? prev.homingCorner,
visualizerMode: importedSettings.machine?.visualizerMode ?? prev.visualizerMode,
autoSwitchToMonitorEnabled: importedSettings.machine?.autoSwitchToMonitor ?? prev.autoSwitchToMonitorEnabled,
toolSpinupDelayEnabled: importedSettings.machine?.toolSpinup?.enabled ?? prev.toolSpinupDelayEnabled,
toolSpinupDelaySeconds: importedSettings.machine?.toolSpinup?.delaySeconds ?? prev.toolSpinupDelaySeconds,
Expand Down Expand Up @@ -1119,6 +1124,7 @@ export default function Settings() {
machine: {
name: DEFAULT_MACHINE_CONFIG.name,
limits: DEFAULT_MACHINE_CONFIG.limits,
visualizerMode: DEFAULT_MACHINE_CONFIG.visualizerMode,
autoSwitchToMonitor: DEFAULT_MACHINE_CONFIG.autoSwitchToMonitorEnabled,
toolSpinup: {
enabled: DEFAULT_MACHINE_CONFIG.toolSpinupDelayEnabled,
Expand Down Expand Up @@ -1304,6 +1310,9 @@ export default function Settings() {
if (changes.homingCorner !== undefined) {
updated.homingCorner = changes.homingCorner
}
if (changes.visualizerMode !== undefined) {
updated.visualizerMode = changes.visualizerMode
}
if (changes.autoSwitchToMonitorEnabled !== undefined) {
updated.autoSwitchToMonitorEnabled = changes.autoSwitchToMonitorEnabled
}
Expand Down Expand Up @@ -1333,11 +1342,14 @@ export default function Settings() {

// Save to backend
const saveData: PartialSettings = {}
if (changes.name !== undefined || changes.limits || changes.homingCorner !== undefined || changes.autoSwitchToMonitorEnabled !== undefined) {
if (changes.name !== undefined || changes.limits || changes.homingCorner !== undefined || changes.visualizerMode !== undefined || changes.autoSwitchToMonitorEnabled !== undefined) {
saveData.machine = saveData.machine || {}
if (changes.name !== undefined) saveData.machine.name = changes.name
if (changes.limits) saveData.machine.limits = changes.limits
if (changes.homingCorner !== undefined) saveData.machine.homingCorner = changes.homingCorner
if (changes.visualizerMode !== undefined) {
saveData.machine.visualizerMode = changes.visualizerMode
}
if (changes.autoSwitchToMonitorEnabled !== undefined) {
saveData.machine.autoSwitchToMonitor = changes.autoSwitchToMonitorEnabled
}
Expand Down
27 changes: 27 additions & 0 deletions apps/web/src/routes/Settings/sections/MachineSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export interface MachineConfig {
zmax: number
}
homingCorner?: HomingCorner // Optional: inferred if not provided
visualizerMode: 'machine' | 'wcs'
autoSwitchToMonitorEnabled: boolean
toolSpinupDelayEnabled: boolean
toolSpinupDelaySeconds: number
Expand Down Expand Up @@ -423,6 +424,32 @@ export function MachineSection({

{/* Controller Behavior */}
<div className="space-y-3 pt-4">
<SettingsField
label={t('Visualizer mode')}
description={t('Machine mode shows the full work envelope. WCS mode shows toolpaths relative to the work coordinate origin.')}
tooltip={t('Machine mode displays the full machine envelope with toolpaths positioned in absolute machine coordinates. WCS mode shows toolpaths relative to the work coordinate system origin (G54, etc.) without the machine envelope.')}
horizontal
>
<div className="flex gap-1">
<Button
variant={config.visualizerMode === 'machine' ? 'default' : 'outline'}
size="sm"
className="h-8 text-xs"
onClick={() => onConfigChange({ visualizerMode: 'machine' })}
>
{t('Machine')}
</Button>
<Button
variant={config.visualizerMode === 'wcs' ? 'default' : 'outline'}
size="sm"
className="h-8 text-xs"
onClick={() => onConfigChange({ visualizerMode: 'wcs' })}
>
{t('WCS')}
</Button>
</div>
</SettingsField>

<SettingsField
label={t('Auto-switch to Monitor when jobs start')}
description={t('Automatically navigate to the Monitor tab when starting a job from Setup')}
Expand Down
Loading
Loading