From e264d7ed4e14be06fa6ba928b465d13f45bdacf0 Mon Sep 17 00:00:00 2001 From: tonym Date: Thu, 15 Jan 2026 00:00:00 -0600 Subject: [PATCH] Add scripts to parse temps from comments - 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 --- scripts/parse_temps_api.py | 178 +++++++++++++++++++++++++ scripts/parse_temps_from_comments.py | 190 +++++++++++++++++++++++++++ 2 files changed, 368 insertions(+) create mode 100644 scripts/parse_temps_api.py create mode 100644 scripts/parse_temps_from_comments.py diff --git a/scripts/parse_temps_api.py b/scripts/parse_temps_api.py new file mode 100644 index 0000000..5459452 --- /dev/null +++ b/scripts/parse_temps_api.py @@ -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) diff --git a/scripts/parse_temps_from_comments.py b/scripts/parse_temps_from_comments.py new file mode 100644 index 0000000..0d29a97 --- /dev/null +++ b/scripts/parse_temps_from_comments.py @@ -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))