Updated spool UIs (create/edit)

This commit is contained in:
Matt Gerega
2024-03-27 14:47:05 -04:00
parent 3aed735cea
commit 3ea4937f24
7 changed files with 349 additions and 80 deletions

View File

@@ -144,6 +144,8 @@
"measured_weight": "Measured Weight", "measured_weight": "Measured Weight",
"used_length": "Used Length", "used_length": "Used Length",
"remaining_length": "Remaining Length", "remaining_length": "Remaining Length",
"initial_weight": "Initial Weight",
"empty_weight": "Empty Weight",
"location": "Location", "location": "Location",
"lot_nr": "Lot Nr", "lot_nr": "Lot Nr",
"first_used": "First Used", "first_used": "First Used",
@@ -158,6 +160,8 @@
"used_weight": "How much filament has been used from the spool. A new spool should have 0g used.", "used_weight": "How much filament has been used from the spool. A new spool should have 0g used.",
"remaining_weight": "How much filament is left on the spool. For a new spool this should match the spool weight.", "remaining_weight": "How much filament is left on the spool. For a new spool this should match the spool weight.",
"measured_weight": "How much the filament and spool weigh.", "measured_weight": "How much the filament and spool weigh.",
"initial_weight": "The initial Total Weight of the filament and spool.",
"empty_weight": "The weight of the spool when it is empty.",
"location": "Where the spool is located if you have multiple locations where you store your spools.", "location": "Where the spool is located if you have multiple locations where you store your spools.",
"lot_nr": "Manufacturer's lot number. Can be used to ensure a print has consistent color if multiple spools are used." "lot_nr": "Manufacturer's lot number. Can be used to ensure a print has consistent color if multiple spools are used."
}, },

View File

@@ -107,9 +107,6 @@ export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps
}); });
filamentOptions?.sort((a, b) => a.label.localeCompare(b.label, undefined, { sensitivity: "base" })); filamentOptions?.sort((a, b) => a.label.localeCompare(b.label, undefined, { sensitivity: "base" }));
const [defaultEmptySpoolWeight, setDefaultEmptySpoolWeight] = useState(0);
const [defaultInitialTotalWeight, setDefaultInitialTotalWeight] = useState(0);
const [weightToEnter, setWeightToEnter] = useState(1); const [weightToEnter, setWeightToEnter] = useState(1);
const [usedWeight, setUsedWeight] = useState(0); const [usedWeight, setUsedWeight] = useState(0);
@@ -117,28 +114,26 @@ export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps
const selectedFilament = filamentOptions?.find((obj) => { const selectedFilament = filamentOptions?.find((obj) => {
return obj.value === selectedFilamentID; return obj.value === selectedFilamentID;
}); });
const filamentWeight = selectedFilament?.weight || 0;
const spoolWeight = selectedFilament?.spool_weight || 0;
const filamentChange = (newID: number) => { const filamentChange = (newID: number) => {
const newSelectedFilament = filamentOptions?.find((obj) => { const newSelectedFilament = filamentOptions?.find((obj) => {
return obj.value === newID; return obj.value === newID;
}); });
const initial_weight = form.getFieldValue("initial_weight") as number ?? 0;
const empty_weight = form.getFieldValue("empty_weight") as number ?? 0;
const newFilamentWeight = newSelectedFilament?.weight || 0; const newFilamentWeight = newSelectedFilament?.weight || 0;
const newSpoolWeight = newSelectedFilament?.spool_weight || 0; const newSpoolWeight = newSelectedFilament?.spool_weight || 0;
setDefaultEmptySpoolWeight(newSpoolWeight); const currentCalculatedFilamentWeight = getTotalWeightFromFilament();
setDefaultInitialTotalWeight(newFilamentWeight + newSpoolWeight); if ((initial_weight === 0 || initial_weight === currentCalculatedFilamentWeight) && newFilamentWeight > 0) {
form.setFieldValue("initial_weight", newFilamentWeight + newSpoolWeight);
if (weightToEnter >= 3) {
if (!(newFilamentWeight && newSpoolWeight)) {
setWeightToEnter(2);
}
} }
if (weightToEnter >= 2) {
if (!newFilamentWeight) { if ((empty_weight === 0 || empty_weight === (selectedFilament?.spool_weight ?? 0)) && newSpoolWeight > 0) {
setWeightToEnter(1); form.setFieldValue("empty_weight", newSpoolWeight);
}
} }
}; };
@@ -166,6 +161,98 @@ export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps
setQuantity(quantity - 1); setQuantity(quantity - 1);
}; };
const getSpoolTotalWeight = (): number => {
const initial_weight = form.getFieldValue("initial_weight") as number;
const empty_weight = form.getFieldValue("empty_weight") as number;
const spool_weight = empty_weight ?? selectedFilament?.spool_weight;
return initial_weight ?? (selectedFilament?.weight ?? 0) + spool_weight;
};
const getTotalWeightFromFilament = (): number => {
return (selectedFilament?.weight ?? 0) + (selectedFilament?.spool_weight ?? 0);
}
const getFilamentWeight = (): number => {
const initial_weight = form.getFieldValue("initial_weight") as number;
const empty_weight = form.getFieldValue("empty_weight") as number;
const spool_weight = empty_weight ?? selectedFilament?.spool_weight;
if (initial_weight) {
return initial_weight - (spool_weight ?? 0);
}
return selectedFilament?.weight ?? 0;
}
const getMeasuredWeight = (): number => {
const initial_weight = form.getFieldValue("initial_weight") as number;
if (initial_weight) {
return initial_weight - usedWeight;
}
const empty_weight = form.getFieldValue("empty_weight") as number;
const spool_weight = empty_weight ?? selectedFilament?.spool_weight;
if (selectedFilament?.weight && spool_weight) {
return selectedFilament?.weight - usedWeight + spool_weight;
}
return 0;
}
const getRemainingWeight = (): number => {
const initial_weight = form.getFieldValue("initial_weight") as number ?? 0;
const empty_weight = form.getFieldValue("empty_weight") as number;
const spool_weight = empty_weight ?? selectedFilament?.spool_weight;
let remaining_weight = 0;
if (initial_weight === 0) {
remaining_weight = (selectedFilament?.weight ?? 0) - usedWeight;
}
else {
remaining_weight = initial_weight - spool_weight - usedWeight;
}
return (remaining_weight >= 0) ? remaining_weight : 0;
}
const isMeasuredWeightEnabled = (): boolean => {
if (!isRemainingWeightEnabled()) {
return false;
}
const empty_weight = form.getFieldValue("empty_weight") as number;
return (empty_weight || selectedFilament?.spool_weight) ? true : false;
}
const isRemainingWeightEnabled = (): boolean => {
const initial_weight = form.getFieldValue("initial_weight") as number;
if (initial_weight) {
return true;
}
return selectedFilament?.weight ? true : false;
}
React.useEffect(() => {
if (weightToEnter >= 3)
{
if (!isMeasuredWeightEnabled()) {
setWeightToEnter(2);
return;
}
}
if (weightToEnter >= 2)
{
if (!isRemainingWeightEnabled()) {
setWeightToEnter(1);
return;
}
}
}, [selectedFilament, weightChange])
return ( return (
<Create <Create
title={props.mode === "create" ? t("spool.titles.create") : t("spool.titles.clone")} title={props.mode === "create" ? t("spool.titles.create") : t("spool.titles.clone")}
@@ -242,8 +329,8 @@ export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps
/> />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("filament.fields.price")} label={t("spool.fields.price")}
help={t("filament.fields_help.price")} help={t("spool.fields_help.price")}
name={["price"]} name={["price"]}
rules={[ rules={[
{ {
@@ -272,10 +359,7 @@ export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps
}, },
]} ]}
> >
<InputNumber <InputNumber addonAfter="g" precision={1}/>
addonAfter="g"
precision={1}
defaultValue={defaultInitialTotalWeight} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
@@ -290,10 +374,7 @@ export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps
}, },
]} ]}
> >
<InputNumber <InputNumber addonAfter="g" precision={1} />
addonAfter="g"
precision={1}
defaultValue={defaultEmptySpoolWeight} />
</Form.Item> </Form.Item>
<Form.Item hidden={true} name={["used_weight"]} initialValue={0}> <Form.Item hidden={true} name={["used_weight"]} initialValue={0}>
@@ -309,10 +390,10 @@ export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps
value={weightToEnter} value={weightToEnter}
> >
<Radio.Button value={1}>{t("spool.fields.used_weight")}</Radio.Button> <Radio.Button value={1}>{t("spool.fields.used_weight")}</Radio.Button>
<Radio.Button value={2} disabled={!filamentWeight}> <Radio.Button value={2} disabled={!isRemainingWeightEnabled()}>
{t("spool.fields.remaining_weight")} {t("spool.fields.remaining_weight")}
</Radio.Button> </Radio.Button>
<Radio.Button value={3} disabled={!(filamentWeight && spoolWeight)}> <Radio.Button value={3} disabled={!isMeasuredWeightEnabled()}>
{t("spool.fields.measured_weight")} {t("spool.fields.measured_weight")}
</Radio.Button> </Radio.Button>
</Radio.Group> </Radio.Group>
@@ -344,9 +425,9 @@ export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps
formatter={numberFormatter} formatter={numberFormatter}
parser={numberParser} parser={numberParser}
disabled={weightToEnter != 2} disabled={weightToEnter != 2}
value={filamentWeight ? filamentWeight - usedWeight : 0} value={getRemainingWeight()}
onChange={(value) => { onChange={(value) => {
weightChange(filamentWeight - (value ?? 0)); weightChange(getFilamentWeight() - (value ?? 0));
}} }}
/> />
</Form.Item> </Form.Item>
@@ -362,9 +443,10 @@ export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps
formatter={numberFormatter} formatter={numberFormatter}
parser={numberParser} parser={numberParser}
disabled={weightToEnter != 3} disabled={weightToEnter != 3}
value={filamentWeight && spoolWeight ? filamentWeight - usedWeight + spoolWeight : 0} value={getMeasuredWeight()}
onChange={(value) => { onChange={(value) => {
weightChange(filamentWeight - ((value ?? 0) - spoolWeight)); const totalWeight = getSpoolTotalWeight();
weightChange(totalWeight - (value ?? 0));
}} }}
/> />
</Form.Item> </Form.Item>

View File

@@ -5,7 +5,7 @@ import { Form, Input, DatePicker, Select, InputNumber, Radio, Divider, Alert, Ty
import dayjs from "dayjs"; import dayjs from "dayjs";
import TextArea from "antd/es/input/TextArea"; import TextArea from "antd/es/input/TextArea";
import { IFilament } from "../filaments/model"; import { IFilament } from "../filaments/model";
import { ISpool, ISpoolParsedExtras } from "./model"; import { ISpool, ISpoolParsedExtras, WeightToEnter } from "./model";
import { numberFormatter, numberParser } from "../../utils/parsing"; import { numberFormatter, numberParser } from "../../utils/parsing";
import { useSpoolmanLocations } from "../../components/otherModels"; import { useSpoolmanLocations } from "../../components/otherModels";
import { message } from "antd/lib"; import { message } from "antd/lib";
@@ -21,12 +21,6 @@ We also need to stringify them again before sending them back to the API, which
the form's onFinish method. Form.Item's normalize should do this, but it doesn't seem to work. the form's onFinish method. Form.Item's normalize should do this, but it doesn't seem to work.
*/ */
enum WeightToEnter {
used_weight = 1,
remaining_weight = 2,
measured_weight = 3,
}
export const SpoolEdit: React.FC<IResourceComponentsProps> = () => { export const SpoolEdit: React.FC<IResourceComponentsProps> = () => {
const t = useTranslate(); const t = useTranslate();
const [messageApi, contextHolder] = message.useMessage(); const [messageApi, contextHolder] = message.useMessage();
@@ -56,9 +50,6 @@ export const SpoolEdit: React.FC<IResourceComponentsProps> = () => {
formProps.initialValues = ParsedExtras(formProps.initialValues); formProps.initialValues = ParsedExtras(formProps.initialValues);
} }
const [defaultEmptySpoolWeight, setDefaultEmptySpoolWeight] = useState(0);
const [defaultInitialTotalWeight, setDefaultInitialTotalWeight] = useState(0);
// Override the form's onFinish method to stringify the extra fields // Override the form's onFinish method to stringify the extra fields
const originalOnFinish = formProps.onFinish; const originalOnFinish = formProps.onFinish;
formProps.onFinish = (allValues: ISpoolParsedExtras) => { formProps.onFinish = (allValues: ISpoolParsedExtras) => {
@@ -103,38 +94,34 @@ export const SpoolEdit: React.FC<IResourceComponentsProps> = () => {
const selectedFilament = filamentOptions?.find((obj) => { const selectedFilament = filamentOptions?.find((obj) => {
return obj.value === selectedFilamentID; return obj.value === selectedFilamentID;
}); });
const filamentWeight = selectedFilament?.weight || 0;
const spoolWeight = selectedFilament?.spool_weight || 0;
const filamentChange = (newID: number) => { const filamentChange = (newID: number) => {
const newSelectedFilament = filamentOptions?.find((obj) => { const newSelectedFilament = filamentOptions?.find((obj) => {
return obj.value === newID; return obj.value === newID;
}); });
const initial_weight = form.getFieldValue("initial_weight") as number ?? 0;
const empty_weight = form.getFieldValue("empty_weight") as number ?? 0;
const newFilamentWeight = newSelectedFilament?.weight || 0; const newFilamentWeight = newSelectedFilament?.weight || 0;
const newSpoolWeight = newSelectedFilament?.spool_weight || 0; const newSpoolWeight = newSelectedFilament?.spool_weight || 0;
setDefaultEmptySpoolWeight(newSpoolWeight); const currentCalculatedFilamentWeight = getTotalWeightFromFilament();
setDefaultInitialTotalWeight(newFilamentWeight + newSpoolWeight); if ((initial_weight === 0 || initial_weight === currentCalculatedFilamentWeight) && newFilamentWeight > 0) {
form.setFieldValue("initial_weight", newFilamentWeight + newSpoolWeight);
const filamentHasWeight = newSelectedFilament?.weight || 0;
const filamentHasSpoolWeight = newSelectedFilament?.spool_weight || 0;
if (weightToEnter == WeightToEnter.measured_weight) {
if (!(filamentHasWeight && filamentHasSpoolWeight)) {
setWeightToEnter(WeightToEnter.remaining_weight);
}
} }
if (weightToEnter == WeightToEnter.remaining_weight || weightToEnter == WeightToEnter.measured_weight) {
if (!filamentHasWeight) { if ((empty_weight === 0 || empty_weight === (selectedFilament?.spool_weight ?? 0)) && newSpoolWeight > 0) {
setWeightToEnter(WeightToEnter.used_weight); form.setFieldValue("empty_weight", newSpoolWeight);
}
} }
}; };
const weightChange = (weight: number) => { const weightChange = (weight: number) => {
setUsedWeight(weight); setUsedWeight(weight);
form.setFieldValue("used_weight", weight); form.setFieldsValue({
used_weight: weight,
});
}; };
const locations = useSpoolmanLocations(true); const locations = useSpoolmanLocations(true);
@@ -145,6 +132,97 @@ export const SpoolEdit: React.FC<IResourceComponentsProps> = () => {
allLocations.push(newLocation.trim()); allLocations.push(newLocation.trim());
} }
const getSpoolTotalWeight = (): number => {
const initial_weight = form.getFieldValue("initial_weight") as number;
const empty_weight = form.getFieldValue("empty_weight") as number;
const spool_weight = empty_weight ?? selectedFilament?.spool_weight;
return initial_weight ?? (selectedFilament?.weight ?? 0) + spool_weight;
};
const getTotalWeightFromFilament = (): number => {
return (selectedFilament?.weight ?? 0) + (selectedFilament?.spool_weight ?? 0);
}
const getFilamentWeight = (): number => {
const initial_weight = form.getFieldValue("initial_weight") as number;
const empty_weight = form.getFieldValue("empty_weight") as number;
const spool_weight = empty_weight ?? selectedFilament?.spool_weight;
if (initial_weight) {
return initial_weight - (spool_weight ?? 0);
}
return selectedFilament?.weight ?? 0;
}
const getMeasuredWeight = (): number => {
const initial_weight = form.getFieldValue("initial_weight") as number;
if (initial_weight) {
return initial_weight - usedWeight;
}
const empty_weight = form.getFieldValue("empty_weight") as number;
const spool_weight = empty_weight ?? selectedFilament?.spool_weight;
if (selectedFilament?.weight && spool_weight) {
return selectedFilament?.weight - usedWeight + spool_weight;
}
return 0;
}
const getRemainingWeight = (): number => {
const initial_weight = form.getFieldValue("initial_weight") as number ?? 0;
const empty_weight = form.getFieldValue("empty_weight") as number;
const spool_weight = empty_weight ?? selectedFilament?.spool_weight;
let remaining_weight = 0;
if (initial_weight === 0) {
remaining_weight = (selectedFilament?.weight ?? 0) - usedWeight;
}
else {
remaining_weight = initial_weight - spool_weight - usedWeight;
}
return (remaining_weight >= 0) ? remaining_weight : 0;
}
const isMeasuredWeightEnabled = (): boolean => {
if (!isRemainingWeightEnabled()) {
return false;
}
const empty_weight = form.getFieldValue("empty_weight") as number;
return (empty_weight || selectedFilament?.spool_weight) ? true : false;
}
const isRemainingWeightEnabled = (): boolean => {
const initial_weight = form.getFieldValue("initial_weight") as number;
if (initial_weight) {
return true;
}
return selectedFilament?.weight ? true : false;
}
React.useEffect(() => {
if (weightToEnter >= WeightToEnter.measured_weight)
{
if (!isMeasuredWeightEnabled()) {
setWeightToEnter(WeightToEnter.remaining_weight);
return;
}
}
if (weightToEnter >= WeightToEnter.remaining_weight)
{
if (!isRemainingWeightEnabled()) {
setWeightToEnter(WeightToEnter.used_weight);
return;
}
}
}, [selectedFilament, weightChange])
const initialUsedWeight = formProps.initialValues?.used_weight || 0; const initialUsedWeight = formProps.initialValues?.used_weight || 0;
useEffect(() => { useEffect(() => {
if (initialUsedWeight) { if (initialUsedWeight) {
@@ -260,10 +338,7 @@ export const SpoolEdit: React.FC<IResourceComponentsProps> = () => {
}, },
]} ]}
> >
<InputNumber <InputNumber addonAfter="g" precision={1}/>
addonAfter="g"
precision={1}
defaultValue={defaultInitialTotalWeight} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
@@ -278,15 +353,13 @@ export const SpoolEdit: React.FC<IResourceComponentsProps> = () => {
}, },
]} ]}
> >
<InputNumber <InputNumber addonAfter="g" precision={1} />
addonAfter="g"
precision={1}
defaultValue={defaultEmptySpoolWeight} />
</Form.Item> </Form.Item>
<Form.Item hidden={true} name={["used_weight"]} initialValue={0}> <Form.Item hidden={true} name={["used_weight"]} initialValue={0}>
<InputNumber value={usedWeight} /> <InputNumber value={usedWeight} />
</Form.Item> </Form.Item>
<Form.Item label={t("spool.fields.weight_to_use")} help={t("spool.fields_help.weight_to_use")}> <Form.Item label={t("spool.fields.weight_to_use")} help={t("spool.fields_help.weight_to_use")}>
<Radio.Group <Radio.Group
onChange={(value) => { onChange={(value) => {
@@ -296,10 +369,10 @@ export const SpoolEdit: React.FC<IResourceComponentsProps> = () => {
value={weightToEnter} value={weightToEnter}
> >
<Radio.Button value={WeightToEnter.used_weight}>{t("spool.fields.used_weight")}</Radio.Button> <Radio.Button value={WeightToEnter.used_weight}>{t("spool.fields.used_weight")}</Radio.Button>
<Radio.Button value={WeightToEnter.remaining_weight} disabled={!filamentWeight}> <Radio.Button value={WeightToEnter.remaining_weight} disabled={!isRemainingWeightEnabled()}>
{t("spool.fields.remaining_weight")} {t("spool.fields.remaining_weight")}
</Radio.Button> </Radio.Button>
<Radio.Button value={WeightToEnter.measured_weight} disabled={!(filamentWeight && spoolWeight)}> <Radio.Button value={WeightToEnter.measured_weight} disabled={!isMeasuredWeightEnabled()}>
{t("spool.fields.measured_weight")} {t("spool.fields.measured_weight")}
</Radio.Button> </Radio.Button>
</Radio.Group> </Radio.Group>
@@ -318,7 +391,10 @@ export const SpoolEdit: React.FC<IResourceComponentsProps> = () => {
}} }}
/> />
</Form.Item> </Form.Item>
<Form.Item label={t("spool.fields.remaining_weight")} help={t("spool.fields_help.remaining_weight")}> <Form.Item
label={t("spool.fields.remaining_weight")}
help={t("spool.fields_help.remaining_weight")}
>
<InputNumber <InputNumber
min={0} min={0}
addonAfter="g" addonAfter="g"
@@ -326,13 +402,16 @@ export const SpoolEdit: React.FC<IResourceComponentsProps> = () => {
formatter={numberFormatter} formatter={numberFormatter}
parser={numberParser} parser={numberParser}
disabled={weightToEnter != WeightToEnter.remaining_weight} disabled={weightToEnter != WeightToEnter.remaining_weight}
value={filamentWeight ? filamentWeight - usedWeight : 0} value={getRemainingWeight()}
onChange={(value) => { onChange={(value) => {
weightChange(filamentWeight - (value ?? 0)); weightChange(getFilamentWeight() - (value ?? 0));
}} }}
/> />
</Form.Item> </Form.Item>
<Form.Item label={t("spool.fields.measured_weight")} help={t("spool.fields_help.measured_weight")}> <Form.Item
label={t("spool.fields.measured_weight")}
help={t("spool.fields_help.measured_weight")}
>
<InputNumber <InputNumber
min={0} min={0}
addonAfter="g" addonAfter="g"
@@ -340,9 +419,10 @@ export const SpoolEdit: React.FC<IResourceComponentsProps> = () => {
formatter={numberFormatter} formatter={numberFormatter}
parser={numberParser} parser={numberParser}
disabled={weightToEnter != WeightToEnter.measured_weight} disabled={weightToEnter != WeightToEnter.measured_weight}
value={filamentWeight && spoolWeight ? filamentWeight - usedWeight + spoolWeight : 0} value={getMeasuredWeight()}
onChange={(value) => { onChange={(value) => {
weightChange(filamentWeight - ((value ?? 0) - spoolWeight)); const totalWeight = getSpoolTotalWeight();
weightChange(totalWeight - (value ?? 0));
}} }}
/> />
</Form.Item> </Form.Item>

View File

@@ -1,5 +1,11 @@
import { IFilament } from "../filaments/model"; import { IFilament } from "../filaments/model";
export enum WeightToEnter {
used_weight = 1,
remaining_weight = 2,
measured_weight = 3,
}
export interface ISpool { export interface ISpool {
id: number; id: number;
registered: string; registered: string;
@@ -7,6 +13,8 @@ export interface ISpool {
last_used?: string; last_used?: string;
filament: IFilament; filament: IFilament;
price?: number; price?: number;
initial_weight?: number;
empty_weight?: number;
remaining_weight?: number; remaining_weight?: number;
used_weight: number; used_weight: number;
remaining_length?: number; remaining_length?: number;

View File

@@ -187,6 +187,18 @@ class Spool(BaseModel):
), ),
example=500.6, example=500.6,
) )
initial_weight: Optional[float] = Field(
default=None,
ge=0,
description=("Initial weight of the filament and spool (gross weight)"),
example=1246,
)
empty_weight: Optional[float] = Field(
default=None,
ge=0,
description=("Weight of an empty spool (tare weight)."),
example=246,
)
used_weight: float = Field(ge=0, description="Consumed weight of filament from the spool in grams.", example=500.3) used_weight: float = Field(ge=0, description="Consumed weight of filament from the spool in grams.", example=500.3)
remaining_length: Optional[float] = Field( remaining_length: Optional[float] = Field(
default=None, default=None,
@@ -228,7 +240,17 @@ class Spool(BaseModel):
remaining_weight: Optional[float] = None remaining_weight: Optional[float] = None
remaining_length: Optional[float] = None remaining_length: Optional[float] = None
if filament.weight is not None:
if item.initial_weight is not None:
filament_spool_weight = item.filament.spool_weight if item.filament.spool_weight is not None else 0
spool_weight = item.empty_weight if item.empty_weight is not None else filament_spool_weight
remaining_weight = max(item.initial_weight - spool_weight - item.used_weight, 0)
remaining_length = length_from_weight(
weight=remaining_weight,
density=filament.density,
diameter=filament.diameter,
)
elif filament.weight is not None:
remaining_weight = max(filament.weight - item.used_weight, 0) remaining_weight = max(filament.weight - item.used_weight, 0)
remaining_length = length_from_weight( remaining_length = length_from_weight(
weight=remaining_weight, weight=remaining_weight,
@@ -249,6 +271,8 @@ class Spool(BaseModel):
last_used=item.last_used, last_used=item.last_used,
filament=filament, filament=filament,
price=item.price, price=item.price,
initial_weight=item.initial_weight,
empty_weight=item.empty_weight,
used_weight=item.used_weight, used_weight=item.used_weight,
used_length=used_length, used_length=used_length,
remaining_weight=remaining_weight, remaining_weight=remaining_weight,

View File

@@ -42,12 +42,12 @@ class SpoolParameters(BaseModel):
) )
initial_weight: Optional[float] = Field( initial_weight: Optional[float] = Field(
ge=0, ge=0,
description="The initial total weight of the spool.", description="The initial total weight of the filament and spool. (gross weight)",
example=200, example=200,
) )
empty_weight: Optional[float] = Field( empty_weight: Optional[float] = Field(
ge=0, ge=0,
description="The weight of an empty spool.", description="The weight of an empty spool. (tare weight)",
example=200, example=200,
) )
remaining_weight: Optional[float] = Field( remaining_weight: Optional[float] = Field(
@@ -85,6 +85,10 @@ class SpoolUseParameters(BaseModel):
use_weight: Optional[float] = Field(description="Filament weight to reduce by, in g.", example=5.3) use_weight: Optional[float] = Field(description="Filament weight to reduce by, in g.", example=5.3)
class SpoolMeasureParameters(BaseModel):
weight: float = Field(description="Current gross weight of the spool.", example=200)
@router.get( @router.get(
"", "",
name="Find spool", name="Find spool",
@@ -492,3 +496,23 @@ async def use( # noqa: ANN201
status_code=400, status_code=400,
content={"message": "Either use_weight or use_length must be specified."}, content={"message": "Either use_weight or use_length must be specified."},
) )
@router.put(
"/{spool_id}/measure",
name="Use spool filament based on the current weight measurement",
description=("Use some weight of filament from the spool. Specify the current gross weight of the spool."),
response_model_exclude_none=True,
response_model=Spool,
responses={
400: {"model": Message},
404: {"model": Message},
},
)
async def measure( # noqa: ANN201
db: Annotated[AsyncSession, Depends(get_db_session)],
spool_id: int,
body: SpoolMeasureParameters,
):
db_item = await spool.measure(db, spool_id, body.weight)
return Spool.from_db(db_item)

View File

@@ -323,6 +323,53 @@ async def use_length(db: AsyncSession, spool_id: int, length: float) -> models.S
return spool return spool
async def measure(db: AsyncSession, spool_id: int, weight: float) -> models.Spool:
"""Record usage based on current gross weight of spool.
Increases the used_weight attribute of the spool.
Updates the first_used and last_used attributes where appropriate.
Args:
db (AsyncSession): Database session
spool_id (int): Spool ID
weight (float): Length of filament to consume, in mm
Returns:
models.Spool: Updated spool object
"""
spool_result = await db.execute(
sqlalchemy.select(models.Spool.initial_weight, models.Spool.used_weight).where(models.Spool.id == spool_id),
)
try:
spool_info = spool_result.one()
except NoResultFound as exc:
raise ItemNotFoundError("Spool not found.") from exc
initial_weight = spool_info[0]
if initial_weight is None:
# Get filament weight and spool_weight
result = await db.execute(
sqlalchemy.select(models.Filament.weight, models.Filament.spool_weight)
.join(models.Spool, models.Spool.filament_id == models.Filament.id)
.where(models.Spool.id == spool_id),
)
try:
filament_info = result.one()
except NoResultFound as exc:
raise ItemNotFoundError("Filament not found for spool.") from exc
initial_weight = filament_info[0] + filament_info[1]
# Calculate the current gross weight (initial_weight - used_weight)
current_use = initial_weight - spool_info[1]
# Calculate the weight used since last measure
weight_to_use = current_use - weight
return await use_weight(db, spool_id, weight_to_use)
async def find_locations( async def find_locations(
*, *,
db: AsyncSession, db: AsyncSession,