baserow add

This commit is contained in:
Nicolas Predella 2026-05-18 14:56:55 +03:00
parent 118269938f
commit 0c33dae183
4 changed files with 153 additions and 1 deletions

View file

@ -393,9 +393,18 @@ class SLICENODE_GetMesh(SliceNode):
obj: Any = self.resolve("Object") obj: Any = self.resolve("Object")
if not obj: return None if not obj: return None
modifiers = {v.field: v.value_str for k, v in dict(obj.nodeslicer.modifiers).items()}
extruder = obj.nodeslicer.extruder
part_type = obj.nodeslicer.object_type
family = [obj] + (get_all_children(obj) if self.include_children else []) family = [obj] + (get_all_children(obj) if self.include_children else [])
sc: SlicingCollection = SlicingCollection(family, obj.name) sc: SlicingCollection = SlicingCollection(family, obj.name)
for so in sc.objects:
so.assign_modifiers(modifiers)
so.object_type = part_type
so.extruder = extruder
return sc return sc
@ -1142,6 +1151,61 @@ class SLICENODE_StartPrint(ExecSliceNode):
return 'ok' 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]
table_id: NfStringProperty(name="Table ID", default="") # pyright: ignore[reportInvalidTypeForm]
gcode_field: NfStringProperty(name="Gcode Field", default="GCODE") # pyright: ignore[reportInvalidTypeForm]
def init(self, context):
self._init_exec()
self.add_input("SLICESOCK_Gcode")
self.add_input("SLICESOCK_String", "Filename")
def draw_buttons(self, context, layout):
super().draw_buttons(context, layout)
layout.prop(self, "host")
layout.prop(self, "token")
layout.prop(self, "table_id")
layout.prop(self, "gcode_field")
def evaluate(self, operator=None):
from ...infra.networking.baserow_backend import BaserowBackend
import tempfile
from pathlib import Path
gcode: str = resolve_input(self.inputs.get("Gcode"))
if not gcode:
if operator: operator.report({'ERROR'}, "No Gcode to send")
return 'no gcode'
if not self.token or not self.table_id:
if operator: operator.report({'ERROR'}, "Baserow configuration incomplete")
return 'incomplete config'
db = BaserowBackend(self.host, self.token)
with tempfile.NamedTemporaryFile(mode='w', suffix='.gcode', delete=False) as tf:
tf.write(gcode)
temp_path = Path(tf.name)
try:
res = db.create_record(self.table_id, {})
record_id = int(res['id'])
db.upload_attachment(self.table_id, record_id, self.gcode_field, temp_path)
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()
@register_class @register_class
class SLICENODE_PrusaslicerSlice(ExecSliceNode): class SLICENODE_PrusaslicerSlice(ExecSliceNode):
bl_label = "PrusaSlicer Slice" bl_label = "PrusaSlicer Slice"

View file

@ -185,6 +185,7 @@ class SLICENODE_MT_Menu(Menu):
layout.operator("node.add_node", text="Gcode to File").type = "SLICENODE_GcodeToFile" layout.operator("node.add_node", text="Gcode to File").type = "SLICENODE_GcodeToFile"
layout.operator("node.add_node", text="Gcode to Text Block").type = "SLICENODE_GcodeToTextBlock" layout.operator("node.add_node", text="Gcode to Text Block").type = "SLICENODE_GcodeToTextBlock"
layout.operator("node.add_node", text="Start Print").type = "SLICENODE_StartPrint" layout.operator("node.add_node", text="Start Print").type = "SLICENODE_StartPrint"
layout.operator("node.add_node", text="Baserow Submit").type = "SLICENODE_BaserowSubmit"
layout.operator("node.add_node", text="Gcode Preview").type = "SLICENODE_GcodeToMesh" layout.operator("node.add_node", text="Gcode Preview").type = "SLICENODE_GcodeToMesh"
layout.operator("node.add_node", text="PrusaSlicer Slice").type = "SLICENODE_PrusaslicerSlice" layout.operator("node.add_node", text="PrusaSlicer Slice").type = "SLICENODE_PrusaslicerSlice"
layout.operator("node.add_node", text="Executor").type = "SLICENODE_Executor" layout.operator("node.add_node", text="Executor").type = "SLICENODE_Executor"

View file

@ -0,0 +1,87 @@
from pathlib import Path
import requests # pyright: ignore[reportMissingModuleSource]
from typing import Any
class BaserowBackend:
def __init__(self, host: str, token: str):
self.host = host.rstrip('/')
self.token = token
self.headers = {
"Authorization": f"Token {self.token}",
"Content-Type": "application/json"
}
def fetch(self, table_id: str, filter_formula: str | None = None) -> list[dict]:
url = f"{self.host}/api/database/rows/table/{table_id}/"
params = {"user_field_names": "true"}
rows = []
while url:
response = requests.get(url, headers=self.headers, params=params)
response.raise_for_status()
data = response.json()
rows.extend(data.get("results", []))
url = data.get("next")
params = {}
if filter_formula:
# Simple client-side filtering to mimic Airtable formulas
import re
match = re.search(r"^({)?(.*?)(})?\s*=\s*['\"]?(.*?)['\"]?$", filter_formula)
if match:
_, f, _, v = match.groups()
filtered_rows = []
for r in rows:
actual_val = r['id'] if f in ('RECORD_ID()', 'ID') else r.get(f)
# Direct comparison
if str(actual_val) == str(v):
filtered_rows.append(r)
continue
# Numeric comparison fallback
try:
if float(actual_val) == float(v):
filtered_rows.append(r)
except (ValueError, TypeError):
pass
rows = filtered_rows
return rows
def create_record(self, table_id: str, fields: dict[str, Any]) -> dict:
url = f"{self.host}/api/database/rows/table/{table_id}/"
response = requests.post(url, headers=self.headers, json=fields, params={"user_field_names": "true"})
try:
response.raise_for_status()
except requests.exceptions.HTTPError as e:
print(f"Baserow Create Error: {response.status_code} - {response.text}")
raise e
return response.json()
def update_record(self, table_id: str, record_id: int, fields: dict[str, Any]) -> dict:
url = f"{self.host}/api/database/rows/table/{table_id}/{record_id}/"
response = requests.patch(url, headers=self.headers, json=fields, params={"user_field_names": "true"})
try:
response.raise_for_status()
except requests.exceptions.HTTPError as e:
print(f"Baserow Update Error: {response.status_code} - {response.text}")
raise e
return response.json()
def upload_attachment(self, table_id: str, record_id: int, field_id: str, filepath: str | Path) -> Any:
# 1. Upload the file to Baserow
with open(filepath, "rb") as f:
response = requests.post(
f"{self.host}/api/user-files/upload-file/",
headers={"Authorization": f"Token {self.token}"},
files={'file': f}
)
response.raise_for_status()
file_name = response.json()['name']
# 2. Link the uploaded file to the record
url = f"{self.host}/api/database/rows/table/{table_id}/{record_id}/"
payload = {field_id: [{"name": file_name}]}
response = requests.patch(url, headers=self.headers, json=payload, params={"user_field_names": "true"})
response.raise_for_status()
return response.json()

View file

@ -15,7 +15,7 @@ def with_api_state(api_state: str) -> Callable[..., Any]:
from dataclasses import dataclass, field from dataclasses import dataclass, field
from pathlib import Path from pathlib import Path
from typing import Protocol, Any, Optional from typing import Protocol, Any, Optional
import requests import requests # pyright: ignore[reportMissingModuleSource]
import time import time
@dataclass(frozen=True) @dataclass(frozen=True)