Add Filament and Printer Profile components with associated functionality

- Introduced FilamentProfiles and PrinterProfiles components for managing filament and printer profiles, respectively.
- Implemented modal dialogs for creating and editing profiles, enhancing user interaction.
- Added ObjectTable for displaying profiles with action handling for edit and delete operations.
- Integrated context for API server interactions and improved state management for profile visibility and actions.
- Created detailed info components for both filament and printer profiles, allowing for comprehensive data management and display.
This commit is contained in:
Tom Butcher 2026-07-20 02:10:41 +01:00
parent 830cf5224a
commit def9b9aefb
8 changed files with 4118 additions and 0 deletions

View File

@ -0,0 +1,144 @@
import { useContext, useRef, useState } from 'react'
import { Button, Dropdown, Flex, Modal, Space } from 'antd'
import NewFilamentProfile, {
EditFilamentProfile
} from './FilamentProfiles/NewFilamentProfile'
import ColumnViewButton from '../common/ColumnViewButton'
import ExportListButton from '../common/ExportListButton'
import FilterSidebarButton from '../common/FilterSidebarButton'
import ObjectTable from '../common/ObjectTable'
import ObjectTableViewButton from '../common/ObjectTableViewButton'
import { ApiServerContext } from '../context/ApiServerContext'
import PlusIcon from '../../Icons/PlusIcon'
import ReloadIcon from '../../Icons/ReloadIcon'
import useColumnVisibility from '../hooks/useColumnVisibility'
import useFilterSidebarVisibility from '../hooks/useFilterSidebarVisibility'
import useViewMode from '../hooks/useViewMode'
const FilamentProfiles = () => {
const { deleteObject } = useContext(ApiServerContext)
const [profileModal, setProfileModal] = useState({
open: false,
profileId: null
})
const tableRef = useRef()
const [viewMode, setViewMode] = useViewMode('FilamentProfiles')
const [columnVisibility, setColumnVisibility] =
useColumnVisibility('filamentProfile')
const [showFilterSidebar, setShowFilterSidebar] =
useFilterSidebarVisibility('FilamentProfiles')
const actionItems = {
items: [
{
label: 'New Filament Profile',
key: 'newFilamentProfile',
icon: <PlusIcon />
},
{ type: 'divider' },
{
label: 'Reload List',
key: 'reloadList',
icon: <ReloadIcon />
}
],
onClick: ({ key }) => {
if (key === 'reloadList') {
tableRef.current?.reload()
} else if (key === 'newFilamentProfile') {
setProfileModal({ open: true, profileId: null })
}
}
}
const handleRowAction = (action, profile) => {
if (action.name === 'edit') {
setProfileModal({ open: true, profileId: profile._id })
return true
}
if (action.name === 'delete') {
Modal.confirm({
title: 'Delete filament profile?',
content: `This will permanently delete ${profile.name || profile._reference}.`,
okText: 'Delete',
okType: 'danger',
onOk: async () => {
await deleteObject(profile._id, 'filamentProfile')
await tableRef.current?.reload()
}
})
return true
}
return false
}
return (
<>
<Flex vertical={'true'} gap='large' className='h-100'>
<Flex justify={'space-between'}>
<Space>
<Dropdown menu={actionItems}>
<Button>Actions</Button>
</Dropdown>
<ColumnViewButton
type='filamentProfile'
visibleState={columnVisibility}
updateVisibleState={setColumnVisibility}
/>
<ExportListButton objectType='filamentProfile' />
</Space>
<Space>
<FilterSidebarButton
active={showFilterSidebar}
onClick={() => setShowFilterSidebar(!showFilterSidebar)}
/>
<ObjectTableViewButton
viewMode={viewMode}
setViewMode={setViewMode}
/>
</Space>
</Flex>
<ObjectTable
ref={tableRef}
type='filamentProfile'
cards={viewMode === 'cards'}
visibleColumns={columnVisibility}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
onRowAction={handleRowAction}
/>
</Flex>
<Modal
open={profileModal.open}
footer={null}
width={800}
onCancel={() => setProfileModal({ open: false, profileId: null })}
destroyOnHidden
>
{profileModal.open &&
(profileModal.profileId ? (
<EditFilamentProfile
profileId={profileModal.profileId}
onOk={() => {
setProfileModal({ open: false, profileId: null })
tableRef.current?.reload()
}}
/>
) : (
<NewFilamentProfile
onOk={() => {
setProfileModal({ open: false, profileId: null })
tableRef.current?.reload()
}}
/>
))}
</Modal>
</>
)
}
export default FilamentProfiles

View File

@ -0,0 +1,529 @@
import { useRef, useState } from 'react'
import { useLocation } from 'react-router-dom'
import { Card, Flex, Space } from 'antd'
import { LoadingOutlined } from '@ant-design/icons'
import ActionHandler from '../../common/ActionHandler'
import AuditLogIcon from '../../../Icons/AuditLogIcon'
import DocumentPrintButton from '../../common/DocumentPrintButton'
import EditButtons from '../../common/EditButtons'
import FilamentIcon from '../../../Icons/FilamentIcon'
import GCodeFileIcon from '../../../Icons/GCodeFileIcon'
import HotEndIcon from '../../../Icons/HotEndIcon'
import InfoCircleIcon from '../../../Icons/InfoCircleIcon'
import InfoCollapse from '../../common/InfoCollapse'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder'
import LockIndicator from '../../common/LockIndicator'
import MultiMotionIcon from '../../../Icons/MultiMotionIcon'
import NoteIcon from '../../../Icons/NoteIcon'
import NotesPanel from '../../common/NotesPanel'
import ObjectActions from '../../common/ObjectActions'
import ObjectForm from '../../common/ObjectForm'
import ObjectInfo from '../../common/ObjectInfo'
import ObjectTable from '../../common/ObjectTable'
import RulerIcon from '../../../Icons/RulerIcon'
import ScrollBox from '../../common/ScrollBox'
import SettingsIcon from '../../../Icons/SettingsIcon'
import UserNotifierToggle from '../../common/UserNotifierToggle'
import ViewButton from '../../common/ViewButton'
import useCollapseState from '../../hooks/useCollapseState'
import {
FILAMENT_PROFILE_BED_TEMPERATURE_PROPERTIES,
FILAMENT_PROFILE_COMPATIBILITY_PROPERTIES,
FILAMENT_PROFILE_COOLING_PROPERTIES,
FILAMENT_PROFILE_EXHAUST_FAN_PROPERTIES,
FILAMENT_PROFILE_FLOW_PRESSURE_PROPERTIES,
FILAMENT_PROFILE_GCODE_PROPERTIES,
FILAMENT_PROFILE_LOADING_PROPERTIES,
FILAMENT_PROFILE_MATERIAL_PROPERTIES,
FILAMENT_PROFILE_RETRACTION_PROPERTIES,
FILAMENT_PROFILE_SHRINKAGE_DRYING_PROPERTIES,
FILAMENT_PROFILE_SPEED_PROPERTIES,
FILAMENT_PROFILE_TEMPERATURE_PROPERTIES,
FILAMENT_PROFILE_WIPE_TOWER_PROPERTIES
} from '../../../../database/models/FilamentProfile'
const toVisibleProperties = (propertyNames) =>
Object.fromEntries(propertyNames.map((name) => [name, true]))
const FilamentProfileInfo = () => {
const location = useLocation()
const objectFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const filamentProfileId = new URLSearchParams(location.search).get(
'filamentProfileId'
)
const [collapseState, updateCollapseState] = useCollapseState(
'FilamentProfileInfo',
{
info: true,
material: true,
flowPressure: true,
temperature: true,
bedTemperature: true,
speed: true,
cooling: true,
exhaustFan: true,
retraction: true,
loading: true,
wipeTower: true,
gCodeSettings: true,
compatibility: true,
shrinkageDrying: true,
notes: true,
auditLogs: false
}
)
const [objectFormState, setObjectFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
lock: null,
loading: false,
objectData: {}
})
const actions = {
edit: () => {
objectFormRef.current?.startEditing?.()
return false
},
cancelEdit: () => {
objectFormRef.current?.cancelEditing?.()
return true
},
finishEdit: () => {
objectFormRef.current?.handleUpdate?.()
return true
},
delete: () => {
objectFormRef.current?.handleDelete?.()
return true
}
}
return (
<Flex
gap='large'
vertical='true'
style={{ maxHeight: '100%', minHeight: 0 }}
>
<Flex justify='space-between'>
<Space size='middle'>
<Space size='small'>
<ObjectActions
type='filamentProfile'
id={filamentProfileId}
disabled={objectFormState.loading}
objectData={objectFormState.objectData}
/>
<ViewButton
disabled={objectFormState.loading}
items={[
{ key: 'info', label: 'Filament Profile Information' },
{ key: 'material', label: 'Material' },
{ key: 'flowPressure', label: 'Flow & Pressure' },
{ key: 'temperature', label: 'Temperature' },
{ key: 'bedTemperature', label: 'Bed Temperatures' },
{ key: 'speed', label: 'Speed' },
{ key: 'cooling', label: 'Part Cooling Fan' },
{ key: 'exhaustFan', label: 'Exhaust Fan' },
{ key: 'retraction', label: 'Retraction Overrides' },
{ key: 'loading', label: 'Loading & Multi-tool' },
{ key: 'wipeTower', label: 'Wipe Tower & Flushing' },
{ key: 'gCodeSettings', label: 'G-Code Settings' },
{ key: 'compatibility', label: 'Compatibility' },
{ key: 'shrinkageDrying', label: 'Shrinkage & Drying' },
{ key: 'notes', label: 'Notes' },
{ key: 'auditLogs', label: 'Audit Logs' }
]}
visibleState={collapseState}
updateVisibleState={updateCollapseState}
/>
<UserNotifierToggle
type='filamentProfile'
objectData={objectFormState.objectData}
disabled={objectFormState.loading}
/>
<DocumentPrintButton
type='filamentProfile'
objectData={objectFormState.objectData}
disabled={objectFormState.loading}
/>
</Space>
<LockIndicator lock={objectFormState.lock} />
</Space>
<EditButtons
isEditing={objectFormState.isEditing}
handleUpdate={() => actionHandlerRef.current.callAction('finishEdit')}
cancelEditing={() =>
actionHandlerRef.current.callAction('cancelEdit')
}
startEditing={() => actionHandlerRef.current.callAction('edit')}
editLoading={objectFormState.editLoading}
formValid={objectFormState.formValid}
disabled={objectFormState.lock?.locked || objectFormState.loading}
loading={objectFormState.editLoading}
/>
</Flex>
<ScrollBox>
<Flex vertical gap='large'>
<ActionHandler
actions={actions}
loading={objectFormState.loading}
ref={actionHandlerRef}
>
<ObjectForm
id={filamentProfileId}
type='filamentProfile'
ref={objectFormRef}
onStateChange={(state) =>
setObjectFormState((current) => ({ ...current, ...state }))
}
>
{({ loading, isEditing, objectData }) => (
<Flex vertical gap='large'>
<InfoCollapse
title='Filament Profile Information'
icon={<InfoCircleIcon />}
active={collapseState.info}
onToggle={(expanded) =>
updateCollapseState('info', expanded)
}
collapseKey='info'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='filamentProfile'
objectData={objectData}
labelWidth='140px'
visibleProperties={{
_id: true,
name: true,
_reference: true,
filamentType: true,
filament: true,
createdAt: true,
updatedAt: true
}}
/>
</InfoCollapse>
<InfoCollapse
title='Material'
icon={<FilamentIcon />}
active={collapseState.material}
onToggle={(expanded) =>
updateCollapseState('material', expanded)
}
collapseKey='material'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='filamentProfile'
objectData={objectData}
labelWidth='220px'
visibleProperties={toVisibleProperties(
FILAMENT_PROFILE_MATERIAL_PROPERTIES
)}
/>
</InfoCollapse>
<InfoCollapse
title='Flow & Pressure'
icon={<SettingsIcon />}
active={collapseState.flowPressure}
onToggle={(expanded) =>
updateCollapseState('flowPressure', expanded)
}
collapseKey='flowPressure'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='filamentProfile'
objectData={objectData}
labelWidth='325px'
visibleProperties={toVisibleProperties(
FILAMENT_PROFILE_FLOW_PRESSURE_PROPERTIES
)}
/>
</InfoCollapse>
<InfoCollapse
title='Temperature'
icon={<HotEndIcon />}
active={collapseState.temperature}
onToggle={(expanded) =>
updateCollapseState('temperature', expanded)
}
collapseKey='temperature'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='filamentProfile'
objectData={objectData}
labelWidth='280px'
visibleProperties={toVisibleProperties(
FILAMENT_PROFILE_TEMPERATURE_PROPERTIES
)}
/>
</InfoCollapse>
<InfoCollapse
title='Bed Temperatures'
icon={<RulerIcon />}
active={collapseState.bedTemperature}
onToggle={(expanded) =>
updateCollapseState('bedTemperature', expanded)
}
collapseKey='bedTemperature'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='filamentProfile'
objectData={objectData}
labelWidth='280px'
visibleProperties={toVisibleProperties(
FILAMENT_PROFILE_BED_TEMPERATURE_PROPERTIES
)}
/>
</InfoCollapse>
<InfoCollapse
title='Speed'
icon={<MultiMotionIcon />}
active={collapseState.speed}
onToggle={(expanded) =>
updateCollapseState('speed', expanded)
}
collapseKey='speed'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='filamentProfile'
objectData={objectData}
labelWidth='265px'
visibleProperties={toVisibleProperties(
FILAMENT_PROFILE_SPEED_PROPERTIES
)}
/>
</InfoCollapse>
<InfoCollapse
title='Part Cooling Fan'
icon={<SettingsIcon />}
active={collapseState.cooling}
onToggle={(expanded) =>
updateCollapseState('cooling', expanded)
}
collapseKey='cooling'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='filamentProfile'
objectData={objectData}
labelWidth='350px'
visibleProperties={toVisibleProperties(
FILAMENT_PROFILE_COOLING_PROPERTIES
)}
/>
</InfoCollapse>
<InfoCollapse
title='Exhaust Fan'
icon={<SettingsIcon />}
active={collapseState.exhaustFan}
onToggle={(expanded) =>
updateCollapseState('exhaustFan', expanded)
}
collapseKey='exhaustFan'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='filamentProfile'
objectData={objectData}
labelWidth='285px'
visibleProperties={toVisibleProperties(
FILAMENT_PROFILE_EXHAUST_FAN_PROPERTIES
)}
/>
</InfoCollapse>
<InfoCollapse
title='Retraction Overrides'
icon={<SettingsIcon />}
active={collapseState.retraction}
onToggle={(expanded) =>
updateCollapseState('retraction', expanded)
}
collapseKey='retraction'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='filamentProfile'
objectData={objectData}
labelWidth='250px'
visibleProperties={toVisibleProperties(
FILAMENT_PROFILE_RETRACTION_PROPERTIES
)}
/>
</InfoCollapse>
<InfoCollapse
title='Loading & Multi-tool'
icon={<FilamentIcon />}
active={collapseState.loading}
onToggle={(expanded) =>
updateCollapseState('loading', expanded)
}
collapseKey='loading'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='filamentProfile'
objectData={objectData}
labelWidth='220px'
visibleProperties={toVisibleProperties(
FILAMENT_PROFILE_LOADING_PROPERTIES
)}
/>
</InfoCollapse>
<InfoCollapse
title='Wipe Tower & Flushing'
icon={<SettingsIcon />}
active={collapseState.wipeTower}
onToggle={(expanded) =>
updateCollapseState('wipeTower', expanded)
}
collapseKey='wipeTower'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='filamentProfile'
objectData={objectData}
labelWidth='330px'
visibleProperties={toVisibleProperties(
FILAMENT_PROFILE_WIPE_TOWER_PROPERTIES
)}
/>
</InfoCollapse>
<InfoCollapse
title='G-Code Settings'
icon={<GCodeFileIcon />}
active={collapseState.gCodeSettings}
onToggle={(expanded) =>
updateCollapseState('gCodeSettings', expanded)
}
collapseKey='gCodeSettings'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='filamentProfile'
objectData={objectData}
labelWidth='270px'
visibleProperties={toVisibleProperties(
FILAMENT_PROFILE_GCODE_PROPERTIES
)}
/>
</InfoCollapse>
<InfoCollapse
title='Compatibility'
icon={<SettingsIcon />}
active={collapseState.compatibility}
onToggle={(expanded) =>
updateCollapseState('compatibility', expanded)
}
collapseKey='compatibility'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='filamentProfile'
objectData={objectData}
labelWidth='270px'
visibleProperties={toVisibleProperties(
FILAMENT_PROFILE_COMPATIBILITY_PROPERTIES
)}
/>
</InfoCollapse>
<InfoCollapse
title='Shrinkage & Drying'
icon={<SettingsIcon />}
active={collapseState.shrinkageDrying}
onToggle={(expanded) =>
updateCollapseState('shrinkageDrying', expanded)
}
collapseKey='shrinkageDrying'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='filamentProfile'
objectData={objectData}
labelWidth='340px'
visibleProperties={{
...toVisibleProperties(
FILAMENT_PROFILE_SHRINKAGE_DRYING_PROPERTIES
),
filamentNotes: true
}}
/>
</InfoCollapse>
</Flex>
)}
</ObjectForm>
</ActionHandler>
<InfoCollapse
title='Notes'
icon={<NoteIcon />}
active={collapseState.notes}
onToggle={(expanded) => updateCollapseState('notes', expanded)}
collapseKey='notes'
>
<Card>
<NotesPanel _id={filamentProfileId} type='filamentProfile' />
</Card>
</InfoCollapse>
<InfoCollapse
title='Audit Logs'
icon={<AuditLogIcon />}
active={collapseState.auditLogs}
onToggle={(expanded) => updateCollapseState('auditLogs', expanded)}
collapseKey='auditLogs'
>
{objectFormState.loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable
type='auditLog'
masterFilter={{ 'parent._id': filamentProfileId }}
visibleColumns={{ _id: false, 'parent._id': false }}
/>
)}
</InfoCollapse>
</Flex>
</ScrollBox>
</Flex>
)
}
export default FilamentProfileInfo

View File

@ -0,0 +1,420 @@
import { useEffect, useMemo, useRef, useState } from 'react'
import PropTypes from 'prop-types'
import NewObjectForm from '../../common/NewObjectForm'
import ObjectForm from '../../common/ObjectForm'
import ObjectInfo from '../../common/ObjectInfo'
import WizardView from '../../common/WizardView'
import {
FILAMENT_PROFILE_BED_TEMPERATURE_PROPERTIES,
FILAMENT_PROFILE_COMPATIBILITY_PROPERTIES,
FILAMENT_PROFILE_COOLING_PROPERTIES,
FILAMENT_PROFILE_EXHAUST_FAN_PROPERTIES,
FILAMENT_PROFILE_FLOW_PRESSURE_PROPERTIES,
FILAMENT_PROFILE_MATERIAL_PROPERTIES,
FILAMENT_PROFILE_SPEED_PROPERTIES,
FILAMENT_PROFILE_TEMPERATURE_PROPERTIES
} from '../../../../database/models/FilamentProfile'
const PRUSA_GENERIC_PETG_DEFAULTS = {
filamentType: 'filament',
filamentIsSupport: false,
filamentSoluble: false,
filamentPrintable: 3,
filamentAdhesivenessCategory: 0,
temperatureVitrification: 80,
idleTemperature: 0,
pelletFlowCoefficient: 0.4157,
requiredNozzleHrc: 0,
filamentFlowRatio: 0.98,
enablePressureAdvance: false,
pressureAdvance: 0.02,
adaptivePressureAdvance: false,
adaptivePressureAdvanceBridges: false,
adaptivePressureAdvanceOverhangs: false,
adaptivePressureAdvanceModel: '0,0,0\n0,0,0',
activateChamberTempControl: false,
chamberTemperature: 0,
chamberMinimalTemperature: 0,
nozzleTemperatureInitialLayer: 230,
nozzleTemperature: 240,
nozzleTemperatureRangeLow: 220,
nozzleTemperatureRangeHigh: 260,
hotPlateTempInitialLayer: 85,
hotPlateTemp: 85,
coolPlateTempInitialLayer: 60,
coolPlateTemp: 60,
engPlateTempInitialLayer: 0,
engPlateTemp: 0,
texturedPlateTempInitialLayer: 45,
texturedPlateTemp: 45,
texturedCoolPlateTempInitialLayer: 40,
texturedCoolPlateTemp: 40,
supertackPlateTempInitialLayer: 35,
supertackPlateTemp: 35,
filamentAdaptiveVolumetricSpeed: false,
filamentMaxVolumetricSpeed: 8,
volumetricSpeedCoefficients: '',
closeFanTheFirstXLayers: 3,
fullFanSpeedLayer: 0,
fanMinSpeed: 40,
fanMaxSpeed: 90,
reduceFanStopStartFreq: true,
slowDownForLayerCooling: true,
dontSlowDownOuterWall: false,
slowDownMinSpeed: 10,
slowDownLayerTime: 8,
fanCoolingLayerTime: 30,
enableOverhangBridgeFan: true,
overhangFanThreshold: '25%',
overhangFanSpeed: 90,
internalBridgeFanSpeed: -1,
supportMaterialInterfaceFanSpeed: -1,
ironingFanSpeed: -1,
initialLayerFanSpeed: -1,
firstXLayerFanSpeed: 0,
additionalCoolingFanSpeed: 0,
additionalFanFullSpeedLayer: 0,
closeAdditionalFanFirstXLayers: true,
activateAirFiltration: false,
activateAirFiltrationDuringPrint: true,
activateAirFiltrationOnCompletion: true,
duringPrintExhaustFanSpeed: 60,
completePrintExhaustFanSpeed: 80,
filamentLoadingSpeed: 28,
filamentLoadingSpeedStart: 3,
filamentUnloadingSpeed: 90,
filamentUnloadingSpeedStart: 100,
filamentChangeLength: 10,
filamentChangeLengthNc: 10,
filamentToolchangeDelay: 0,
filamentExtruderCompatibility: 0,
filamentExtruderVariant: 'Direct Drive Standard',
filamentMultitoolRamming: false,
filamentMultitoolRammingFlow: 10,
filamentMultitoolRammingVolume: 10,
filamentRammingParameters:
'120 100 6.6 6.8 7.2 7.6 7.9 8.2 8.7 9.4 9.9 10.0| 0.05 6.6 0.45 6.8 0.95 7.8 1.45 8.3 1.95 9.7 2.45 10 2.95 7.6 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6',
filamentRammingTravelTime: 0,
filamentRammingTravelTimeNc: 0,
filamentRammingVolumetricSpeed: -1,
filamentRammingVolumetricSpeedNc: -1,
filamentMinimalPurgeOnWipeTower: 15,
filamentTowerInterfacePreExtrusionDist: 10,
filamentTowerInterfacePreExtrusionLength: 0,
filamentTowerInterfacePrintTemp: -1,
filamentTowerInterfacePurgeVolume: 20,
filamentTowerIroningArea: 4,
filamentCoolingBeforeTower: 10,
filamentCoolingInitialSpeed: 2.2,
filamentCoolingFinalSpeed: 3.4,
filamentCoolingMoves: 4,
filamentFlushTemp: 0,
filamentFlushTempFast: 0,
filamentFlushVolumetricSpeed: 0,
filamentPreCoolingTemperature: 0,
filamentPreCoolingTemperatureNc: 0,
filamentPreheatTemperatureDelta: 0,
filamentPrimeVolumeNc: 60,
filamentStampingDistance: 0,
filamentStampingLoadingSpeed: 0,
longRetractionsWhenEc: false,
retractionDistancesWhenEc: 10,
filamentStartGcode:
'; filament start gcode\nM900 K{if printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.6}0.12{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.8}0.06{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/}0.2{elsif nozzle_diameter[0]==0.8}0.02{elsif nozzle_diameter[0]==0.6}0.04{else}0.08{endif} ; Filament gcode LA 1.5',
filamentEndGcode: '; filament end gcode \n',
filamentChangeExtrusionRoleGcode: '',
filamentShrink: '100%',
filamentShrinkageCompensationZ: '100%',
compatiblePrinters: [],
compatiblePrintersCondition: '',
compatiblePrints: [],
compatiblePrintsCondition: '',
filamentNotes: ''
}
const REQUIRED_PROPERTIES = ['name', 'filamentType', 'filament']
const SUMMARY_PROPERTIES = [
...REQUIRED_PROPERTIES,
...FILAMENT_PROFILE_MATERIAL_PROPERTIES,
...FILAMENT_PROFILE_FLOW_PRESSURE_PROPERTIES,
...FILAMENT_PROFILE_TEMPERATURE_PROPERTIES,
...FILAMENT_PROFILE_BED_TEMPERATURE_PROPERTIES,
...FILAMENT_PROFILE_SPEED_PROPERTIES,
...FILAMENT_PROFILE_COOLING_PROPERTIES,
...FILAMENT_PROFILE_EXHAUST_FAN_PROPERTIES
]
const FilamentProfileWizard = ({
objectData,
formValid,
loading,
onSubmit,
editing,
filamentId = null
}) => {
const visibleProperties = useMemo(() => {
if (!filamentId) return undefined
return { filament: false, 'filament._id': false }
}, [filamentId])
const steps = useMemo(
() => [
{
title: 'Required',
key: 'required',
content: (
<ObjectInfo
type='filamentProfile'
properties={REQUIRED_PROPERTIES}
column={1}
bordered={false}
isEditing
labelWidth='110px'
required={true}
objectData={objectData}
visibleProperties={visibleProperties}
/>
)
},
{
title: 'Material',
key: 'material',
content: (
<ObjectInfo
type='filamentProfile'
properties={FILAMENT_PROFILE_MATERIAL_PROPERTIES}
column={1}
bordered={false}
isEditing
labelWidth='210px'
objectData={objectData}
/>
)
},
{
title: 'Flow & Pressure',
key: 'flowPressure',
content: (
<ObjectInfo
type='filamentProfile'
properties={FILAMENT_PROFILE_FLOW_PRESSURE_PROPERTIES}
column={1}
bordered={false}
isEditing
labelWidth='290px'
objectData={objectData}
/>
)
},
{
title: 'Temperature',
key: 'temperature',
content: (
<ObjectInfo
type='filamentProfile'
properties={[
...FILAMENT_PROFILE_TEMPERATURE_PROPERTIES,
...FILAMENT_PROFILE_BED_TEMPERATURE_PROPERTIES
]}
column={1}
bordered={false}
isEditing
labelWidth='250px'
objectData={objectData}
/>
)
},
{
title: 'Speed',
key: 'speed',
content: (
<ObjectInfo
type='filamentProfile'
properties={FILAMENT_PROFILE_SPEED_PROPERTIES}
column={1}
bordered={false}
isEditing
labelWidth='220px'
objectData={objectData}
/>
)
},
{
title: 'Cooling',
key: 'cooling',
content: (
<ObjectInfo
type='filamentProfile'
properties={FILAMENT_PROFILE_COOLING_PROPERTIES}
column={1}
bordered={false}
isEditing
labelWidth='320px'
objectData={objectData}
/>
)
},
{
title: 'Exhaust Fan',
key: 'exhaustFan',
content: (
<ObjectInfo
type='filamentProfile'
properties={FILAMENT_PROFILE_EXHAUST_FAN_PROPERTIES}
column={1}
bordered={false}
isEditing
labelWidth='270px'
objectData={objectData}
/>
)
},
{
title: 'Compatibility',
key: 'compatibility',
content: (
<ObjectInfo
type='filamentProfile'
properties={FILAMENT_PROFILE_COMPATIBILITY_PROPERTIES}
column={1}
bordered={false}
isEditing
labelWidth='270px'
objectData={objectData}
/>
)
},
{
title: 'Summary',
key: 'summary',
content: (
<ObjectInfo
type='filamentProfile'
properties={SUMMARY_PROPERTIES}
column={1}
bordered={false}
isEditing={false}
labelWidth='250px'
objectData={objectData}
visibleProperties={visibleProperties}
/>
)
}
],
[objectData, visibleProperties]
)
return (
<WizardView
sizeBarWidth='185px'
steps={steps}
loading={loading}
formValid={formValid}
title={editing ? 'Edit Filament Profile' : 'New Filament Profile'}
submitText={editing ? 'Save' : 'Create'}
onSubmit={onSubmit}
/>
)
}
FilamentProfileWizard.propTypes = {
objectData: PropTypes.object,
formValid: PropTypes.bool.isRequired,
loading: PropTypes.bool,
onSubmit: PropTypes.func.isRequired,
editing: PropTypes.bool,
filamentId: PropTypes.string
}
const NewFilamentProfile = ({ onOk, filamentId = null }) => (
<NewObjectForm
type='filamentProfile'
defaultValues={{
...PRUSA_GENERIC_PETG_DEFAULTS,
...(filamentId ? { filamentType: 'filament', filament: filamentId } : {})
}}
>
{({ handleSubmit, submitLoading, objectData, formValid }) => (
<FilamentProfileWizard
objectData={objectData}
formValid={formValid}
loading={submitLoading}
filamentId={filamentId}
onSubmit={async () => {
const result = await handleSubmit()
if (result?._id) onOk(result)
}}
/>
)}
</NewObjectForm>
)
NewFilamentProfile.propTypes = {
onOk: PropTypes.func.isRequired,
filamentId: PropTypes.string
}
export const EditFilamentProfile = ({ profileId, onOk, filamentId = null }) => {
const formRef = useRef(null)
const startedEditingRef = useRef(false)
const saveRequestedRef = useRef(false)
const stateRef = useRef({})
const [formState, setFormState] = useState({
loading: true,
editLoading: false,
formValid: false,
isEditing: false,
objectData: {}
})
useEffect(() => {
if (
!formState.loading &&
!formState.isEditing &&
!startedEditingRef.current
) {
startedEditingRef.current = true
formRef.current?.startEditing()
}
}, [formState.isEditing, formState.loading])
return (
<ObjectForm
id={profileId}
type='filamentProfile'
ref={formRef}
onStateChange={(nextState) => {
stateRef.current = { ...stateRef.current, ...nextState }
setFormState((current) => ({ ...current, ...nextState }))
if (saveRequestedRef.current && nextState.isEditing === false) {
saveRequestedRef.current = false
onOk(stateRef.current.objectData)
}
}}
>
{({ objectData, formValid, editLoading, handleUpdate }) => (
<FilamentProfileWizard
editing
objectData={objectData}
formValid={formValid}
loading={formState.loading || editLoading}
filamentId={filamentId}
onSubmit={() => {
saveRequestedRef.current = true
handleUpdate()
}}
/>
)}
</ObjectForm>
)
}
EditFilamentProfile.propTypes = {
profileId: PropTypes.string.isRequired,
onOk: PropTypes.func.isRequired,
filamentId: PropTypes.string
}
export default NewFilamentProfile

View File

@ -0,0 +1,143 @@
import { useContext, useRef, useState } from 'react'
import { Button, Dropdown, Flex, Modal, Space } from 'antd'
import NewPrinterProfile, {
EditPrinterProfile
} from './PrinterProfiles/NewPrinterProfile'
import ColumnViewButton from '../common/ColumnViewButton'
import ExportListButton from '../common/ExportListButton'
import FilterSidebarButton from '../common/FilterSidebarButton'
import ObjectTable from '../common/ObjectTable'
import ObjectTableViewButton from '../common/ObjectTableViewButton'
import { ApiServerContext } from '../context/ApiServerContext'
import PlusIcon from '../../Icons/PlusIcon'
import ReloadIcon from '../../Icons/ReloadIcon'
import useColumnVisibility from '../hooks/useColumnVisibility'
import useFilterSidebarVisibility from '../hooks/useFilterSidebarVisibility'
import useViewMode from '../hooks/useViewMode'
const PrinterProfiles = () => {
const { deleteObject } = useContext(ApiServerContext)
const [profileModal, setProfileModal] = useState({
open: false,
profileId: null
})
const tableRef = useRef()
const [viewMode, setViewMode] = useViewMode('PrinterProfiles')
const [columnVisibility, setColumnVisibility] =
useColumnVisibility('printerProfile')
const [showFilterSidebar, setShowFilterSidebar] =
useFilterSidebarVisibility('PrinterProfiles')
const actionItems = {
items: [
{
label: 'New Printer Profile',
key: 'newPrinterProfile',
icon: <PlusIcon />
},
{ type: 'divider' },
{
label: 'Reload List',
key: 'reloadList',
icon: <ReloadIcon />
}
],
onClick: ({ key }) => {
if (key === 'reloadList') {
tableRef.current?.reload()
} else if (key === 'newPrinterProfile') {
setProfileModal({ open: true, profileId: null })
}
}
}
const handleRowAction = (action, profile) => {
if (action.name === 'edit') {
setProfileModal({ open: true, profileId: profile._id })
return true
}
if (action.name === 'delete') {
Modal.confirm({
title: 'Delete printer profile?',
content: `This will permanently delete ${profile.name || profile._reference}.`,
okText: 'Delete',
okType: 'danger',
onOk: async () => {
await deleteObject(profile._id, 'printerProfile')
await tableRef.current?.reload()
}
})
return true
}
return false
}
return (
<>
<Flex vertical={'true'} gap='large' className='h-100'>
<Flex justify={'space-between'}>
<Space>
<Dropdown menu={actionItems}>
<Button>Actions</Button>
</Dropdown>
<ColumnViewButton
type='printerProfile'
visibleState={columnVisibility}
updateVisibleState={setColumnVisibility}
/>
<ExportListButton objectType='printerProfile' />
</Space>
<Space>
<FilterSidebarButton
active={showFilterSidebar}
onClick={() => setShowFilterSidebar(!showFilterSidebar)}
/>
<ObjectTableViewButton
viewMode={viewMode}
setViewMode={setViewMode}
/>
</Space>
</Flex>
<ObjectTable
ref={tableRef}
type='printerProfile'
cards={viewMode === 'cards'}
visibleColumns={columnVisibility}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
onRowAction={handleRowAction}
/>
</Flex>
<Modal
open={profileModal.open}
footer={null}
width={800}
onCancel={() => setProfileModal({ open: false, profileId: null })}
destroyOnHidden
>
{profileModal.open &&
(profileModal.profileId ? (
<EditPrinterProfile
profileId={profileModal.profileId}
onOk={() => {
setProfileModal({ open: false, profileId: null })
tableRef.current?.reload()
}}
/>
) : (
<NewPrinterProfile
onOk={() => {
setProfileModal({ open: false, profileId: null })
tableRef.current?.reload()
}}
/>
))}
</Modal>
</>
)
}
export default PrinterProfiles

View File

@ -0,0 +1,372 @@
import { useEffect, useMemo, useRef, useState } from 'react'
import PropTypes from 'prop-types'
import NewObjectForm from '../../common/NewObjectForm'
import ObjectForm from '../../common/ObjectForm'
import ObjectInfo from '../../common/ObjectInfo'
import WizardView from '../../common/WizardView'
const DEFAULT_EXTRUDER = {
nozzleDiameter: 0.4,
nozzleVolume: 0,
nozzleType: 'hardened_steel',
minLayerHeight: 0.05,
maxLayerHeight: 0.3,
positionOffsetX: 0,
positionOffsetY: 0,
retractionLength: 0.8,
retractionExtraLengthOnRestart: 0,
retractionSpeed: 35,
deretractionSpeed: 0,
retractionTravelDistanceThreshold: 2,
retractOnLayerChange: false,
wipeWhileRetracting: false,
wipeDistance: 1,
retractAmountBeforeWipe: 70,
retractAmountAfterWipe: 0,
zHopOnSurfaces: 'All Surfaces',
zHopType: 'Auto',
zHopHeight: 0,
zHopTravelingAngle: 0,
zHopOnlyLiftZAbove: 0,
zHopOnlyLiftZBelow: 0,
materialSwitchRetractionLength: 10,
materialSwitchExtraLengthOnRestart: 0,
longRetractionWhenCut: false
}
const PRUSA_MK3S_PROFILE_DEFAULTS = {
extruders: [{ ...DEFAULT_EXTRUDER }],
printBedWidth: 250,
printBedHeight: 210,
originX: 0,
originY: 0,
bedExcludeArea: [{ x: 0, y: 0 }],
printableHeight: 210,
supportMultiBedTypes: false,
bestObjectPositionX: 125,
bestObjectPositionY: 105,
zOffset: 0,
preferredOrientation: 0,
machineMaxAccelerationRetracting: { min: 2500, max: 2500 },
machineMaxSpeedE: { min: 120, max: 120 },
machineMaxSpeedX: { min: 200, max: 200 },
machineMaxSpeedY: { min: 200, max: 200 },
machinePauseGcode: 'M601',
machineStartGcode: `M862.1 P[nozzle_diameter] ; nozzle diameter check
M115 U3.13.0 ; tell printer latest fw version
G90 ; use absolute coordinates
M83 ; extruder relative mode
M104 S[first_layer_temperature] ; set extruder temp
M140 S[first_layer_bed_temperature] ; set bed temp
M190 S[first_layer_bed_temperature] ; wait for bed temp
M109 S[first_layer_temperature] ; wait for extruder temp
G28 W ; home all without mesh bed level
G80 ; mesh bed leveling
G1 Z0.3 F720
G1 Y-3 F1000 ; go outside print area
G92 E0
G1 X60 E9 F1000 ; intro line
G1 X100 E9 F1000 ; intro line
{else}
G1 Z0.2 F720
G1 Y-3 F1000 ; go outside print area
G92 E0
G1 X60 E9 F1000 ; intro line
G1 X100 E12.5 F1000 ; intro line
{endif}
G92 E0
M221 S{if layer_height<0.075}100{else}95{endif}`,
machineEndGcode: `{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+1, max_print_height)} F720 ; Move print head up{endif}
G1 X0 Y200 F3600 ; park
{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+49, max_print_height)} F720 ; Move print head further up{endif}
G4 ; wait
M221 S100 ; reset flow
M900 K0 ; reset LA
M104 S0 ; turn off temperature
M140 S0 ; turn off heatbed
M107 ; turn off fan
M84 ; disable motors
; max_layer_z = [max_layer_z]`,
layerChangeGcode: `;AFTER_LAYER_CHANGE
;[layer_z]`,
beforeLayerChangeGcode: `;BEFORE_LAYER_CHANGE
;[layer_z]
G92 E0
`,
printerNotes: `Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.
PRINTER_VENDOR_PRUSA3D
PRINTER_MODEL_MK3
`,
scanFirstLayer: false,
machineLoadFilamentTime: 17,
machineUnloadFilamentTime: 16,
thumbnails: [{ width: 160, height: 120 }],
auxiliaryFan: false,
machineMaxJunctionDeviation: { min: 0.02, max: 0 }
}
const REQUIRED_PROPERTIES = ['name', 'printer']
const DIMENSION_PROPERTIES = [
'printBedWidth',
'printBedHeight',
'originX',
'originY',
'printableHeight',
'supportMultiBedTypes',
'bestObjectPositionX',
'bestObjectPositionY',
'zOffset',
'preferredOrientation'
]
const EXTRUDER_STEP_PROPERTIES = ['extruders']
const MOTION_PROPERTIES = [
'machineMaxAccelerationRetracting',
'machineMaxSpeedE',
'machineMaxSpeedX',
'machineMaxSpeedY',
'machineMaxJunctionDeviation'
]
const OPTIONAL_PROPERTIES = [
'scanFirstLayer',
'machineLoadFilamentTime',
'machineUnloadFilamentTime',
'auxiliaryFan'
]
const SUMMARY_PROPERTIES = [
...REQUIRED_PROPERTIES,
...DIMENSION_PROPERTIES,
...MOTION_PROPERTIES,
...OPTIONAL_PROPERTIES
]
const PrinterProfileWizard = ({
objectData,
formValid,
loading,
onSubmit,
editing,
printerId = null
}) => {
const visibleProperties = useMemo(() => {
if (!printerId) return undefined
return { printer: false, 'printer._id': false }
}, [printerId])
const steps = useMemo(
() => [
{
title: 'Required',
key: 'required',
content: (
<ObjectInfo
type='printerProfile'
properties={REQUIRED_PROPERTIES}
column={1}
bordered={false}
isEditing
labelWidth='75px'
required={true}
objectData={objectData}
visibleProperties={visibleProperties}
/>
)
},
{
title: 'Dimensions',
key: 'dimensions',
content: (
<ObjectInfo
type='printerProfile'
properties={DIMENSION_PROPERTIES}
column={1}
bordered={false}
isEditing
labelWidth='190px'
objectData={objectData}
/>
)
},
{
title: 'Extruders',
key: 'extruders',
content: (
<ObjectInfo
type='printerProfile'
properties={EXTRUDER_STEP_PROPERTIES}
column={1}
bordered={false}
isEditing
showLabels={false}
labelWidth='90px'
objectData={objectData}
/>
)
},
{
title: 'Motion',
key: 'motion',
content: (
<ObjectInfo
type='printerProfile'
properties={MOTION_PROPERTIES}
column={1}
bordered={false}
isEditing
labelWidth='185px'
objectData={objectData}
/>
)
},
{
title: 'Optional',
key: 'optional',
content: (
<ObjectInfo
type='printerProfile'
properties={OPTIONAL_PROPERTIES}
column={1}
labelWidth='170px'
bordered={false}
isEditing
objectData={objectData}
/>
)
},
{
title: 'Summary',
key: 'summary',
content: (
<ObjectInfo
type='printerProfile'
properties={SUMMARY_PROPERTIES}
column={1}
bordered={false}
isEditing={false}
labelWidth='210px'
objectData={objectData}
visibleProperties={visibleProperties}
/>
)
}
],
[objectData, visibleProperties]
)
return (
<WizardView
sizeBarWidth='160px'
steps={steps}
loading={loading}
formValid={formValid}
title={editing ? 'Edit Printer Profile' : 'New Printer Profile'}
submitText={editing ? 'Save' : 'Create'}
onSubmit={onSubmit}
/>
)
}
PrinterProfileWizard.propTypes = {
objectData: PropTypes.object,
formValid: PropTypes.bool.isRequired,
loading: PropTypes.bool,
onSubmit: PropTypes.func.isRequired,
editing: PropTypes.bool,
printerId: PropTypes.string
}
const NewPrinterProfile = ({ onOk, printerId = null }) => (
<NewObjectForm
type='printerProfile'
defaultValues={{
...PRUSA_MK3S_PROFILE_DEFAULTS,
...(printerId ? { printer: printerId } : {})
}}
>
{({ handleSubmit, submitLoading, objectData, formValid }) => (
<PrinterProfileWizard
objectData={objectData}
formValid={formValid}
loading={submitLoading}
printerId={printerId}
onSubmit={async () => {
const result = await handleSubmit()
if (result?._id) onOk(result)
}}
/>
)}
</NewObjectForm>
)
NewPrinterProfile.propTypes = {
onOk: PropTypes.func.isRequired,
printerId: PropTypes.string
}
export const EditPrinterProfile = ({ profileId, onOk, printerId = null }) => {
const formRef = useRef(null)
const startedEditingRef = useRef(false)
const saveRequestedRef = useRef(false)
const stateRef = useRef({})
const [formState, setFormState] = useState({
loading: true,
editLoading: false,
formValid: false,
isEditing: false,
objectData: {}
})
useEffect(() => {
if (
!formState.loading &&
!formState.isEditing &&
!startedEditingRef.current
) {
startedEditingRef.current = true
formRef.current?.startEditing()
}
}, [formState.isEditing, formState.loading])
return (
<ObjectForm
id={profileId}
type='printerProfile'
ref={formRef}
onStateChange={(nextState) => {
stateRef.current = { ...stateRef.current, ...nextState }
setFormState((current) => ({ ...current, ...nextState }))
if (saveRequestedRef.current && nextState.isEditing === false) {
saveRequestedRef.current = false
onOk(stateRef.current.objectData)
}
}}
>
{({ objectData, formValid, editLoading, handleUpdate }) => (
<PrinterProfileWizard
editing
objectData={objectData}
formValid={formValid}
loading={formState.loading || editLoading}
printerId={printerId}
onSubmit={() => {
saveRequestedRef.current = true
handleUpdate()
}}
/>
)}
</ObjectForm>
)
}
EditPrinterProfile.propTypes = {
profileId: PropTypes.string.isRequired,
onOk: PropTypes.func.isRequired,
printerId: PropTypes.string
}
export default NewPrinterProfile

View File

@ -0,0 +1,381 @@
import { useRef, useState } from 'react'
import { useLocation } from 'react-router-dom'
import { Card, Flex, Space } from 'antd'
import { LoadingOutlined } from '@ant-design/icons'
import ActionHandler from '../../common/ActionHandler'
import AuditLogIcon from '../../../Icons/AuditLogIcon'
import DocumentPrintButton from '../../common/DocumentPrintButton'
import EditButtons from '../../common/EditButtons'
import HotEndIcon from '../../../Icons/HotEndIcon'
import InfoCircleIcon from '../../../Icons/InfoCircleIcon'
import MultiMotionIcon from '../../../Icons/MultiMotionIcon'
import PictureIcon from '../../../Icons/PictureIcon'
import RulerIcon from '../../../Icons/RulerIcon'
import InfoCollapse from '../../common/InfoCollapse'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder'
import LockIndicator from '../../common/LockIndicator'
import NoteIcon from '../../../Icons/NoteIcon'
import NotesPanel from '../../common/NotesPanel'
import ObjectActions from '../../common/ObjectActions'
import ObjectForm from '../../common/ObjectForm'
import ObjectInfo from '../../common/ObjectInfo'
import ObjectTable from '../../common/ObjectTable'
import ScrollBox from '../../common/ScrollBox'
import UserNotifierToggle from '../../common/UserNotifierToggle'
import ViewButton from '../../common/ViewButton'
import useCollapseState from '../../hooks/useCollapseState'
import ObjectProperty from '../../common/ObjectProperty'
import { getModelProperty } from '../../../../database/ObjectModels'
import GCodeFileIcon from '../../../Icons/GCodeFileIcon'
import SettingsIcon from '../../../Icons/SettingsIcon'
import ExclamationOctagonIcon from '../../../Icons/ExclamationOctagonIcon'
const PrinterProfileInfo = () => {
const location = useLocation()
const objectFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const printerProfileId = new URLSearchParams(location.search).get(
'printerProfileId'
)
const [collapseState, updateCollapseState] = useCollapseState(
'PrinterProfileInfo',
{
info: true,
dimensions: true,
bedExcludeArea: true,
extruders: true,
motion: true,
gCodeSettings: true,
optional: true,
thumbnails: true,
notes: true,
auditLogs: false
}
)
const [objectFormState, setObjectFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
lock: null,
loading: false,
objectData: {}
})
const actions = {
edit: () => {
objectFormRef.current?.startEditing?.()
return false
},
cancelEdit: () => {
objectFormRef.current?.cancelEditing?.()
return true
},
finishEdit: () => {
objectFormRef.current?.handleUpdate?.()
return true
},
delete: () => {
objectFormRef.current?.handleDelete?.()
return true
}
}
return (
<Flex
gap='large'
vertical='true'
style={{ maxHeight: '100%', minHeight: 0 }}
>
<Flex justify='space-between'>
<Space size='middle'>
<Space size='small'>
<ObjectActions
type='printerProfile'
id={printerProfileId}
disabled={objectFormState.loading}
objectData={objectFormState.objectData}
/>
<ViewButton
disabled={objectFormState.loading}
items={[
{ key: 'info', label: 'Printer Profile Information' },
{ key: 'dimensions', label: 'Dimensions' },
{ key: 'bedExcludeArea', label: 'Excluded Bed Area' },
{ key: 'extruders', label: 'Extruders' },
{ key: 'motion', label: 'Motion' },
{ key: 'gCodeSettings', label: 'G-Code Settings' },
{ key: 'miscellaneous', label: 'Miscellaneous' },
{ key: 'thumbnails', label: 'Thumbnail Sizes' },
{ key: 'notes', label: 'Notes' },
{ key: 'auditLogs', label: 'Audit Logs' }
]}
visibleState={collapseState}
updateVisibleState={updateCollapseState}
/>
<UserNotifierToggle
type='printerProfile'
objectData={objectFormState.objectData}
disabled={objectFormState.loading}
/>
<DocumentPrintButton
type='printerProfile'
objectData={objectFormState.objectData}
disabled={objectFormState.loading}
/>
</Space>
<LockIndicator lock={objectFormState.lock} />
</Space>
<EditButtons
isEditing={objectFormState.isEditing}
handleUpdate={() => actionHandlerRef.current.callAction('finishEdit')}
cancelEditing={() =>
actionHandlerRef.current.callAction('cancelEdit')
}
startEditing={() => actionHandlerRef.current.callAction('edit')}
editLoading={objectFormState.editLoading}
formValid={objectFormState.formValid}
disabled={objectFormState.lock?.locked || objectFormState.loading}
loading={objectFormState.editLoading}
/>
</Flex>
<ScrollBox>
<Flex vertical gap='large'>
<ActionHandler
actions={actions}
loading={objectFormState.loading}
ref={actionHandlerRef}
>
<ObjectForm
id={printerProfileId}
type='printerProfile'
ref={objectFormRef}
onStateChange={(state) =>
setObjectFormState((current) => ({ ...current, ...state }))
}
>
{({ loading, isEditing, objectData }) => (
<Flex vertical gap='large'>
<InfoCollapse
title='Printer Profile Information'
icon={<InfoCircleIcon />}
active={collapseState.info}
onToggle={(expanded) =>
updateCollapseState('info', expanded)
}
collapseKey='info'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='printerProfile'
objectData={objectData}
labelWidth='140px'
visibleProperties={{
_id: true,
name: true,
_reference: true,
printer: true,
createdAt: true,
updatedAt: true
}}
/>
</InfoCollapse>
<InfoCollapse
title='Dimensions'
icon={<RulerIcon />}
active={collapseState.dimensions}
onToggle={(expanded) =>
updateCollapseState('dimensions', expanded)
}
collapseKey='dimensions'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='printerProfile'
objectData={objectData}
labelWidth='230px'
visibleProperties={{
printBedWidth: true,
printBedHeight: true,
originX: true,
originY: true,
printableHeight: true,
supportMultiBedTypes: true,
bestObjectPositionX: true,
bestObjectPositionY: true,
zOffset: true,
preferredOrientation: true
}}
/>
</InfoCollapse>
<InfoCollapse
title='Excluded Bed Area'
icon={<ExclamationOctagonIcon />}
active={collapseState.bedExcludeArea}
onToggle={(expanded) =>
updateCollapseState('bedExcludeArea', expanded)
}
collapseKey='bedExcludeArea'
>
<ObjectProperty
{...getModelProperty('printerProfile', 'bedExcludeArea')}
isEditing={isEditing}
objectData={objectData}
loading={loading}
size='medium'
/>
</InfoCollapse>
<InfoCollapse
title='Extruders'
icon={<HotEndIcon />}
active={collapseState.extruders}
onToggle={(expanded) =>
updateCollapseState('extruders', expanded)
}
collapseKey='extruders'
>
<ObjectProperty
{...getModelProperty('printerProfile', 'extruders')}
isEditing={isEditing}
objectData={objectData}
loading={loading}
size='medium'
/>
</InfoCollapse>
<InfoCollapse
title='Motion'
icon={<MultiMotionIcon />}
active={collapseState.motion}
onToggle={(expanded) =>
updateCollapseState('motion', expanded)
}
collapseKey='motion'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='printerProfile'
objectData={objectData}
labelWidth='225px'
visibleProperties={{
machineMaxAccelerationRetracting: true,
machineMaxSpeedE: true,
machineMaxSpeedX: true,
machineMaxSpeedY: true,
machineMaxJunctionDeviation: true
}}
/>
</InfoCollapse>
<InfoCollapse
title='G-Code Settings'
icon={<GCodeFileIcon />}
active={collapseState.gCodeSettings}
onToggle={(expanded) =>
updateCollapseState('gCodeSettings', expanded)
}
collapseKey='gCodeSettings'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='printerProfile'
objectData={objectData}
labelWidth='240px'
visibleProperties={{
machinePauseGcode: true,
machineStartGcode: true,
machineEndGcode: true,
layerChangeGcode: true,
beforeLayerChangeGcode: true
}}
/>
</InfoCollapse>
<InfoCollapse
title='Miscellaneous'
icon={<SettingsIcon />}
active={collapseState.miscellaneous}
onToggle={(expanded) =>
updateCollapseState('miscellaneous', expanded)
}
collapseKey='miscellaneous'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='printerProfile'
objectData={objectData}
labelWidth='210px'
visibleProperties={{
printerNotes: true,
scanFirstLayer: true,
machineLoadFilamentTime: true,
machineUnloadFilamentTime: true,
auxiliaryFan: true
}}
/>
</InfoCollapse>
<InfoCollapse
title='Thumbnail Sizes'
icon={<PictureIcon />}
active={collapseState.thumbnails}
onToggle={(expanded) =>
updateCollapseState('thumbnails', expanded)
}
collapseKey='thumbnails'
>
<ObjectProperty
{...getModelProperty('printerProfile', 'thumbnails')}
isEditing={isEditing}
objectData={objectData}
loading={loading}
size='medium'
/>
</InfoCollapse>
</Flex>
)}
</ObjectForm>
</ActionHandler>
<InfoCollapse
title='Notes'
icon={<NoteIcon />}
active={collapseState.notes}
onToggle={(expanded) => updateCollapseState('notes', expanded)}
collapseKey='notes'
>
<Card>
<NotesPanel _id={printerProfileId} type='printerProfile' />
</Card>
</InfoCollapse>
<InfoCollapse
title='Audit Logs'
icon={<AuditLogIcon />}
active={collapseState.auditLogs}
onToggle={(expanded) => updateCollapseState('auditLogs', expanded)}
collapseKey='auditLogs'
>
{objectFormState.loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable
type='auditLog'
masterFilter={{ 'parent._id': printerProfileId }}
visibleColumns={{ _id: false, 'parent._id': false }}
/>
)}
</InfoCollapse>
</Flex>
</ScrollBox>
</Flex>
)
}
export default PrinterProfileInfo

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,665 @@
import PrinterProfileIcon from '../../components/Icons/PrinterProfileIcon'
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
import EditIcon from '../../components/Icons/EditIcon'
import CheckIcon from '../../components/Icons/CheckIcon'
import XMarkIcon from '../../components/Icons/XMarkIcon'
import BinIcon from '../../components/Icons/BinIcon'
const THUMBNAIL_PROPERTIES = [
{
name: 'width',
label: 'Width',
type: 'number',
min: 0,
step: 1,
columnWidth: 120
},
{
name: 'height',
label: 'Height',
type: 'number',
min: 0,
step: 1,
columnWidth: 120
}
]
const BED_EXCLUDE_AREA_PROPERTIES = [
{
name: 'x',
label: 'X',
type: 'number',
step: 0.01,
columnWidth: 120
},
{
name: 'y',
label: 'Y',
type: 'number',
step: 0.01,
columnWidth: 120
}
]
const EXTRUDER_PROPERTIES = [
{
name: 'nozzleDiameter',
label: 'Nozzle Diameter',
type: 'number',
suffix: ' mm',
min: 0,
step: 0.05,
columnWidth: 150
},
{
name: 'nozzleVolume',
label: 'Nozzle Volume',
type: 'number',
suffix: ' mm³',
min: 0,
step: 0.1,
columnWidth: 150
},
{
name: 'nozzleType',
label: 'Nozzle Type',
type: 'text',
columnWidth: 160
},
{
name: 'minLayerHeight',
label: 'Min Layer Height',
type: 'number',
suffix: ' mm',
min: 0,
step: 0.01,
columnWidth: 180
},
{
name: 'maxLayerHeight',
label: 'Max Layer Height',
type: 'number',
suffix: ' mm',
min: 0,
step: 0.01,
columnWidth: 180
},
{
name: 'positionOffsetX',
label: 'Extruder Offset X',
type: 'number',
suffix: ' mm',
step: 0.01,
columnWidth: 180
},
{
name: 'positionOffsetY',
label: 'Extruder Offset Y',
type: 'number',
suffix: ' mm',
step: 0.01,
columnWidth: 180
},
{
name: 'retractionLength',
label: 'Retraction Length',
type: 'number',
suffix: ' mm',
min: 0,
step: 0.1,
columnWidth: 170
},
{
name: 'retractionExtraLengthOnRestart',
label: 'Extra Length on Restart',
type: 'number',
suffix: ' mm',
min: 0,
step: 0.1,
columnWidth: 210
},
{
name: 'retractionSpeed',
label: 'Retraction Speed',
type: 'number',
suffix: ' mm/s',
min: 0,
step: 1,
columnWidth: 170
},
{
name: 'deretractionSpeed',
label: 'Deretraction Speed',
type: 'number',
suffix: ' mm/s',
min: 0,
step: 1,
columnWidth: 180
},
{
name: 'retractionTravelDistanceThreshold',
label: 'Travel Distance Threshold',
type: 'number',
suffix: ' mm',
min: 0,
step: 0.1,
columnWidth: 220
},
{
name: 'retractOnLayerChange',
label: 'Retract on Layer Change',
type: 'bool',
columnWidth: 210
},
{
name: 'wipeWhileRetracting',
label: 'Wipe While Retracting',
type: 'bool',
columnWidth: 190
},
{
name: 'wipeDistance',
label: 'Wipe Distance',
type: 'number',
suffix: ' mm',
min: 0,
step: 0.1,
columnWidth: 150
},
{
name: 'retractAmountBeforeWipe',
label: 'Retract Amount Before Wipe',
type: 'number',
suffix: ' mm',
min: 0,
step: 0.1,
columnWidth: 230
},
{
name: 'retractAmountAfterWipe',
label: 'Retract Amount After Wipe',
type: 'number',
suffix: ' mm',
min: 0,
step: 0.1,
columnWidth: 230
},
{
name: 'zHopOnSurfaces',
label: 'Z-Hop On Surfaces',
type: 'select',
options: [
{ label: 'All Surfaces', value: 'All Surfaces' },
{ label: 'Top Only', value: 'Top Only' },
{ label: 'Bottom Only', value: 'Bottom Only' },
{ label: 'None', value: 'None' }
],
columnWidth: 170
},
{
name: 'zHopType',
label: 'Z-Hop Type',
type: 'select',
options: [
{ label: 'Auto', value: 'Auto' },
{ label: 'Normal', value: 'Normal' },
{ label: 'Spiral', value: 'Spiral' },
{ label: 'Slope', value: 'Slope' }
],
columnWidth: 140
},
{
name: 'zHopHeight',
label: 'Z-Hop Height',
type: 'number',
suffix: ' mm',
min: 0,
step: 0.01,
columnWidth: 150
},
{
name: 'zHopTravelingAngle',
label: 'Traveling Angle',
type: 'number',
suffix: '°',
min: 0,
step: 1,
columnWidth: 160
},
{
name: 'zHopOnlyLiftZAbove',
label: 'Only Lift Z Above',
type: 'number',
suffix: ' mm',
min: 0,
step: 0.01,
columnWidth: 170
},
{
name: 'zHopOnlyLiftZBelow',
label: 'Only Lift Z Below',
type: 'number',
suffix: ' mm',
min: 0,
step: 0.01,
columnWidth: 170
},
{
name: 'materialSwitchRetractionLength',
label: 'Material Switch Retraction Length',
type: 'number',
suffix: ' mm',
min: 0,
step: 0.1,
columnWidth: 250
},
{
name: 'materialSwitchExtraLengthOnRestart',
label: 'Material Switch Extra Length on Restart',
type: 'number',
suffix: ' mm',
min: 0,
step: 0.1,
columnWidth: 290
},
{
name: 'longRetractionWhenCut',
label: 'Long Retraction When Cut (beta)',
type: 'bool',
columnWidth: 240
}
]
export const PrinterProfile = {
name: 'printerProfile',
label: 'Printer Profile',
labelPlural: 'Printer Profiles',
url: '/dashboard/production/printerprofiles',
prefix: 'PPF',
icon: PrinterProfileIcon,
actions: [
{
name: 'info',
label: 'Info',
default: true,
row: true,
icon: InfoCircleIcon,
url: (_id) =>
`/dashboard/production/printerprofiles/info?printerProfileId=${_id}`
},
{
name: 'edit',
label: 'Edit',
row: true,
icon: EditIcon,
url: (_id) =>
`/dashboard/production/printerprofiles/info?printerProfileId=${_id}&action=edit`,
visible: (objectData) => !objectData?._isEditing
},
{
name: 'finishEdit',
label: 'Save Edits',
icon: CheckIcon,
url: (_id) =>
`/dashboard/production/printerprofiles/info?printerProfileId=${_id}&action=finishEdit`,
visible: (objectData) => objectData?._isEditing === true
},
{
name: 'cancelEdit',
label: 'Cancel Edits',
icon: XMarkIcon,
url: (_id) =>
`/dashboard/production/printerprofiles/info?printerProfileId=${_id}&action=cancelEdit`,
visible: (objectData) => objectData?._isEditing === true
},
{ type: 'divider' },
{
name: 'delete',
label: 'Delete',
row: true,
icon: BinIcon,
danger: true,
url: (_id) =>
`/dashboard/production/printerprofiles/info?printerProfileId=${_id}&action=delete`
}
],
columns: [
'_reference',
'name',
'printer',
'printBedWidth',
'printBedHeight',
'printableHeight',
'extruders.0.nozzleDiameter',
'extruders.0.nozzleType',
'zOffset',
'scanFirstLayer',
'auxiliaryFan',
'updatedAt'
],
filters: ['_id', 'name', 'printer'],
sorters: [
'name',
'printBedWidth',
'printBedHeight',
'printableHeight',
'zOffset',
'createdAt',
'updatedAt'
],
properties: [
{
name: '_id',
label: 'ID',
type: 'id',
objectType: 'printerProfile',
showCopy: true,
readOnly: true,
columnWidth: 140
},
{
name: 'createdAt',
label: 'Created At',
type: 'dateTime',
readOnly: true,
columnWidth: 175
},
{
name: '_reference',
label: 'Reference',
type: 'reference',
objectType: 'printerProfile',
showCopy: true,
readOnly: true,
columnFixed: 'left',
columnWidth: 180
},
{
name: 'updatedAt',
label: 'Updated At',
type: 'dateTime',
readOnly: true,
columnWidth: 175
},
{
name: 'name',
label: 'Name',
type: 'text',
required: true,
columnFixed: 'left',
columnWidth: 220,
value: (objectData) => {
if (objectData?.name == undefined) {
return objectData?.printer?.name
}
return objectData?.name
}
},
{
name: 'printer',
label: 'Printer',
type: 'object',
objectType: 'printer',
required: true,
showHyperlink: true,
columnWidth: 220
},
{
name: 'printBedWidth',
label: 'Print Bed Width',
type: 'number',
suffix: ' mm',
min: 0,
step: 1,
columnWidth: 170
},
{
name: 'printBedHeight',
label: 'Print Bed Height',
type: 'number',
suffix: ' mm',
min: 0,
step: 1,
columnWidth: 170
},
{
name: 'originX',
label: 'Origin X',
type: 'number',
suffix: ' mm',
step: 0.01,
columnWidth: 150
},
{
name: 'originY',
label: 'Origin Y',
type: 'number',
suffix: ' mm',
step: 0.01,
columnWidth: 150
},
{
name: 'bestObjectPositionX',
label: 'Best Object Position X',
type: 'number',
suffix: ' mm',
step: 0.01,
columnWidth: 200
},
{
name: 'bestObjectPositionY',
label: 'Best Object Position Y',
type: 'number',
suffix: ' mm',
step: 0.01,
columnWidth: 200
},
{
name: 'bedExcludeArea',
label: 'Excluded Bed Area',
type: 'objectChildren',
canAddRemove: true,
span: 2,
columns: ['x', 'y'],
properties: BED_EXCLUDE_AREA_PROPERTIES
},
{
name: 'printableHeight',
label: 'Printable Height',
type: 'number',
suffix: ' mm',
min: 0,
step: 1,
columnWidth: 170
},
{
name: 'supportMultiBedTypes',
label: 'Support Multi Bed Types',
type: 'bool',
columnWidth: 200
},
{
name: 'zOffset',
label: 'Z Offset',
type: 'number',
suffix: ' mm',
step: 0.01,
columnWidth: 140
},
{
name: 'preferredOrientation',
label: 'Preferred Orientation',
type: 'number',
suffix: '°',
min: 0,
max: 360,
step: 1,
columnWidth: 190
},
{
name: 'extruders.0.nozzleDiameter',
label: 'Nozzle Diameter',
type: 'number',
suffix: ' mm',
min: 0,
step: 0.05,
readOnly: true,
columnWidth: 150
},
{
name: 'extruders.0.nozzleType',
label: 'Nozzle Type',
type: 'text',
readOnly: true,
columnWidth: 160
},
{
name: 'extruders',
label: 'Extruders',
type: 'objectChildren',
canAddRemove: true,
span: 2,
columns: [
'nozzleDiameter',
'nozzleType',
'nozzleVolume',
'minLayerHeight',
'maxLayerHeight',
'positionOffsetX',
'positionOffsetY'
],
hiddenPropertyWidth: 330,
properties: EXTRUDER_PROPERTIES
},
{
name: 'machineMaxAccelerationRetracting',
label: 'Max Retracting Accel',
type: 'minMax',
suffix: ' mm/s²',
min: 0,
step: 1,
columnWidth: 260
},
{
name: 'machineMaxSpeedE',
label: 'Max Extruder Speed',
type: 'minMax',
suffix: ' mm/s',
min: 0,
step: 1,
columnWidth: 220
},
{
name: 'machineMaxSpeedX',
label: 'Max X Speed',
type: 'minMax',
suffix: ' mm/s',
min: 0,
step: 1,
columnWidth: 190
},
{
name: 'machineMaxSpeedY',
label: 'Max Y Speed',
type: 'minMax',
suffix: ' mm/s',
min: 0,
step: 1,
columnWidth: 190
},
{
name: 'machinePauseGcode',
label: 'Pause G-code',
type: 'codeBlock',
language: 'gcode',
height: '180px',
span: 2
},
{
name: 'machineStartGcode',
label: 'Start G-code',
type: 'codeBlock',
language: 'gcode',
height: '240px',
span: 2
},
{
name: 'machineEndGcode',
label: 'End G-code',
type: 'codeBlock',
language: 'gcode',
height: '240px',
span: 2
},
{
name: 'layerChangeGcode',
label: 'Layer Change G-code',
type: 'codeBlock',
language: 'gcode',
height: '180px',
span: 2
},
{
name: 'beforeLayerChangeGcode',
label: 'Before Layer Change G-code',
type: 'codeBlock',
language: 'gcode',
height: '180px',
span: 2
},
{
name: 'machineLoadFilamentTime',
label: 'Filament Load Time',
type: 'number',
suffix: ' s',
min: 0,
step: 0.1,
columnWidth: 180
},
{
name: 'scanFirstLayer',
label: 'Scan First Layer',
type: 'bool',
columnWidth: 150
},
{
name: 'machineUnloadFilamentTime',
label: 'Filament Unload Time',
type: 'number',
suffix: ' s',
min: 0,
step: 0.1,
columnWidth: 190
},
{
name: 'thumbnails',
label: 'Thumbnail Sizes',
type: 'objectChildren',
canAddRemove: true,
span: 2,
columns: ['width', 'height'],
properties: THUMBNAIL_PROPERTIES
},
{
name: 'auxiliaryFan',
label: 'Auxiliary Fan',
type: 'bool',
columnWidth: 140
},
{
name: 'printerNotes',
label: 'Printer Notes',
type: 'markdown',
span: 2
},
{
name: 'machineMaxJunctionDeviation',
label: 'Max Junction Deviation',
type: 'minMax',
suffix: ' mm',
min: 0,
step: 0.01,
columnWidth: 240
}
]
}