Commit 58123b41 by Lucía Elizo Gómez

Plugin biaprint

parent 022cb767
"""
Kadi4Mat plugin: Biaprint
--------------------------
Endpoint para recibir sesiones de impresion 3D desde la Biaprint,
Endpoint para recibir sesiones de impresion 3D desde la Raspberry Pi4,
crear un Record con metricas y archivos, y vincularlo a coleccion
del año actual.
......@@ -12,13 +12,13 @@ Hooks:
Endpoint:
POST /api/biaprint/folder
Autenticación:
Authorization: Bearer <personal_access_token> (scope: record.create + collection.create)
Autenticacion:
Authorization: Bearer <personal_access_token> (scope: record.create + collection.create (full access para evitar fallos))
Form-data:
folder_name (str) nombre de la carpeta / sesion
description (str) descripcion opcional
files[] (file, varios) archivos de la sesion
folder_name (str) nombre de la carpeta
description (str) descripcion
files[] (file, varios) archivos de la impresion
"""
import json
......@@ -91,27 +91,27 @@ def _analizar_sesion(samples: list) -> dict:
def safe_mean(lst): return round(sum(lst) / len(lst), 3) if lst else None
# ── Tiempo ──
t_inicio = primero.get("wall_time_utc", "")
t_fin = ultimo.get("wall_time_utc", "")
t_inicio = primero.get("wall_time_utc", "") # Primer valor de tiempo (sample = 0) (Tiempo inicio)
t_fin = ultimo.get("wall_time_utc", "") # Ultimo valor de tiempo del ultimo sample (Tiempo fin)
duracion_s = None
try:
dt0 = datetime.fromisoformat(t_inicio)
dt1 = datetime.fromisoformat(t_fin)
duracion_s = round((dt1 - dt0).total_seconds(), 1)
duracion_s = round((dt1 - dt0).total_seconds(), 1) # Duracion total en segundos
except Exception:
pass
# ── Hotend ──
hotend_temps = vals(["hotend", "temp_c"])
hotend_temps = vals(["hotend", "temp_c"]) # Crea un array con todos los valores de la temperatura del hotend
hotend_target = None
for s in samples:
t = s.get("printer", {}).get("hotend", {}).get("target_c")
t = s.get("printer", {}).get("hotend", {}).get("target_c") # Busca la temperatura objetivo
if t and t > 0:
hotend_target = t
break
# ── Cama ──
cama_temps = vals(["cama", "temp_c"])
cama_temps = vals(["cama", "temp_c"]) # Crea un array con todos los valores de la temperatura de la cama
cama_target = None
for s in samples:
t = s.get("printer", {}).get("cama", {}).get("target_c")
......@@ -119,7 +119,8 @@ def _analizar_sesion(samples: list) -> dict:
cama_target = t
break
return {
# Diccionario metadatos
return {
# Identificacion
"gcode_file": primero.get("printer", {}).get("estado", {}).get("filename", ""),
"n_muestras": len(samples),
......@@ -205,12 +206,12 @@ def _generar_tags(metricas: dict) -> list:
else:
tags.append("impresion-larga")
# Estado de la sesion
# Estado de la sesion
state = metricas.get("state")
if state == "complete":
tags.append("Impresion-completa")
tags.append("Impresion-completa")
elif state in ("cancelled", "error"):
tags.append("Impresion-incompleta")
tags.append("Impresion-incompleta")
# Filamento
fil = metricas.get("filamento_usado_mm")
......@@ -236,7 +237,7 @@ def _build_extras(metricas: dict) -> list:
def _is_camera_photo(filename: str) -> bool:
"""Devuelve True si el archivo viene de una subcarpeta de cámara
"""Devuelve True si el archivo viene de una subcarpeta
(cam1/ o cam2/) y tiene formato de imagen. """
parts = Path(filename).parts
ext = Path(filename).suffix.lower()
......@@ -244,8 +245,7 @@ def _is_camera_photo(filename: str) -> bool:
def _get_or_create_collection(host: str, pat: str, year_title: str):
"""Busca la colección del año por título exacto.
Si no existe, la crea."""
"""Busca la coleccion (año actual) por titulo. Si no existe, la crea."""
import requests as req
session = req.Session()
......@@ -331,10 +331,7 @@ def _upload_file_to_record(host: str, pat: str, record_id: int,
filename: str, data: bytes):
"""Sube un archivo al record:
1. POST /records/{id}/uploads → registra nombre y tamaño
2. PUT /records/{id}/uploads/{upload_id} → envía el binario
Firma corregida: recibe los datos ya leídos (bytes) en lugar de
una Path, porque los ficheros llegan como streams de Flask.
2. PUT /records/{id}/uploads/{upload_id} → envia el contenido
"""
import mimetypes
import requests as req
......@@ -360,7 +357,7 @@ def _upload_file_to_record(host: str, pat: str, record_id: int,
upload_id = r.json()["id"]
# Paso 2: enviar binario
# Paso 2: enviar contenido
r2 = session.put(
f"{host}/api/v1/records/{record_id}/uploads/{upload_id}",
data=data,
......@@ -379,7 +376,7 @@ def _upload_file_to_record(host: str, pat: str, record_id: int,
@api_bp.post("/biaprint/folder")
@scopes_required("record.create")
def upload_folder():
"""Recibe una sesion de impresion 3D de la Biaprint y registra en Kadi4Mat:
"""Recibe una sesion de impresion 3D de la Raspberry Pi y registra en Kadi4Mat:
1. Analiza el .json si viene incluido
2. Calcula metricas, tags y extras
3. Busca o crea la coleccion del año actual
......@@ -403,7 +400,7 @@ def upload_folder():
cfg = get_plugin_config(PLUGIN_NAME)
host = (cfg.get("host", "https://localhost") if cfg else "https://localhost").rstrip("/")
# Extraer el PAT directamente del header Authorization.
# Extraer el PAT del header Authorization.
_auth_header = request.headers.get("Authorization", "")
if _auth_header.lower().startswith("bearer "):
pat = _auth_header[7:].strip()
......@@ -411,7 +408,7 @@ def upload_folder():
return json_error_response(401, description="Header Authorization Bearer requerido.")
# ── Leer todos los archivos en memoria ─────────────────
file_buffers = {} # filename → bytes
file_buffers = {}
for f in uploaded_files:
if f.filename:
file_buffers[f.filename] = f.read()
......@@ -422,7 +419,7 @@ def upload_folder():
extras = []
for filename, data in file_buffers.items():
if Path(filename).name == "session_data.json":
if Path(filename).name == "session_data.json": # El nombre del archivo JSON debe ser este
try:
samples = json.loads(data.decode("utf-8"))
if isinstance(samples, list):
......@@ -443,7 +440,7 @@ def upload_folder():
description = (
f"Sesion de impresion 3D registrada el {fecha}. "
f"Archivo: {gcode}. "
f"Duración de impresion: {dur} s. "
f"Duracion de impresion: {dur} s. "
f"Filamento usado: {fil} mm."
)
......@@ -454,7 +451,7 @@ def upload_folder():
collection_data, col_created = _get_or_create_collection(host, pat, year_title)
collection_id = collection_data["id"]
except Exception as exc:
return json_error_response(500, description=f"Error con la colección: {exc}")
return json_error_response(500, description=f"Error con la coleccion: {exc}")
# ── Crear Record por API REST ──────────────────────────────────────────
identifier = _make_identifier(folder_name)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment