Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
K
Kadi4
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Lucía Elizo Gómez
Kadi4
Commits
58123b41
Commit
58123b41
authored
Jun 01, 2026
by
Lucía Elizo Gómez
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Plugin biaprint
parent
022cb767
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
27 additions
and
30 deletions
+27
-30
kadi_plugin_biaprint/kadi_plugin_biaprint/plugin.py
+27
-30
No files found.
kadi_plugin_biaprint/kadi_plugin_biaprint/plugin.py
View file @
58123b41
"""
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)
Autenticaci
o
n:
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
s
esion
folder_name (str) nombre de la carpeta
description (str) descripcion
files[] (file, varios) archivos de la
impr
esion
"""
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
binari
o
# Paso 2: enviar
contenid
o
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 d
irectamente d
el 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
"Duraci
o
n 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 colecci
o
n: {exc}"
)
# ── Crear Record por API REST ──────────────────────────────────────────
identifier
=
_make_identifier
(
folder_name
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment