Compare commits
2 Commits
10b98957b8
...
e264d7ed4e
| Author | SHA1 | Date | |
|---|---|---|---|
| e264d7ed4e | |||
| 46cc0656d6 |
@@ -254,7 +254,8 @@
|
||||
"coaxial": "Coextruded",
|
||||
"longitudinal": "Longitudinal",
|
||||
"external_id": "External ID",
|
||||
"spools": "Show Spools"
|
||||
"spools": "Show Spools",
|
||||
"not_set": "Not Set"
|
||||
},
|
||||
"fields_help": {
|
||||
"name": "Filament name, to distinguish this filament type among others from the same manufacturer. Should contain the color for example.",
|
||||
|
||||
@@ -131,16 +131,20 @@ export const FilamentShow: React.FC<IResourceComponentsProps> = () => {
|
||||
}}
|
||||
/>
|
||||
<Title level={5}>{t("filament.fields.settings_extruder_temp")}</Title>
|
||||
{!record?.settings_extruder_temp ? (
|
||||
<TextField value="Not Set" />
|
||||
{!record?.settings_extruder_temp_min && !record?.settings_extruder_temp_max ? (
|
||||
<TextField value={t("filament.fields.not_set")} />
|
||||
) : record?.settings_extruder_temp_min === record?.settings_extruder_temp_max || !record?.settings_extruder_temp_max ? (
|
||||
<NumberFieldUnit value={record?.settings_extruder_temp_min ?? ""} unit="°C" />
|
||||
) : (
|
||||
<NumberFieldUnit value={record?.settings_extruder_temp ?? ""} unit="°C" />
|
||||
<TextField value={`${record?.settings_extruder_temp_min ?? "?"} - ${record?.settings_extruder_temp_max ?? "?"} °C`} />
|
||||
)}
|
||||
<Title level={5}>{t("filament.fields.settings_bed_temp")}</Title>
|
||||
{!record?.settings_bed_temp ? (
|
||||
<TextField value="Not Set" />
|
||||
{!record?.settings_bed_temp_min && !record?.settings_bed_temp_max ? (
|
||||
<TextField value={t("filament.fields.not_set")} />
|
||||
) : record?.settings_bed_temp_min === record?.settings_bed_temp_max || !record?.settings_bed_temp_max ? (
|
||||
<NumberFieldUnit value={record?.settings_bed_temp_min ?? ""} unit="°C" />
|
||||
) : (
|
||||
<NumberFieldUnit value={record?.settings_bed_temp ?? ""} unit="°C" />
|
||||
<TextField value={`${record?.settings_bed_temp_min ?? "?"} - ${record?.settings_bed_temp_max ?? "?"} °C`} />
|
||||
)}
|
||||
<Title level={5}>{t("filament.fields.article_number")}</Title>
|
||||
<TextField value={record?.article_number} />
|
||||
|
||||
@@ -102,24 +102,45 @@ function applyTextFormatting(text: string): ReactElement[] {
|
||||
|
||||
export function renderLabelContents(template: string, spool: ISpool): ReactElement {
|
||||
// Find all {tags} in the template string and loop over them
|
||||
// let matches = [...template.matchAll(/(?:{(.*?))?{(.*?)}(.*?)(?:}(.*?))?/gs)];
|
||||
let matches = [...template.matchAll(/{(?:[^}{]|{[^}{]*})*}/gs)];
|
||||
let label_text = template;
|
||||
matches.forEach((match) => {
|
||||
if ((match[0].match(/{/g) || []).length == 1) {
|
||||
const braceCount = (match[0].match(/{/g) || []).length;
|
||||
if (braceCount == 1) {
|
||||
// Simple tag: {tag}
|
||||
let tag = match[0].replace(/[{}]/g, "");
|
||||
let tagValue = getTagValue(tag, spool);
|
||||
label_text = label_text.replace(match[0], tagValue);
|
||||
} else if ((match[0].match(/{/g) || []).length == 2) {
|
||||
let structure = match[0].match(/{(.*?){(.*?)}(.*?)}/);
|
||||
if (structure != null) {
|
||||
const tag = structure[2];
|
||||
let tagValue = getTagValue(tag, spool);
|
||||
if (tagValue == "?") {
|
||||
label_text = label_text.replace(match[0], "");
|
||||
} else {
|
||||
label_text = label_text.replace(match[0], structure[1] + tagValue + structure[3]);
|
||||
} else if (braceCount >= 2) {
|
||||
// Conditional block with one or more inner tags: {prefix {tag1} middle {tag2} suffix}
|
||||
// First, extract the outer content and find all inner tags
|
||||
const outerContent = match[0].slice(1, -1); // Remove outer braces
|
||||
const innerTagMatches = [...outerContent.matchAll(/{([^{}]+)}/g)];
|
||||
|
||||
if (innerTagMatches.length === 0) {
|
||||
// No inner tags found, leave as-is
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if any inner tag resolves to "?" (missing value)
|
||||
let allTagsValid = true;
|
||||
let processedContent = outerContent;
|
||||
|
||||
for (const innerMatch of innerTagMatches) {
|
||||
const tag = innerMatch[1];
|
||||
const tagValue = getTagValue(tag, spool);
|
||||
if (tagValue === "?" || tagValue === null || tagValue === undefined) {
|
||||
allTagsValid = false;
|
||||
break;
|
||||
}
|
||||
processedContent = processedContent.replace(innerMatch[0], String(tagValue));
|
||||
}
|
||||
|
||||
if (allTagsValid) {
|
||||
label_text = label_text.replace(match[0], processedContent);
|
||||
} else {
|
||||
// If any tag is missing, remove the entire conditional block
|
||||
label_text = label_text.replace(match[0], "");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
178
scripts/parse_temps_api.py
Normal file
178
scripts/parse_temps_api.py
Normal file
@@ -0,0 +1,178 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Parse temperature values from filament comments using the API."""
|
||||
|
||||
import re
|
||||
import requests
|
||||
import sys
|
||||
|
||||
|
||||
def parse_temps_from_comment(comment: str) -> dict:
|
||||
"""Parse extruder and bed temps from a comment string.
|
||||
|
||||
Patterns to match:
|
||||
- "extrude 190-230" or "extruder 190-230" or "nozzle 190-230"
|
||||
- "bed 50-70"
|
||||
- "190-230 / 50-70" (extruder / bed)
|
||||
- "190-230C" or "190-230°C"
|
||||
- Single values like "extrude 210" → min=max=210
|
||||
"""
|
||||
result = {
|
||||
"extruder_temp_min": None,
|
||||
"extruder_temp_max": None,
|
||||
"bed_temp_min": None,
|
||||
"bed_temp_max": None,
|
||||
}
|
||||
|
||||
if not comment:
|
||||
return result
|
||||
|
||||
comment_lower = comment.lower()
|
||||
|
||||
# Pattern for temperature ranges: 190-230 or 190 - 230
|
||||
temp_range_pattern = r"(\d{2,3})\s*[-–]\s*(\d{2,3})"
|
||||
# Pattern for single temp: 210
|
||||
single_temp_pattern = r"(\d{2,3})"
|
||||
|
||||
# Try to find extruder temp (usually higher values, 150-300 range)
|
||||
extruder_keywords = ["extrude", "extruder", "nozzle", "hotend", "hot end", "printing temp"]
|
||||
for keyword in extruder_keywords:
|
||||
if keyword in comment_lower:
|
||||
# Find temp after keyword
|
||||
idx = comment_lower.index(keyword)
|
||||
search_area = comment[idx:idx+50] # Look in next 50 chars
|
||||
|
||||
# Try range first
|
||||
match = re.search(temp_range_pattern, search_area)
|
||||
if match:
|
||||
result["extruder_temp_min"] = int(match.group(1))
|
||||
result["extruder_temp_max"] = int(match.group(2))
|
||||
break
|
||||
# Try single value
|
||||
match = re.search(single_temp_pattern, search_area[len(keyword):])
|
||||
if match:
|
||||
val = int(match.group(1))
|
||||
if 150 <= val <= 350: # Reasonable extruder temp range
|
||||
result["extruder_temp_min"] = val
|
||||
result["extruder_temp_max"] = val
|
||||
break
|
||||
|
||||
# Try to find bed temp (usually lower values, 30-120 range)
|
||||
bed_keywords = ["bed", "plate", "build plate"]
|
||||
for keyword in bed_keywords:
|
||||
if keyword in comment_lower:
|
||||
# Find temp after keyword
|
||||
idx = comment_lower.index(keyword)
|
||||
search_area = comment[idx:idx+40] # Look in next 40 chars
|
||||
|
||||
# Try range first
|
||||
match = re.search(temp_range_pattern, search_area)
|
||||
if match:
|
||||
t1, t2 = int(match.group(1)), int(match.group(2))
|
||||
# Validate: bed temps should be 30-120°C typically
|
||||
if 30 <= t1 <= 120 and 30 <= t2 <= 120:
|
||||
result["bed_temp_min"] = t1
|
||||
result["bed_temp_max"] = t2
|
||||
break
|
||||
# Try single value
|
||||
match = re.search(single_temp_pattern, search_area[len(keyword):])
|
||||
if match:
|
||||
val = int(match.group(1))
|
||||
if 30 <= val <= 120: # Reasonable bed temp range
|
||||
result["bed_temp_min"] = val
|
||||
result["bed_temp_max"] = val
|
||||
break
|
||||
|
||||
# If no keywords found, try to find generic temp pattern
|
||||
# Format: "190-230 / 50-70" (extruder / bed separated by /)
|
||||
if result["extruder_temp_min"] is None and "/" in comment:
|
||||
parts = comment.split("/")
|
||||
if len(parts) >= 2:
|
||||
# First part might be extruder
|
||||
match = re.search(temp_range_pattern, parts[0])
|
||||
if match:
|
||||
t1, t2 = int(match.group(1)), int(match.group(2))
|
||||
if t1 > 100: # Likely extruder temp
|
||||
result["extruder_temp_min"] = t1
|
||||
result["extruder_temp_max"] = t2
|
||||
|
||||
# Second part might be bed
|
||||
match = re.search(temp_range_pattern, parts[1])
|
||||
if match:
|
||||
t1, t2 = int(match.group(1)), int(match.group(2))
|
||||
if t1 < 150: # Likely bed temp
|
||||
result["bed_temp_min"] = t1
|
||||
result["bed_temp_max"] = t2
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def main(api_url: str, dry_run: bool = True):
|
||||
"""Main function to parse temps from comments."""
|
||||
print(f"Connecting to: {api_url}")
|
||||
|
||||
# Get all filaments
|
||||
response = requests.get(f"{api_url}/filament")
|
||||
response.raise_for_status()
|
||||
filaments = response.json()
|
||||
|
||||
print(f"\nFound {len(filaments)} filaments total")
|
||||
|
||||
updated_count = 0
|
||||
for fil in filaments:
|
||||
comment = fil.get("comment")
|
||||
if not comment:
|
||||
continue
|
||||
|
||||
parsed = parse_temps_from_comment(comment)
|
||||
|
||||
# Check if we found any temps
|
||||
has_extruder = parsed["extruder_temp_min"] is not None
|
||||
has_bed = parsed["bed_temp_min"] is not None
|
||||
|
||||
if has_extruder or has_bed:
|
||||
print(f"\n--- Filament #{fil['id']}: {fil.get('name', 'unnamed')} ---")
|
||||
print(f" Comment: {comment}")
|
||||
print(f" Parsed extruder: {parsed['extruder_temp_min']}-{parsed['extruder_temp_max']} °C" if has_extruder else " No extruder temp found")
|
||||
print(f" Parsed bed: {parsed['bed_temp_min']}-{parsed['bed_temp_max']} °C" if has_bed else " No bed temp found")
|
||||
|
||||
current_extruder_min = fil.get("settings_extruder_temp_min")
|
||||
current_bed_min = fil.get("settings_bed_temp_min")
|
||||
print(f" Current extruder: {current_extruder_min}-{fil.get('settings_extruder_temp_max')}" if current_extruder_min else " Current extruder: Not set")
|
||||
print(f" Current bed: {current_bed_min}-{fil.get('settings_bed_temp_max')}" if current_bed_min else " Current bed: Not set")
|
||||
|
||||
if not dry_run:
|
||||
update_data = {}
|
||||
|
||||
# Only update if not already set
|
||||
if has_extruder and not current_extruder_min:
|
||||
update_data["settings_extruder_temp_min"] = parsed["extruder_temp_min"]
|
||||
update_data["settings_extruder_temp_max"] = parsed["extruder_temp_max"]
|
||||
if has_bed and not current_bed_min:
|
||||
update_data["settings_bed_temp_min"] = parsed["bed_temp_min"]
|
||||
update_data["settings_bed_temp_max"] = parsed["bed_temp_max"]
|
||||
|
||||
# Clear comment after parsing
|
||||
update_data["comment"] = ""
|
||||
|
||||
if update_data:
|
||||
response = requests.patch(f"{api_url}/filament/{fil['id']}", json=update_data)
|
||||
response.raise_for_status()
|
||||
print(" → Updated and cleared comment")
|
||||
|
||||
updated_count += 1
|
||||
|
||||
if dry_run:
|
||||
print(f"\n[DRY RUN] Would update {updated_count} filaments")
|
||||
print("Run with --apply to actually make changes")
|
||||
else:
|
||||
print(f"\n✓ Updated {updated_count} filaments")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(description="Parse temps from filament comments via API")
|
||||
parser.add_argument("--url", default="http://192.168.0.11:8000/api/v1", help="API URL (default: http://192.168.0.11:8000/api/v1)")
|
||||
parser.add_argument("--apply", action="store_true", help="Actually apply changes (default is dry run)")
|
||||
args = parser.parse_args()
|
||||
|
||||
main(api_url=args.url, dry_run=not args.apply)
|
||||
190
scripts/parse_temps_from_comments.py
Normal file
190
scripts/parse_temps_from_comments.py
Normal file
@@ -0,0 +1,190 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Parse temperature values from filament comments and update temp range fields."""
|
||||
|
||||
import re
|
||||
import asyncio
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Add the spoolman module to the path
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from spoolman.database.models import Filament
|
||||
|
||||
|
||||
def parse_temps_from_comment(comment: str) -> dict:
|
||||
"""Parse extruder and bed temps from a comment string.
|
||||
|
||||
Patterns to match:
|
||||
- "extrude 190-230" or "extruder 190-230" or "nozzle 190-230"
|
||||
- "bed 50-70"
|
||||
- "190-230 / 50-70" (extruder / bed)
|
||||
- "190-230C" or "190-230°C"
|
||||
- Single values like "extrude 210" → min=max=210
|
||||
"""
|
||||
result = {
|
||||
"extruder_temp_min": None,
|
||||
"extruder_temp_max": None,
|
||||
"bed_temp_min": None,
|
||||
"bed_temp_max": None,
|
||||
}
|
||||
|
||||
if not comment:
|
||||
return result
|
||||
|
||||
comment_lower = comment.lower()
|
||||
|
||||
# Pattern for temperature ranges: 190-230 or 190 - 230
|
||||
temp_range_pattern = r"(\d{2,3})\s*[-–]\s*(\d{2,3})"
|
||||
# Pattern for single temp: 210
|
||||
single_temp_pattern = r"(\d{2,3})"
|
||||
|
||||
# Try to find extruder temp (usually higher values, 150-300 range)
|
||||
extruder_keywords = ["extrude", "extruder", "nozzle", "hotend", "hot end", "printing temp"]
|
||||
for keyword in extruder_keywords:
|
||||
if keyword in comment_lower:
|
||||
# Find temp after keyword
|
||||
idx = comment_lower.index(keyword)
|
||||
search_area = comment[idx:idx+50] # Look in next 50 chars
|
||||
|
||||
# Try range first
|
||||
match = re.search(temp_range_pattern, search_area)
|
||||
if match:
|
||||
result["extruder_temp_min"] = int(match.group(1))
|
||||
result["extruder_temp_max"] = int(match.group(2))
|
||||
break
|
||||
# Try single value
|
||||
match = re.search(single_temp_pattern, search_area[len(keyword):])
|
||||
if match:
|
||||
val = int(match.group(1))
|
||||
if 150 <= val <= 350: # Reasonable extruder temp range
|
||||
result["extruder_temp_min"] = val
|
||||
result["extruder_temp_max"] = val
|
||||
break
|
||||
|
||||
# Try to find bed temp (usually lower values, 0-120 range)
|
||||
bed_keywords = ["bed", "plate", "build plate"]
|
||||
for keyword in bed_keywords:
|
||||
if keyword in comment_lower:
|
||||
# Find temp after keyword
|
||||
idx = comment_lower.index(keyword)
|
||||
search_area = comment[idx:idx+40] # Look in next 40 chars
|
||||
|
||||
# Try range first
|
||||
match = re.search(temp_range_pattern, search_area)
|
||||
if match:
|
||||
result["bed_temp_min"] = int(match.group(1))
|
||||
result["bed_temp_max"] = int(match.group(2))
|
||||
break
|
||||
# Try single value
|
||||
match = re.search(single_temp_pattern, search_area[len(keyword):])
|
||||
if match:
|
||||
val = int(match.group(1))
|
||||
if 0 <= val <= 150: # Reasonable bed temp range
|
||||
result["bed_temp_min"] = val
|
||||
result["bed_temp_max"] = val
|
||||
break
|
||||
|
||||
# If no keywords found, try to find generic temp pattern
|
||||
# Format: "190-230 / 50-70" (extruder / bed separated by /)
|
||||
if result["extruder_temp_min"] is None and "/" in comment:
|
||||
parts = comment.split("/")
|
||||
if len(parts) >= 2:
|
||||
# First part might be extruder
|
||||
match = re.search(temp_range_pattern, parts[0])
|
||||
if match:
|
||||
t1, t2 = int(match.group(1)), int(match.group(2))
|
||||
if t1 > 100: # Likely extruder temp
|
||||
result["extruder_temp_min"] = t1
|
||||
result["extruder_temp_max"] = t2
|
||||
|
||||
# Second part might be bed
|
||||
match = re.search(temp_range_pattern, parts[1])
|
||||
if match:
|
||||
t1, t2 = int(match.group(1)), int(match.group(2))
|
||||
if t1 < 150: # Likely bed temp
|
||||
result["bed_temp_min"] = t1
|
||||
result["bed_temp_max"] = t2
|
||||
|
||||
return result
|
||||
|
||||
|
||||
async def main(db_path: str = None, dry_run: bool = True):
|
||||
"""Main function to parse temps from comments."""
|
||||
# Determine database URL
|
||||
if db_path:
|
||||
db_url = f"sqlite+aiosqlite:///{db_path}"
|
||||
else:
|
||||
# Default to local spoolman database
|
||||
default_path = Path.home() / ".local/share/spoolman/spoolman.db"
|
||||
if not default_path.exists():
|
||||
# Try the project directory
|
||||
default_path = Path(__file__).parent.parent / "spoolman.db"
|
||||
db_url = f"sqlite+aiosqlite:///{default_path}"
|
||||
|
||||
print(f"Connecting to: {db_url}")
|
||||
|
||||
engine = create_async_engine(db_url, echo=False)
|
||||
async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
|
||||
|
||||
async with async_session() as session:
|
||||
# Get all filaments with comments
|
||||
result = await session.execute(select(Filament))
|
||||
filaments = result.scalars().all()
|
||||
|
||||
print(f"\nFound {len(filaments)} filaments total")
|
||||
|
||||
updated_count = 0
|
||||
for fil in filaments:
|
||||
if not fil.comment:
|
||||
continue
|
||||
|
||||
parsed = parse_temps_from_comment(fil.comment)
|
||||
|
||||
# Check if we found any temps
|
||||
has_extruder = parsed["extruder_temp_min"] is not None
|
||||
has_bed = parsed["bed_temp_min"] is not None
|
||||
|
||||
if has_extruder or has_bed:
|
||||
print(f"\n--- Filament #{fil.id}: {fil.name or 'unnamed'} ---")
|
||||
print(f" Comment: {fil.comment}")
|
||||
print(f" Parsed extruder: {parsed['extruder_temp_min']}-{parsed['extruder_temp_max']} °C" if has_extruder else " No extruder temp found")
|
||||
print(f" Parsed bed: {parsed['bed_temp_min']}-{parsed['bed_temp_max']} °C" if has_bed else " No bed temp found")
|
||||
print(f" Current extruder: {fil.settings_extruder_temp_min}-{fil.settings_extruder_temp_max}" if fil.settings_extruder_temp_min else " Current extruder: Not set")
|
||||
print(f" Current bed: {fil.settings_bed_temp_min}-{fil.settings_bed_temp_max}" if fil.settings_bed_temp_min else " Current bed: Not set")
|
||||
|
||||
if not dry_run:
|
||||
# Only update if not already set
|
||||
if has_extruder and not fil.settings_extruder_temp_min:
|
||||
fil.settings_extruder_temp_min = parsed["extruder_temp_min"]
|
||||
fil.settings_extruder_temp_max = parsed["extruder_temp_max"]
|
||||
if has_bed and not fil.settings_bed_temp_min:
|
||||
fil.settings_bed_temp_min = parsed["bed_temp_min"]
|
||||
fil.settings_bed_temp_max = parsed["bed_temp_max"]
|
||||
|
||||
# Clear comment after parsing
|
||||
fil.comment = None
|
||||
print(" → Updated and cleared comment")
|
||||
|
||||
updated_count += 1
|
||||
|
||||
if not dry_run and updated_count > 0:
|
||||
await session.commit()
|
||||
print(f"\n✓ Committed changes to {updated_count} filaments")
|
||||
elif dry_run:
|
||||
print(f"\n[DRY RUN] Would update {updated_count} filaments")
|
||||
print("Run with --apply to actually make changes")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(description="Parse temps from filament comments")
|
||||
parser.add_argument("--db", help="Path to SQLite database file")
|
||||
parser.add_argument("--apply", action="store_true", help="Actually apply changes (default is dry run)")
|
||||
args = parser.parse_args()
|
||||
|
||||
asyncio.run(main(db_path=args.db, dry_run=not args.apply))
|
||||
Reference in New Issue
Block a user