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
- parse_temps_api.py - Parse temps via API (for remote instances) - parse_temps_from_comments.py - Parse temps via direct DB access Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
191 lines
7.6 KiB
Python
191 lines
7.6 KiB
Python
#!/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))
|