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
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
23 additions
and
26 deletions
+23
-26
kadi_plugin_biaprint/kadi_plugin_biaprint/plugin.py
+23
-26
No files found.
kadi_plugin_biaprint/kadi_plugin_biaprint/plugin.py
View file @
58123b41
"""
"""
Kadi4Mat plugin: Biaprint
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
crear un Record con metricas y archivos, y vincularlo a coleccion
del año actual.
del año actual.
...
@@ -12,13 +12,13 @@ Hooks:
...
@@ -12,13 +12,13 @@ Hooks:
Endpoint:
Endpoint:
POST /api/biaprint/folder
POST /api/biaprint/folder
Autenticaci
ó
n:
Autenticaci
o
n:
Authorization: Bearer <personal_access_token> (scope: record.create + collection.create)
Authorization: Bearer <personal_access_token> (scope: record.create + collection.create
(full access para evitar fallos)
)
Form-data:
Form-data:
folder_name (str) nombre de la carpeta
/ sesion
folder_name (str) nombre de la carpeta
description (str) descripcion
opcional
description (str) descripcion
files[] (file, varios) archivos de la
s
esion
files[] (file, varios) archivos de la
impr
esion
"""
"""
import
json
import
json
...
@@ -91,27 +91,27 @@ def _analizar_sesion(samples: list) -> dict:
...
@@ -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
def
safe_mean
(
lst
):
return
round
(
sum
(
lst
)
/
len
(
lst
),
3
)
if
lst
else
None
# ── Tiempo ──
# ── Tiempo ──
t_inicio
=
primero
.
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"
,
""
)
t_fin
=
ultimo
.
get
(
"wall_time_utc"
,
""
)
# Ultimo valor de tiempo del ultimo sample (Tiempo fin)
duracion_s
=
None
duracion_s
=
None
try
:
try
:
dt0
=
datetime
.
fromisoformat
(
t_inicio
)
dt0
=
datetime
.
fromisoformat
(
t_inicio
)
dt1
=
datetime
.
fromisoformat
(
t_fin
)
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
:
except
Exception
:
pass
pass
# ── Hotend ──
# ── 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
hotend_target
=
None
for
s
in
samples
:
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
:
if
t
and
t
>
0
:
hotend_target
=
t
hotend_target
=
t
break
break
# ── Cama ──
# ── 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
cama_target
=
None
for
s
in
samples
:
for
s
in
samples
:
t
=
s
.
get
(
"printer"
,
{})
.
get
(
"cama"
,
{})
.
get
(
"target_c"
)
t
=
s
.
get
(
"printer"
,
{})
.
get
(
"cama"
,
{})
.
get
(
"target_c"
)
...
@@ -119,6 +119,7 @@ def _analizar_sesion(samples: list) -> dict:
...
@@ -119,6 +119,7 @@ def _analizar_sesion(samples: list) -> dict:
cama_target
=
t
cama_target
=
t
break
break
# Diccionario metadatos
return
{
return
{
# Identificacion
# Identificacion
"gcode_file"
:
primero
.
get
(
"printer"
,
{})
.
get
(
"estado"
,
{})
.
get
(
"filename"
,
""
),
"gcode_file"
:
primero
.
get
(
"printer"
,
{})
.
get
(
"estado"
,
{})
.
get
(
"filename"
,
""
),
...
@@ -236,7 +237,7 @@ def _build_extras(metricas: dict) -> list:
...
@@ -236,7 +237,7 @@ def _build_extras(metricas: dict) -> list:
def
_is_camera_photo
(
filename
:
str
)
->
bool
:
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. """
(cam1/ o cam2/) y tiene formato de imagen. """
parts
=
Path
(
filename
)
.
parts
parts
=
Path
(
filename
)
.
parts
ext
=
Path
(
filename
)
.
suffix
.
lower
()
ext
=
Path
(
filename
)
.
suffix
.
lower
()
...
@@ -244,8 +245,7 @@ def _is_camera_photo(filename: str) -> bool:
...
@@ -244,8 +245,7 @@ def _is_camera_photo(filename: str) -> bool:
def
_get_or_create_collection
(
host
:
str
,
pat
:
str
,
year_title
:
str
):
def
_get_or_create_collection
(
host
:
str
,
pat
:
str
,
year_title
:
str
):
"""Busca la colección del año por título exacto.
"""Busca la coleccion (año actual) por titulo. Si no existe, la crea."""
Si no existe, la crea."""
import
requests
as
req
import
requests
as
req
session
=
req
.
Session
()
session
=
req
.
Session
()
...
@@ -331,10 +331,7 @@ def _upload_file_to_record(host: str, pat: str, record_id: int,
...
@@ -331,10 +331,7 @@ def _upload_file_to_record(host: str, pat: str, record_id: int,
filename
:
str
,
data
:
bytes
):
filename
:
str
,
data
:
bytes
):
"""Sube un archivo al record:
"""Sube un archivo al record:
1. POST /records/{id}/uploads → registra nombre y tamaño
1. POST /records/{id}/uploads → registra nombre y tamaño
2. PUT /records/{id}/uploads/{upload_id} → envía el binario
2. PUT /records/{id}/uploads/{upload_id} → envia el contenido
Firma corregida: recibe los datos ya leídos (bytes) en lugar de
una Path, porque los ficheros llegan como streams de Flask.
"""
"""
import
mimetypes
import
mimetypes
import
requests
as
req
import
requests
as
req
...
@@ -360,7 +357,7 @@ def _upload_file_to_record(host: str, pat: str, record_id: int,
...
@@ -360,7 +357,7 @@ def _upload_file_to_record(host: str, pat: str, record_id: int,
upload_id
=
r
.
json
()[
"id"
]
upload_id
=
r
.
json
()[
"id"
]
# Paso 2: enviar
binari
o
# Paso 2: enviar
contenid
o
r2
=
session
.
put
(
r2
=
session
.
put
(
f
"{host}/api/v1/records/{record_id}/uploads/{upload_id}"
,
f
"{host}/api/v1/records/{record_id}/uploads/{upload_id}"
,
data
=
data
,
data
=
data
,
...
@@ -379,7 +376,7 @@ def _upload_file_to_record(host: str, pat: str, record_id: int,
...
@@ -379,7 +376,7 @@ def _upload_file_to_record(host: str, pat: str, record_id: int,
@api_bp.post
(
"/biaprint/folder"
)
@api_bp.post
(
"/biaprint/folder"
)
@scopes_required
(
"record.create"
)
@scopes_required
(
"record.create"
)
def
upload_folder
():
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
1. Analiza el .json si viene incluido
2. Calcula metricas, tags y extras
2. Calcula metricas, tags y extras
3. Busca o crea la coleccion del año actual
3. Busca o crea la coleccion del año actual
...
@@ -403,7 +400,7 @@ def upload_folder():
...
@@ -403,7 +400,7 @@ def upload_folder():
cfg
=
get_plugin_config
(
PLUGIN_NAME
)
cfg
=
get_plugin_config
(
PLUGIN_NAME
)
host
=
(
cfg
.
get
(
"host"
,
"https://localhost"
)
if
cfg
else
"https://localhost"
)
.
rstrip
(
"/"
)
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"
,
""
)
_auth_header
=
request
.
headers
.
get
(
"Authorization"
,
""
)
if
_auth_header
.
lower
()
.
startswith
(
"bearer "
):
if
_auth_header
.
lower
()
.
startswith
(
"bearer "
):
pat
=
_auth_header
[
7
:]
.
strip
()
pat
=
_auth_header
[
7
:]
.
strip
()
...
@@ -411,7 +408,7 @@ def upload_folder():
...
@@ -411,7 +408,7 @@ def upload_folder():
return
json_error_response
(
401
,
description
=
"Header Authorization Bearer requerido."
)
return
json_error_response
(
401
,
description
=
"Header Authorization Bearer requerido."
)
# ── Leer todos los archivos en memoria ─────────────────
# ── Leer todos los archivos en memoria ─────────────────
file_buffers
=
{}
# filename → bytes
file_buffers
=
{}
for
f
in
uploaded_files
:
for
f
in
uploaded_files
:
if
f
.
filename
:
if
f
.
filename
:
file_buffers
[
f
.
filename
]
=
f
.
read
()
file_buffers
[
f
.
filename
]
=
f
.
read
()
...
@@ -422,7 +419,7 @@ def upload_folder():
...
@@ -422,7 +419,7 @@ def upload_folder():
extras
=
[]
extras
=
[]
for
filename
,
data
in
file_buffers
.
items
():
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
:
try
:
samples
=
json
.
loads
(
data
.
decode
(
"utf-8"
))
samples
=
json
.
loads
(
data
.
decode
(
"utf-8"
))
if
isinstance
(
samples
,
list
):
if
isinstance
(
samples
,
list
):
...
@@ -443,7 +440,7 @@ def upload_folder():
...
@@ -443,7 +440,7 @@ def upload_folder():
description
=
(
description
=
(
f
"Sesion de impresion 3D registrada el {fecha}. "
f
"Sesion de impresion 3D registrada el {fecha}. "
f
"Archivo: {gcode}. "
f
"Archivo: {gcode}. "
f
"Duraci
ó
n de impresion: {dur} s. "
f
"Duraci
o
n de impresion: {dur} s. "
f
"Filamento usado: {fil} mm."
f
"Filamento usado: {fil} mm."
)
)
...
@@ -454,7 +451,7 @@ def upload_folder():
...
@@ -454,7 +451,7 @@ def upload_folder():
collection_data
,
col_created
=
_get_or_create_collection
(
host
,
pat
,
year_title
)
collection_data
,
col_created
=
_get_or_create_collection
(
host
,
pat
,
year_title
)
collection_id
=
collection_data
[
"id"
]
collection_id
=
collection_data
[
"id"
]
except
Exception
as
exc
:
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 ──────────────────────────────────────────
# ── Crear Record por API REST ──────────────────────────────────────────
identifier
=
_make_identifier
(
folder_name
)
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