baserow add
This commit is contained in:
parent
118269938f
commit
0c33dae183
4 changed files with 153 additions and 1 deletions
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
87
infra/networking/baserow_backend.py
Normal file
87
infra/networking/baserow_backend.py
Normal 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()
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue