Overhaul printer control 1.

This commit is contained in:
Tom Butcher 2025-08-31 21:29:00 +01:00
parent 23b5300a2c
commit 4d01a6a04f
6 changed files with 665 additions and 1079 deletions

File diff suppressed because it is too large Load Diff

View File

@ -12,7 +12,8 @@ const breadcrumbNameMap = {
developer: 'Developer', developer: 'Developer',
overview: 'Overview', overview: 'Overview',
info: 'Info', info: 'Info',
design: 'Design' design: 'Design',
control: 'Control'
} }
const mainSections = ['production', 'inventory', 'management', 'developer'] const mainSections = ['production', 'inventory', 'management', 'developer']

View File

@ -8,11 +8,21 @@ const ObjectInfo = ({
loading = false, loading = false,
isEditing = false, isEditing = false,
type = 'unknown', type = 'unknown',
showHyperlink,
showLabels = true,
objectData = null, objectData = null,
properties = [], properties = [],
required = undefined, required = undefined,
visibleProperties = {}, visibleProperties = {},
objectPropertyProps = {}, objectPropertyProps = {},
column = {
xs: 1,
sm: 1,
md: 1,
lg: 2,
xl: 2,
xxl: 2
},
...rest ...rest
}) => { }) => {
const allItems = getModelProperties(type) const allItems = getModelProperties(type)
@ -47,33 +57,29 @@ const ObjectInfo = ({
const key = item.name || item.label || idx const key = item.name || item.label || idx
return { return {
key, key,
label: ( label:
<Flex vertical style={{ height: '100%' }} justify='center'> showLabels == true ? (
{item.label} <Flex vertical style={{ height: '100%' }} justify='center'>
</Flex> {item.label}
), </Flex>
) : null,
children: ( children: (
<ObjectProperty <ObjectProperty
{...item} {...item}
{...objectPropertyProps} {...objectPropertyProps}
showHyperlink={showHyperlink}
isEditing={isEditing} isEditing={isEditing}
objectData={objectData} objectData={objectData}
/> />
) ),
span: item?.span || undefined
} }
}) })
return ( return (
<Spin spinning={loading} indicator={<LoadingOutlined />}> <Spin spinning={loading} indicator={<LoadingOutlined />}>
<Descriptions <Descriptions
column={{ column={column}
xs: 1,
sm: 1,
md: 1,
lg: 2,
xl: 2,
xxl: 2
}}
bordered={true} bordered={true}
{...rest} {...rest}
items={descriptionItems} items={descriptionItems}
@ -84,6 +90,9 @@ const ObjectInfo = ({
ObjectInfo.propTypes = { ObjectInfo.propTypes = {
loading: PropTypes.bool, loading: PropTypes.bool,
column: PropTypes.object,
showHyperlink: PropTypes.bool,
showLabels: PropTypes.bool,
indicator: PropTypes.node, indicator: PropTypes.node,
properties: PropTypes.array, properties: PropTypes.array,
bordered: PropTypes.bool, bordered: PropTypes.bool,

View File

@ -1,5 +1,5 @@
// PrinterTemperaturePanel.js // PrinterTemperaturePanel.js
import { useContext, useState, useEffect, useCallback } from 'react' import { useContext, useState, useEffect } from 'react'
import { import {
Progress, Progress,
Typography, Typography,
@ -11,9 +11,10 @@ import {
Button Button
} from 'antd' } from 'antd'
import { LoadingOutlined, CaretLeftOutlined } from '@ant-design/icons' import { LoadingOutlined, CaretLeftOutlined } from '@ant-design/icons'
import { PrintServerContext } from '../context/PrintServerContext'
import styled from 'styled-components' import styled from 'styled-components'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { ApiServerContext } from '../context/ApiServerContext'
import merge from 'lodash/merge'
const { Text } = Typography const { Text } = Typography
const { Panel } = Collapse const { Panel } = Collapse
@ -30,90 +31,69 @@ const CustomCollapse = styled(Collapse)`
` `
const PrinterTemperaturePanel = ({ const PrinterTemperaturePanel = ({
printerId, id,
showHotEndControls = true, showExtruderControls = true,
showHeatedBedControls = true, showBedControls = true,
showHotEnd = true, showExtruder = true,
showHeatedBed = true, showBed = true,
showMoreInfo = true, showMoreInfo = true
shouldUnsubscribe = true
}) => { }) => {
const [temperatureData, setTemperatureData] = useState({ const [temperatureData, setTemperatureData] = useState({
hotEnd: {}, extruder: {
heatedBed: {} current: 0,
target: 0,
power: 0
},
bed: {
current: 0,
target: 0,
power: 0
},
pinda: 0,
ambiant: 0
}) })
const [hotEndTemperature, setHotEndTemperature] = useState( const { subscribeToObjectEvent, connected } = useContext(ApiServerContext)
temperatureData?.hotEnd?.target || 0
)
const [heatedBedTemperature, setHeatedBedTemperature] = useState(
temperatureData?.heatedBed?.target || 0
)
const { printServer } = useContext(PrintServerContext)
const notifyTemperatureStatusUpdate = useCallback((statusUpdate) => { // Sync input values with actual temperature targets
setTemperatureData((prev) => { useEffect(() => {
const temperatureObject = { if (temperatureData.extruder?.target !== undefined) {
...prev setExtruderTarget(temperatureData.extruder.target)
} }
if (statusUpdate?.extruder?.temperature !== undefined) { }, [temperatureData.extruder?.target])
temperatureObject.hotEnd.current = statusUpdate?.extruder?.temperature
}
if (statusUpdate?.heater_bed?.temperature !== undefined) {
temperatureObject.heatedBed.current =
statusUpdate?.heater_bed?.temperature
}
if (statusUpdate?.extruder?.target !== undefined) {
temperatureObject.hotEnd.target = statusUpdate?.extruder?.target
setHotEndTemperature(statusUpdate?.extruder?.target)
}
if (statusUpdate?.heater_bed?.target !== undefined) {
temperatureObject.heatedBed.target = statusUpdate?.heater_bed?.target
setHeatedBedTemperature(statusUpdate?.heater_bed?.target)
}
if (statusUpdate?.extruder?.power !== undefined) {
temperatureObject.hotEnd.power = statusUpdate?.extruder?.power
}
if (statusUpdate?.heater_bed?.power !== undefined) {
temperatureObject.heatedBed.power = statusUpdate?.heater_bed?.power
}
return temperatureObject
})
}, [])
useEffect(() => { useEffect(() => {
const params = { if (temperatureData.bed?.target !== undefined) {
printerId, setBedTarget(temperatureData.bed.target)
objects: {
extruder: null,
heater_bed: null // eslint-disable-line
}
} }
if (printServer?.connected == true) { }, [temperatureData.bed?.target])
printServer.emit('printer.objects.subscribe', params)
printServer.emit('printer.objects.query', params)
printServer.on('notify_status_update', notifyTemperatureStatusUpdate)
}
return () => {
if (printServer && shouldUnsubscribe == true) {
printServer.off('notify_status_update', notifyTemperatureStatusUpdate)
printServer.emit('printer.objects.unsubscribe', params)
}
}
}, [printServer, printerId, notifyTemperatureStatusUpdate, shouldUnsubscribe])
const handleSetTemperatureClick = (target, value) => { useEffect(() => {
if (printServer) { if (id && connected) {
printServer.emit('printer.gcode.script', { const temperatureEventUnsubscribe = subscribeToObjectEvent(
printerId, id,
script: `SET_HEATER_TEMPERATURE HEATER=${target} TARGET=${value}` 'printer',
}) 'temperature',
(event) => {
setTemperatureData((prev) => {
const merged = merge({}, prev, event.data)
return merged
})
}
)
return () => {
if (temperatureEventUnsubscribe) temperatureEventUnsubscribe()
}
}
}, [id, connected])
const [extruderTarget, setExtruderTarget] = useState(0)
const [bedTarget, setBedTarget] = useState(0)
const handleSetTemperature = (data) => {
if (id && connected == true) {
console.log(data)
//sendObjectAction(id, 'printer', { type: 'setTemperature', data })
} }
} }
@ -126,33 +106,32 @@ const PrinterTemperaturePanel = ({
<Flex vertical gap={0}> <Flex vertical gap={0}>
<Text> <Text>
Hot End Power:{' '} Hot End Power:{' '}
{Math.round((temperatureData.hotEnd.power || 0) * 100)}% {Math.round((temperatureData.extruder.power || 0) * 100)}%
</Text> </Text>
<Progress <Progress
percent={(temperatureData.hotEnd.power || 0) * 100} percent={(temperatureData.extruder.power || 0) * 100}
showInfo={false} showInfo={false}
/> />
</Flex> </Flex>
<Flex vertical gap={0}> <Flex vertical gap={0}>
<Text> <Text>
Bed Power:{' '} Bed Power: {Math.round((temperatureData.bed.power || 0) * 100)}%
{Math.round((temperatureData.heatedBed.power || 0) * 100)}%
</Text> </Text>
<Progress <Progress
percent={(temperatureData.heatedBed.power || 0) * 100} percent={(temperatureData.bed.power || 0) * 100}
showInfo={false} showInfo={false}
/> />
</Flex> </Flex>
<Panel> <Panel>
{typeof temperatureData.pindaTemp !== 'undefined' && ( {typeof temperatureData.pinda !== 'undefined' && (
<Flex vertical gap={0}> <Flex vertical gap={0}>
<Text>Pinda Temp: {temperatureData.pindaTemp}°C</Text> <Text>Pinda Temp: {temperatureData.pindaTemp}°C</Text>
</Flex> </Flex>
)} )}
{typeof temperatureData.ambiantActual !== 'undefined' && ( {typeof temperatureData.ambiant !== 'undefined' && (
<Flex vertical gap={0}> <Flex vertical gap={0}>
<Text>Ambient Actual: {temperatureData.ambiantActual}°C</Text> <Text>Ambient Actual: {temperatureData.ambiant}°C</Text>
</Flex> </Flex>
)} )}
</Panel> </Panel>
@ -165,41 +144,41 @@ const PrinterTemperaturePanel = ({
<div style={{ minWidth: 190 }}> <div style={{ minWidth: 190 }}>
{temperatureData ? ( {temperatureData ? (
<Flex vertical gap='middle'> <Flex vertical gap='middle'>
{temperatureData.hotEnd && showHotEnd && ( {temperatureData.extruder && showExtruder && (
<Flex vertical gap={0}> <Flex vertical gap={0}>
<Text> <Text>
Hot End: {temperatureData.hotEnd.current}°C /{' '} Hot End: {temperatureData.extruder.current}°C /{' '}
{temperatureData.hotEnd.target}°C {temperatureData.extruder.target}°C
</Text> </Text>
<Progress <Progress
percent={(temperatureData.hotEnd.target / 300) * 100} percent={(temperatureData.extruder.target / 300) * 100}
strokeColor='#FF392F1D' strokeColor='#FF392F1D'
success={{ success={{
percent: (temperatureData.hotEnd.current / 300) * 100, percent: (temperatureData.extruder.current / 300) * 100,
strokeColor: '#FF3B2F' strokeColor: '#FF3B2F'
}} }}
showInfo={false} showInfo={false}
/> />
{showHotEndControls && ( {showExtruderControls && (
<Space direction='horizontal' style={{ marginTop: 5 }}> <Space direction='horizontal' style={{ marginTop: 5 }}>
<Space.Compact block size='small'> <Space.Compact block size='small'>
<InputNumber <InputNumber
value={hotEndTemperature} value={extruderTarget}
min={0} min={0}
max={300} max={300}
style={{ width: '120px' }} style={{ width: '120px' }}
addonAfter='°C' addonAfter='°C'
onChange={(value) => setHotEndTemperature(value)} onChange={(value) => setExtruderTarget(value || 0)}
onPressEnter={() => onPressEnter={handleSetTemperature({
handleSetTemperatureClick('extruder', hotEndTemperature) extruder: { target: extruderTarget }
} })}
/> />
<Button <Button
type='default' type='default'
style={{ width: 40 }} style={{ width: 40 }}
onClick={() => onClick={handleSetTemperature({
handleSetTemperatureClick('extruder', hotEndTemperature) extruder: { target: extruderTarget }
} })}
> >
Set Set
</Button> </Button>
@ -208,7 +187,9 @@ const PrinterTemperaturePanel = ({
type='default' type='default'
size='small' size='small'
style={{ width: 40 }} style={{ width: 40 }}
onClick={() => handleSetTemperatureClick('extruder', 0)} onClick={() =>
handleSetTemperature({ extruder: { target: 0 } })
}
> >
Off Off
</Button> </Button>
@ -217,50 +198,44 @@ const PrinterTemperaturePanel = ({
</Flex> </Flex>
)} )}
{temperatureData.heatedBed && showHeatedBed && ( {temperatureData.bed && showBed && (
<Flex vertical gap={0}> <Flex vertical gap={0}>
<Text> <Text>
Heated Bed: {temperatureData.heatedBed.current}°C /{' '} Heated Bed: {temperatureData.bed.current}°C /{' '}
{temperatureData.heatedBed.target}°C {temperatureData.bed.target}°C
</Text> </Text>
<Progress <Progress
percent={(temperatureData.heatedBed.target / 300) * 100} percent={(temperatureData.bed.target / 300) * 100}
strokeColor='#FF392F1D' strokeColor='#FF392F1D'
success={{ success={{
percent: (temperatureData.heatedBed.current / 300) * 100, percent: (temperatureData.bed.current / 300) * 100,
strokeColor: '#FF3B2F' strokeColor: '#FF3B2F'
}} }}
showInfo={false} showInfo={false}
/> />
{showHeatedBedControls && ( {showBedControls && (
<Space <Space
direction='horizontal' direction='horizontal'
style={{ marginTop: 5, height: '21px' }} style={{ marginTop: 5, height: '21px' }}
> >
<Space.Compact block size='small'> <Space.Compact block size='small'>
<InputNumber <InputNumber
value={heatedBedTemperature} value={bedTarget}
min={0} min={0}
max={300} max={300}
style={{ width: '120px' }} style={{ width: '120px' }}
addonAfter='°C' addonAfter='°C'
onChange={(value) => setHeatedBedTemperature(value)} onChange={(value) => setBedTarget(value || 0)}
onPressEnter={() => onPressEnter={handleSetTemperature({
handleSetTemperatureClick( bed: { target: bedTarget }
'heater_bed', })}
heatedBedTemperature
)
}
/> />
<Button <Button
type='default' type='default'
style={{ width: 40 }} style={{ width: 40 }}
onClick={() => onClick={handleSetTemperature({
handleSetTemperatureClick( bed: { target: bedTarget }
'heater_bed', })}
heatedBedTemperature
)
}
> >
Set Set
</Button> </Button>
@ -269,7 +244,11 @@ const PrinterTemperaturePanel = ({
type='default' type='default'
size='small' size='small'
style={{ width: 40 }} style={{ width: 40 }}
onClick={() => handleSetTemperatureClick('heater_bed', 0)} onClick={() =>
handleSetTemperature({
bed: { target: 0 }
})
}
> >
Off Off
</Button> </Button>
@ -298,11 +277,11 @@ const PrinterTemperaturePanel = ({
} }
PrinterTemperaturePanel.propTypes = { PrinterTemperaturePanel.propTypes = {
printerId: PropTypes.string.isRequired, id: PropTypes.string.isRequired,
showHotEndControls: PropTypes.bool, showExtruderControls: PropTypes.bool,
showHeatedBedControls: PropTypes.bool, showBedControls: PropTypes.bool,
showHotEnd: PropTypes.bool, showExtruder: PropTypes.bool,
showHeatedBed: PropTypes.bool, showBed: PropTypes.bool,
showMoreInfo: PropTypes.bool, showMoreInfo: PropTypes.bool,
shouldUnsubscribe: PropTypes.bool shouldUnsubscribe: PropTypes.bool
} }

View File

@ -93,7 +93,9 @@ const ApiServerProvider = ({ children }) => {
}) })
newSocket.on('objectUpdate', handleObjectUpdate) newSocket.on('objectUpdate', handleObjectUpdate)
newSocket.on('objectEvent', handleObjectEvent)
newSocket.on('objectNew', handleObjectNew) newSocket.on('objectNew', handleObjectNew)
newSocket.on('objectDelete', handleObjectDelete)
newSocket.on('lockUpdate', handleLockUpdate) newSocket.on('lockUpdate', handleLockUpdate)
newSocket.on('disconnect', () => { newSocket.on('disconnect', () => {
@ -216,6 +218,37 @@ const ApiServerProvider = ({ children }) => {
} }
} }
const handleObjectEvent = async (data) => {
const id = data._id
const objectType = data.objectType
const callbacksRefKey = `${objectType}:${id}:events:${data.event.type}`
logger.debug('Notifying object event:', data)
if (
id &&
objectType &&
subscribedCallbacksRef.current.has(callbacksRefKey)
) {
const callbacks = subscribedCallbacksRef.current.get(callbacksRefKey)
logger.debug(
`Calling ${callbacks.length} callbacks for object:`,
callbacksRefKey
)
callbacks.forEach((callback) => {
try {
callback(data.event)
} catch (error) {
logger.error('Error in object event callback:', error)
}
})
} else {
logger.debug(
`No callbacks found for object: ${callbacksRefKey}, subscribed callbacks:`,
Array.from(subscribedCallbacksRef.current.keys())
)
}
}
const handleObjectNew = async (data) => { const handleObjectNew = async (data) => {
logger.debug('Notifying object new:', data) logger.debug('Notifying object new:', data)
const objectType = data.objectType || 'unknown' const objectType = data.objectType || 'unknown'
@ -241,6 +274,31 @@ const ApiServerProvider = ({ children }) => {
} }
} }
const handleObjectDelete = async (data) => {
logger.debug('Notifying object delete:', data)
const objectType = data.objectType || 'unknown'
if (objectType && subscribedCallbacksRef.current.has(objectType)) {
const callbacks = subscribedCallbacksRef.current.get(objectType)
logger.debug(
`Calling ${callbacks.length} callbacks for type:`,
objectType
)
callbacks.forEach((callback) => {
try {
callback(data.object)
} catch (error) {
logger.error('Error in object new callback:', error)
}
})
} else {
logger.debug(
`No callbacks found for object: ${objectType}, subscribed callbacks:`,
Array.from(subscribedCallbacksRef.current.keys())
)
}
}
const offObjectUpdatesEvent = useCallback((id, objectType, callback) => { const offObjectUpdatesEvent = useCallback((id, objectType, callback) => {
if (socketRef.current && socketRef.current.connected == true) { if (socketRef.current && socketRef.current.connected == true) {
const callbacksRefKey = `${objectType}:${id}` const callbacksRefKey = `${objectType}:${id}`
@ -251,7 +309,10 @@ const ApiServerProvider = ({ children }) => {
.filter((cb) => cb !== callback) .filter((cb) => cb !== callback)
if (callbacks.length === 0) { if (callbacks.length === 0) {
subscribedCallbacksRef.current.delete(callbacksRefKey) subscribedCallbacksRef.current.delete(callbacksRefKey)
socketRef.current.emit('unsubscribe', { id: id, type: objectType }) socketRef.current.emit('unsubscribeObjectUpdate', {
id: id,
objectType: objectType
})
} else { } else {
subscribedCallbacksRef.current.set(callbacksRefKey, callbacks) subscribedCallbacksRef.current.set(callbacksRefKey, callbacks)
} }
@ -262,14 +323,22 @@ const ApiServerProvider = ({ children }) => {
const offObjectTypeUpdatesEvent = useCallback((objectType, callback) => { const offObjectTypeUpdatesEvent = useCallback((objectType, callback) => {
if (socketRef.current && socketRef.current.connected == true) { if (socketRef.current && socketRef.current.connected == true) {
// Remove callback from the subscribed callbacks map // Remove callback from the subscribed callbacks map
console.log('Unsubscribing from type') console.log(
'Unsubscribing from type',
objectType,
subscribedCallbacksRef.current.has(objectType)
)
if (subscribedCallbacksRef.current.has(objectType)) { if (subscribedCallbacksRef.current.has(objectType)) {
const callbacks = subscribedCallbacksRef.current const callbacks = subscribedCallbacksRef.current
.get(objectType) .get(objectType)
.filter((cb) => cb !== callback) .filter((cb) => cb !== callback)
console.log('API: CALLBACKS', callbacks)
if (callbacks.length === 0) { if (callbacks.length === 0) {
subscribedCallbacksRef.current.delete(objectType) subscribedCallbacksRef.current.delete(objectType)
socketRef.current.emit('unsubscribe', { objectType: objectType }) socketRef.current.emit('unsubscribeObjectTypeUpdate', {
objectType: objectType
})
} else { } else {
subscribedCallbacksRef.current.set(objectType, callbacks) subscribedCallbacksRef.current.set(objectType, callbacks)
} }
@ -342,7 +411,7 @@ const ApiServerProvider = ({ children }) => {
} }
} }
) )
logger.debug('Registered update event listener for object:', objectType) logger.debug('Registered type event listener for object:', objectType)
// Return cleanup function // Return cleanup function
return () => offObjectTypeUpdatesEvent(objectType, callback) return () => offObjectTypeUpdatesEvent(objectType, callback)
@ -369,6 +438,84 @@ const ApiServerProvider = ({ children }) => {
} }
}, []) }, [])
const offObjectEventEvent = useCallback(
(id, objectType, eventType, callback) => {
if (socketRef.current && socketRef.current.connected == true) {
const callbacksRefKey = `${objectType}:${id}:events:${eventType}`
// Remove callback from the subscribed callbacks map
if (subscribedCallbacksRef.current.has(callbacksRefKey)) {
const callbacks = subscribedCallbacksRef.current
.get(callbacksRefKey)
.filter((cb) => cb !== callback)
if (callbacks.length === 0) {
subscribedCallbacksRef.current.delete(callbacksRefKey)
socketRef.current.emit('unsubscribeObjectEvent', {
_id: id,
objectType,
eventType
})
} else {
subscribedCallbacksRef.current.set(callbacksRefKey, callbacks)
}
}
}
},
[]
)
const subscribeToObjectEvent = useCallback(
(id, objectType, eventType, callback) => {
if (socketRef.current && socketRef.current.connected == true) {
const callbacksRefKey = `${objectType}:${id}:events:${eventType}`
// Add callback to the subscribed callbacks map immediately
if (!subscribedCallbacksRef.current.has(callbacksRefKey)) {
subscribedCallbacksRef.current.set(callbacksRefKey, [])
}
const callbacksLength =
subscribedCallbacksRef.current.get(callbacksRefKey).length
if (callbacksLength <= 0) {
socketRef.current.emit(
'subscribeToObjectEvent',
{
_id: id,
objectType: objectType,
eventType: eventType
},
(result) => {
if (result.success) {
logger.info(
'Subscribed to event id:',
id,
'objectType:',
objectType,
'eventType:',
eventType
)
}
}
)
}
logger.info(
'Adding event callback id:',
id,
'objectType:',
objectType,
'eventType:',
eventType,
'callbacks length:',
callbacksLength + 1
)
subscribedCallbacksRef.current.get(callbacksRefKey).push(callback)
// Return cleanup function
return () => offObjectEventEvent(id, objectType, eventType, callback)
}
},
[offObjectUpdatesEvent]
)
const subscribeToObjectLock = useCallback( const subscribeToObjectLock = useCallback(
(id, type, callback) => { (id, type, callback) => {
logger.debug('Subscribing to lock for object:', id, 'type:', type) logger.debug('Subscribing to lock for object:', id, 'type:', type)
@ -548,13 +695,6 @@ const ApiServerProvider = ({ children }) => {
} }
}) })
logger.debug('Object updated successfully') logger.debug('Object updated successfully')
if (socketRef.current && socketRef.current.connected == true) {
await socketRef.current.emit('update', {
_id: id,
type: type,
updatedAt: response.data.updatedAt
})
}
return response.data return response.data
} catch (err) { } catch (err) {
console.error(err) console.error(err)
@ -577,13 +717,6 @@ const ApiServerProvider = ({ children }) => {
} }
}) })
logger.debug('Object deleted successfully') logger.debug('Object deleted successfully')
if (socketRef.current && socketRef.current.connected == true) {
await socketRef.current.emit('update', {
_id: id,
type: type,
updatedAt: response.data.updatedAt
})
}
return response.data return response.data
} catch (err) { } catch (err) {
console.error(err) console.error(err)
@ -649,7 +782,7 @@ const ApiServerProvider = ({ children }) => {
try { try {
const response = await axios.get(`${config.backendUrl}/notes`, { const response = await axios.get(`${config.backendUrl}/notes`, {
params: { params: {
parent: parentId, 'parent._id': parentId,
sort: 'createdAt', sort: 'createdAt',
order: 'ascend' order: 'ascend'
}, },
@ -755,6 +888,7 @@ const ApiServerProvider = ({ children }) => {
createObject, createObject,
deleteObject, deleteObject,
subscribeToObjectUpdates, subscribeToObjectUpdates,
subscribeToObjectEvent,
subscribeToObjectTypeUpdates, subscribeToObjectTypeUpdates,
subscribeToObjectLock, subscribeToObjectLock,
fetchObject, fetchObject,

View File

@ -143,6 +143,21 @@ export const Printer = {
type: 'text', type: 'text',
required: false, required: false,
readOnly: true readOnly: true
},
{
name: 'currentFilamentStock',
label: 'Filament Stock',
type: 'object',
objectType: 'filamentStock',
required: true
},
{
name: 'currentFilamentStock._id',
label: 'Filament Stock ID',
type: 'id',
objectType: 'filamentStock',
showHyperlink: true,
readOnly: true
} }
] ]
} }