From 95911e680572200503bf2984556f25b60b867cdf Mon Sep 17 00:00:00 2001 From: Nicolas Predella Date: Mon, 18 May 2026 16:39:12 +0300 Subject: [PATCH] metadata --- core/nodeslicer/nodes.py | 91 ++++++++++++++++++++++++++++++++-------- 1 file changed, 74 insertions(+), 17 deletions(-) diff --git a/core/nodeslicer/nodes.py b/core/nodeslicer/nodes.py index 8ac1ee6..e13f06f 100644 --- a/core/nodeslicer/nodes.py +++ b/core/nodeslicer/nodes.py @@ -1151,16 +1151,16 @@ class SLICENODE_StartPrint(ExecSliceNode): return 'ok' -@register_class @register_class class SLICENODE_BaserowSubmit(ExecSliceNode): bl_label = "Baserow Submit" button_label = "Submit to Baserow" host: NfStringProperty(name="Host", default="https://baserow.io") # pyright: ignore[reportInvalidTypeForm] - token: NfStringProperty(name="Token", default="") # pyright: ignore[reportInvalidTypeForm] + token_path: NfStringProperty(name="Token File", subtype='FILE_PATH', default="") # pyright: ignore[reportInvalidTypeForm] table_id: NfStringProperty(name="Table ID", default="") # pyright: ignore[reportInvalidTypeForm] record_id: NfStringProperty(name="Record ID", default="") # pyright: ignore[reportInvalidTypeForm] + gcode_field: NfStringProperty(name="Gcode Field", default="GCODE") # pyright: ignore[reportInvalidTypeForm] def init(self, context): self._init_exec() @@ -1170,13 +1170,17 @@ class SLICENODE_BaserowSubmit(ExecSliceNode): def draw_buttons(self, context, layout): super().draw_buttons(context, layout) layout.prop(self, "host") - layout.prop(self, "token") + layout.prop(self, "token_path") layout.prop(self, "table_id") layout.prop(self, "record_id") + layout.prop(self, "gcode_field") def evaluate(self, operator=None): from ...infra.networking.baserow_backend import BaserowBackend import tempfile + import re + import json + import os from pathlib import Path gcode: str = resolve_input(self.inputs.get("Gcode")) @@ -1184,31 +1188,84 @@ class SLICENODE_BaserowSubmit(ExecSliceNode): if operator: operator.report({'ERROR'}, "No Gcode to send") return 'no gcode' - if not self.token or not self.table_id or not self.host: - if operator: operator.report({'ERROR'}, "Baserow configuration incomplete") + filename: str = resolve_input(self.inputs.get("Filename")) or "output.gcode" + if not filename.endswith(".gcode"): + filename += ".gcode" + + token = "" + if self.token_path: + abs_token_path = bpy.path.abspath(self.token_path) + if os.path.exists(abs_token_path): + try: + with open(abs_token_path, 'r', encoding='utf-8') as f: + token = f.read().strip() + except Exception as e: + if operator: operator.report({'ERROR'}, f"Failed to read token file: {str(e)}") + return 'error' + + if not token or not self.table_id or not self.host: + if operator: operator.report({'ERROR'}, "Baserow configuration incomplete (Token File, Table ID and Host required)") return 'incomplete config' - db = BaserowBackend(self.host, self.token) + # Parse Metadata from Gcode + fields = {} + part_name = filename.split(".")[0] + fields["Part ID / Name"] = part_name + + # 1. Time per print (decimal hours) + time_match = re.search(r"; estimated printing time \(normal mode\) = (.*)", gcode) + if time_match: + time_str = time_match.group(1) + h = re.search(r"(\d+)h", time_str) + m = re.search(r"(\d+)m", time_str) + s = re.search(r"(\d+)s", time_str) + total_hours = 0.0 + if h: total_hours += float(h.group(1)) + if m: total_hours += float(m.group(1)) / 60.0 + if s: total_hours += float(s.group(1)) / 3600.0 + fields["Time per print"] = round(total_hours, 2) - with tempfile.NamedTemporaryFile(mode='w', suffix='.gcode', delete=False) as tf: - tf.write(gcode) - temp_path = Path(tf.name) + # 2. Parts per print + parts_match = re.search(r"; objects_info = (.*)", gcode) + if parts_match: + try: + info = json.loads(parts_match.group(1)) + fields["Parts per print"] = len(info.get("objects", [])) + except Exception: pass + + # 3. Weight per print + weight_match = re.search(r"; total filament used \[g\] = ([\d\.]+)", gcode) + if weight_match: + fields["Weight per print"] = int(float(weight_match.group(1))) + + db = BaserowBackend(self.host, token) + + temp_dir = tempfile.TemporaryDirectory() + temp_path = Path(temp_dir.name) / filename + with open(temp_path, 'w', encoding='utf-8') as f: + f.write(gcode) try: - if not self.record_id: - res = db.create_record(self.table_id, {}) - record_id = int(res['id']) - else: record_id = self.record_id + if self.record_id: + try: + rid = int(self.record_id) + res = db.upsert_record(self.table_id, rid, fields) + except ValueError: + if operator: operator.report({'ERROR'}, "Record ID must be an integer") + return 'error' + else: + res = db.create_record(self.table_id, fields) - db.upload_attachment(self.table_id, record_id, 'GCODE', temp_path) + record_id = int(res['id']) + db.upload_attachment(self.table_id, record_id, self.gcode_field, temp_path) + + if operator: operator.report({'INFO'}, f"Submitted to Baserow (Record: {record_id})") return 'ok' - except Exception as e: if operator: operator.report({'ERROR'}, f"Baserow Error: {str(e)}") return 'error' finally: - if temp_path.exists(): - temp_path.unlink() + temp_dir.cleanup() @register_class class SLICENODE_PrusaslicerSlice(ExecSliceNode):