feat: Add temperature ranges for extruder and bed temps (#5)
Some checks failed
CI / style (push) Has been cancelled
CI / build-client (push) Has been cancelled
CI / build-amd64 (push) Has been cancelled
CI / build-tester (push) Has been cancelled
CI / test (cockroachdb) (push) Has been cancelled
CI / test (mariadb) (push) Has been cancelled
CI / test (postgres) (push) Has been cancelled
CI / test (sqlite) (push) Has been cancelled
CI / build-arm64 (push) Has been cancelled
CI / build-armv7 (push) Has been cancelled
CI / publish-images (push) Has been cancelled
CI / publish-release (push) Has been cancelled

Store extruder and bed temperatures as min/max ranges instead of
single values for more accurate filament specifications.

Backend changes:
- Add 4 new columns: settings_extruder_temp_min/max, settings_bed_temp_min/max
- Keep old columns for backward compatibility (marked deprecated)
- Alembic migration copies existing values to both min and max
- Update Pydantic models and API endpoints

Frontend changes:
- Filament model updated with new fields
- Create form uses min/max input pairs with Space.Compact
- Import from external DB populates both min and max with same value
- Label template updated to show ranges: "ET: 190-220 °C"
- Template help includes new tags

Labels now display temperature ranges like "190-220°C" which is more
useful for printing than a single recommended value.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-14 20:05:02 -06:00
parent dc166450ce
commit 3451996a8f
9 changed files with 183 additions and 35 deletions

View File

@@ -236,6 +236,8 @@
"comment": "Comment",
"settings_extruder_temp": "Extruder Temp",
"settings_bed_temp": "Bed Temp",
"temp_min": "Min",
"temp_max": "Max",
"color_hex": "Color",
"single_color": "Single",
"multi_color": "Multi",

View File

@@ -1,6 +1,6 @@
import { Create, useForm, useSelect } from "@refinedev/antd";
import { HttpError, IResourceComponentsProps, useInvalidate, useTranslate } from "@refinedev/core";
import { AutoComplete, Button, ColorPicker, Divider, Form, Input, InputNumber, Radio, Select, Typography } from "antd";
import { AutoComplete, Button, ColorPicker, Divider, Form, Input, InputNumber, Radio, Select, Space, Typography } from "antd";
import { PlusOutlined } from "@ant-design/icons";
import TextArea from "antd/es/input/TextArea";
import dayjs from "dayjs";
@@ -126,8 +126,10 @@ export const FilamentCreate: React.FC<IResourceComponentsProps & CreateOrClonePr
color_hex: filament.color_hex,
multi_color_hexes: filament.color_hexes?.join(",") || undefined,
multi_color_direction: filament.multi_color_direction,
settings_extruder_temp: filament.extruder_temp || undefined,
settings_bed_temp: filament.bed_temp || undefined,
settings_extruder_temp_min: filament.extruder_temp || undefined,
settings_extruder_temp_max: filament.extruder_temp || undefined,
settings_bed_temp_min: filament.bed_temp || undefined,
settings_bed_temp_max: filament.bed_temp || undefined,
});
};
@@ -376,31 +378,43 @@ export const FilamentCreate: React.FC<IResourceComponentsProps & CreateOrClonePr
>
<InputNumber addonAfter="g" precision={1} />
</Form.Item>
<Form.Item
label={t("filament.fields.settings_extruder_temp")}
name={["settings_extruder_temp"]}
rules={[
{
required: false,
type: "number",
min: 0,
},
]}
>
<InputNumber addonAfter="°C" precision={0} />
<Form.Item label={t("filament.fields.settings_extruder_temp")}>
<Space.Compact>
<Form.Item
name={["settings_extruder_temp_min"]}
noStyle
rules={[{ type: "number", min: 0 }]}
>
<InputNumber placeholder={t("filament.fields.temp_min")} addonAfter="°C" precision={0} style={{ width: 140 }} />
</Form.Item>
<span style={{ padding: "4px 8px", background: "#fafafa", border: "1px solid #d9d9d9", borderLeft: 0, borderRight: 0 }}>-</span>
<Form.Item
name={["settings_extruder_temp_max"]}
noStyle
rules={[{ type: "number", min: 0 }]}
>
<InputNumber placeholder={t("filament.fields.temp_max")} addonAfter="°C" precision={0} style={{ width: 140 }} />
</Form.Item>
</Space.Compact>
</Form.Item>
<Form.Item
label={t("filament.fields.settings_bed_temp")}
name={["settings_bed_temp"]}
rules={[
{
required: false,
type: "number",
min: 0,
},
]}
>
<InputNumber addonAfter="°C" precision={0} />
<Form.Item label={t("filament.fields.settings_bed_temp")}>
<Space.Compact>
<Form.Item
name={["settings_bed_temp_min"]}
noStyle
rules={[{ type: "number", min: 0 }]}
>
<InputNumber placeholder={t("filament.fields.temp_min")} addonAfter="°C" precision={0} style={{ width: 140 }} />
</Form.Item>
<span style={{ padding: "4px 8px", background: "#fafafa", border: "1px solid #d9d9d9", borderLeft: 0, borderRight: 0 }}>-</span>
<Form.Item
name={["settings_bed_temp_max"]}
noStyle
rules={[{ type: "number", min: 0 }]}
>
<InputNumber placeholder={t("filament.fields.temp_max")} addonAfter="°C" precision={0} style={{ width: 140 }} />
</Form.Item>
</Space.Compact>
</Form.Item>
<Form.Item
label={t("filament.fields.article_number")}

View File

@@ -15,6 +15,10 @@ export interface IFilament {
comment?: string;
settings_extruder_temp?: number;
settings_bed_temp?: number;
settings_extruder_temp_min?: number;
settings_extruder_temp_max?: number;
settings_bed_temp_min?: number;
settings_bed_temp_max?: number;
color_hex?: string;
multi_color_hexes?: string;
multi_color_direction?: string;

View File

@@ -157,8 +157,8 @@ const SpoolQRCodePrintingDialog: React.FC<SpoolQRCodePrintingDialog> = ({ spoolI
`**{filament.vendor.name} - {filament.name}
#{id} - {filament.material}**
Spool Weight: {filament.spool_weight} g
{ET: {filament.settings_extruder_temp} °C}
{BT: {filament.settings_bed_temp} °C}
{ET: {filament.settings_extruder_temp_min}-{filament.settings_extruder_temp_max} °C}
{BT: {filament.settings_bed_temp_min}-{filament.settings_bed_temp_max} °C}
{Lot Nr: {lot_nr}}
{{comment}}
{filament.comment}
@@ -201,6 +201,10 @@ Spool Weight: {filament.spool_weight} g
{ tag: "filament.comment" },
{ tag: "filament.settings_extruder_temp" },
{ tag: "filament.settings_bed_temp" },
{ tag: "filament.settings_extruder_temp_min" },
{ tag: "filament.settings_extruder_temp_max" },
{ tag: "filament.settings_bed_temp_min" },
{ tag: "filament.settings_bed_temp_max" },
{ tag: "filament.color_hex" },
{ tag: "filament.multi_color_hexes" },
{ tag: "filament.multi_color_direction" },

View File

@@ -0,0 +1,56 @@
"""add_temperature_ranges.
Revision ID: a1b2c3d4e5f6
Revises: 415a8f855e14
Create Date: 2025-01-15 02:00:00.000000
"""
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = "a1b2c3d4e5f6"
down_revision = "415a8f855e14"
branch_labels = None
depends_on = None
def upgrade() -> None:
"""Add temperature range columns and migrate existing data."""
# Add new columns for temperature ranges
op.add_column("filament", sa.Column("settings_extruder_temp_min", sa.Integer(), nullable=True))
op.add_column("filament", sa.Column("settings_extruder_temp_max", sa.Integer(), nullable=True))
op.add_column("filament", sa.Column("settings_bed_temp_min", sa.Integer(), nullable=True))
op.add_column("filament", sa.Column("settings_bed_temp_max", sa.Integer(), nullable=True))
# Migrate existing single temp values to both min and max
# This preserves the existing data while enabling the new range functionality
connection = op.get_bind()
connection.execute(
sa.text(
"""
UPDATE filament
SET settings_extruder_temp_min = settings_extruder_temp,
settings_extruder_temp_max = settings_extruder_temp
WHERE settings_extruder_temp IS NOT NULL
"""
)
)
connection.execute(
sa.text(
"""
UPDATE filament
SET settings_bed_temp_min = settings_bed_temp,
settings_bed_temp_max = settings_bed_temp
WHERE settings_bed_temp IS NOT NULL
"""
)
)
def downgrade() -> None:
"""Remove temperature range columns."""
op.drop_column("filament", "settings_bed_temp_max")
op.drop_column("filament", "settings_bed_temp_min")
op.drop_column("filament", "settings_extruder_temp_max")
op.drop_column("filament", "settings_extruder_temp_min")

View File

@@ -75,15 +75,39 @@ class FilamentParameters(BaseModel):
settings_extruder_temp: Optional[int] = Field(
None,
ge=0,
description="Overridden extruder temperature, in °C.",
description="Overridden extruder temperature, in °C. Deprecated: use settings_extruder_temp_min/max.",
examples=[210],
)
settings_bed_temp: Optional[int] = Field(
None,
ge=0,
description="Overridden bed temperature, in °C.",
description="Overridden bed temperature, in °C. Deprecated: use settings_bed_temp_min/max.",
examples=[60],
)
settings_extruder_temp_min: Optional[int] = Field(
None,
ge=0,
description="Minimum extruder temperature, in °C.",
examples=[190],
)
settings_extruder_temp_max: Optional[int] = Field(
None,
ge=0,
description="Maximum extruder temperature, in °C.",
examples=[220],
)
settings_bed_temp_min: Optional[int] = Field(
None,
ge=0,
description="Minimum bed temperature, in °C.",
examples=[50],
)
settings_bed_temp_max: Optional[int] = Field(
None,
ge=0,
description="Maximum bed temperature, in °C.",
examples=[70],
)
color_hex: Optional[str] = Field(
None,
description=(
@@ -454,6 +478,10 @@ async def create( # noqa: ANN201
comment=body.comment,
settings_extruder_temp=body.settings_extruder_temp,
settings_bed_temp=body.settings_bed_temp,
settings_extruder_temp_min=body.settings_extruder_temp_min,
settings_extruder_temp_max=body.settings_extruder_temp_max,
settings_bed_temp_min=body.settings_bed_temp_min,
settings_bed_temp_max=body.settings_bed_temp_max,
color_hex=body.color_hex,
multi_color_hexes=body.multi_color_hexes,
multi_color_direction=body.multi_color_direction,

View File

@@ -149,15 +149,39 @@ class Filament(BaseModel):
settings_extruder_temp: Optional[int] = Field(
None,
ge=0,
description="Overridden extruder temperature, in °C.",
description="Overridden extruder temperature, in °C. Deprecated: use settings_extruder_temp_min/max.",
examples=[210],
)
settings_bed_temp: Optional[int] = Field(
None,
ge=0,
description="Overridden bed temperature, in °C.",
description="Overridden bed temperature, in °C. Deprecated: use settings_bed_temp_min/max.",
examples=[60],
)
settings_extruder_temp_min: Optional[int] = Field(
None,
ge=0,
description="Minimum extruder temperature, in °C.",
examples=[190],
)
settings_extruder_temp_max: Optional[int] = Field(
None,
ge=0,
description="Maximum extruder temperature, in °C.",
examples=[220],
)
settings_bed_temp_min: Optional[int] = Field(
None,
ge=0,
description="Minimum bed temperature, in °C.",
examples=[50],
)
settings_bed_temp_max: Optional[int] = Field(
None,
ge=0,
description="Maximum bed temperature, in °C.",
examples=[70],
)
color_hex: Optional[str] = Field(
None,
min_length=6,
@@ -216,6 +240,10 @@ class Filament(BaseModel):
comment=item.comment,
settings_extruder_temp=item.settings_extruder_temp,
settings_bed_temp=item.settings_bed_temp,
settings_extruder_temp_min=item.settings_extruder_temp_min,
settings_extruder_temp_max=item.settings_extruder_temp_max,
settings_bed_temp_min=item.settings_bed_temp_min,
settings_bed_temp_max=item.settings_bed_temp_max,
color_hex=item.color_hex,
multi_color_hexes=item.multi_color_hexes,
multi_color_direction=(

View File

@@ -41,6 +41,10 @@ async def create(
comment: Optional[str] = None,
settings_extruder_temp: Optional[int] = None,
settings_bed_temp: Optional[int] = None,
settings_extruder_temp_min: Optional[int] = None,
settings_extruder_temp_max: Optional[int] = None,
settings_bed_temp_min: Optional[int] = None,
settings_bed_temp_max: Optional[int] = None,
color_hex: Optional[str] = None,
multi_color_hexes: Optional[str] = None,
multi_color_direction: Optional[MultiColorDirection] = None,
@@ -69,6 +73,10 @@ async def create(
comment=comment,
settings_extruder_temp=settings_extruder_temp,
settings_bed_temp=settings_bed_temp,
settings_extruder_temp_min=settings_extruder_temp_min,
settings_extruder_temp_max=settings_extruder_temp_max,
settings_bed_temp_min=settings_bed_temp_min,
settings_bed_temp_max=settings_bed_temp_max,
color_hex=color_hex,
multi_color_hexes=multi_color_hexes,
multi_color_direction=multi_color_direction.value if multi_color_direction is not None else None,

View File

@@ -46,8 +46,12 @@ class Filament(Base):
spool_weight: Mapped[Optional[float]] = mapped_column(comment="The weight of an empty spool.")
article_number: Mapped[Optional[str]] = mapped_column(String(64))
comment: Mapped[Optional[str]] = mapped_column(String(1024))
settings_extruder_temp: Mapped[Optional[int]] = mapped_column(comment="Overridden extruder temperature.")
settings_bed_temp: Mapped[Optional[int]] = mapped_column(comment="Overridden bed temperature.")
settings_extruder_temp: Mapped[Optional[int]] = mapped_column(comment="Overridden extruder temperature (deprecated, use min/max).")
settings_bed_temp: Mapped[Optional[int]] = mapped_column(comment="Overridden bed temperature (deprecated, use min/max).")
settings_extruder_temp_min: Mapped[Optional[int]] = mapped_column(comment="Minimum extruder temperature.")
settings_extruder_temp_max: Mapped[Optional[int]] = mapped_column(comment="Maximum extruder temperature.")
settings_bed_temp_min: Mapped[Optional[int]] = mapped_column(comment="Minimum bed temperature.")
settings_bed_temp_max: Mapped[Optional[int]] = mapped_column(comment="Maximum bed temperature.")
color_hex: Mapped[Optional[str]] = mapped_column(String(8))
multi_color_hexes: Mapped[Optional[str]] = mapped_column(String(128))
multi_color_direction: Mapped[Optional[str]] = mapped_column(String(16))