This commit is contained in:
Nicolas Predella 2026-05-18 16:39:12 +03:00
parent 02f40c55dc
commit 95911e6805

View file

@ -1151,16 +1151,16 @@ class SLICENODE_StartPrint(ExecSliceNode):
return 'ok' return 'ok'
@register_class
@register_class @register_class
class SLICENODE_BaserowSubmit(ExecSliceNode): class SLICENODE_BaserowSubmit(ExecSliceNode):
bl_label = "Baserow Submit" bl_label = "Baserow Submit"
button_label = "Submit to Baserow" button_label = "Submit to Baserow"
host: NfStringProperty(name="Host", default="https://baserow.io") # pyright: ignore[reportInvalidTypeForm] 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] table_id: NfStringProperty(name="Table ID", default="") # pyright: ignore[reportInvalidTypeForm]
record_id: NfStringProperty(name="Record 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): def init(self, context):
self._init_exec() self._init_exec()
@ -1170,13 +1170,17 @@ class SLICENODE_BaserowSubmit(ExecSliceNode):
def draw_buttons(self, context, layout): def draw_buttons(self, context, layout):
super().draw_buttons(context, layout) super().draw_buttons(context, layout)
layout.prop(self, "host") layout.prop(self, "host")
layout.prop(self, "token") layout.prop(self, "token_path")
layout.prop(self, "table_id") layout.prop(self, "table_id")
layout.prop(self, "record_id") layout.prop(self, "record_id")
layout.prop(self, "gcode_field")
def evaluate(self, operator=None): def evaluate(self, operator=None):
from ...infra.networking.baserow_backend import BaserowBackend from ...infra.networking.baserow_backend import BaserowBackend
import tempfile import tempfile
import re
import json
import os
from pathlib import Path from pathlib import Path
gcode: str = resolve_input(self.inputs.get("Gcode")) 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") if operator: operator.report({'ERROR'}, "No Gcode to send")
return 'no gcode' return 'no gcode'
if not self.token or not self.table_id or not self.host: filename: str = resolve_input(self.inputs.get("Filename")) or "output.gcode"
if operator: operator.report({'ERROR'}, "Baserow configuration incomplete") 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' 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
with tempfile.NamedTemporaryFile(mode='w', suffix='.gcode', delete=False) as tf: # 1. Time per print (decimal hours)
tf.write(gcode) time_match = re.search(r"; estimated printing time \(normal mode\) = (.*)", gcode)
temp_path = Path(tf.name) 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)
# 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: try:
if not self.record_id: if self.record_id:
res = db.create_record(self.table_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)
record_id = int(res['id']) record_id = int(res['id'])
else: record_id = self.record_id db.upload_attachment(self.table_id, record_id, self.gcode_field, temp_path)
db.upload_attachment(self.table_id, record_id, 'GCODE', temp_path) if operator: operator.report({'INFO'}, f"Submitted to Baserow (Record: {record_id})")
return 'ok' return 'ok'
except Exception as e: except Exception as e:
if operator: operator.report({'ERROR'}, f"Baserow Error: {str(e)}") if operator: operator.report({'ERROR'}, f"Baserow Error: {str(e)}")
return 'error' return 'error'
finally: finally:
if temp_path.exists(): temp_dir.cleanup()
temp_path.unlink()
@register_class @register_class
class SLICENODE_PrusaslicerSlice(ExecSliceNode): class SLICENODE_PrusaslicerSlice(ExecSliceNode):