PrintVault: remove GCode segment cap + fix PHP upload limits
Deploy HouseHub / deploy (push) Successful in 2s

- PHP upload: 20M → 300M, post_max: 25M → 310M, memory: 256M → 512M
- GCode parser: no segment cap (full file), flat array format for compact JSON
  491k segments = 0.8s parse, 17MB JSON on 80MB GCode file
- Viewer JS updated for flat array stride-7 format

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
perco
2026-05-15 11:46:13 +02:00
co-authored by Claude Sonnet 4.6
parent 237de1e529
commit f2b1c05356
3 changed files with 36 additions and 32 deletions
+5 -3
View File
@@ -1,3 +1,5 @@
upload_max_filesize = 20M upload_max_filesize = 300M
post_max_size = 25M post_max_size = 310M
memory_limit = 256M memory_limit = 512M
max_execution_time = 120
max_input_time = 120
+8 -4
View File
@@ -241,7 +241,7 @@ if ($action === 'models') {
// ── GCODE PATHS ──────────────────────────────────────────────────────────────── // ── GCODE PATHS ────────────────────────────────────────────────────────────────
if ($action === 'gcode_paths') { if ($action === 'gcode_paths') {
$id = (int)($_GET['id'] ?? 0); $id = (int)($_GET['id'] ?? 0);
$max = min((int)($_GET['max'] ?? 300000), 500000); $max = (int)($_GET['max'] ?? 0) ?: PHP_INT_MAX; // no cap by default
$s = $pdo->prepare("SELECT filename, file_type FROM pf_pv_models WHERE id=?"); $s->execute([$id]); $s = $pdo->prepare("SELECT filename, file_type FROM pf_pv_models WHERE id=?"); $s->execute([$id]);
$row = $s->fetch(); if (!$row) pvErr('Introuvable', 404); $row = $s->fetch(); if (!$row) pvErr('Introuvable', 404);
$ext = strtolower($row['file_type']); $ext = strtolower($row['file_type']);
@@ -251,7 +251,7 @@ if ($action === 'gcode_paths') {
$segments = []; $segments = [];
$x=0.0; $y=0.0; $z=0.0; $e=0.0; $lastE=0.0; $x=0.0; $y=0.0; $z=0.0; $e=0.0; $lastE=0.0;
$minZ=PHP_FLOAT_MAX; $maxZ=0.0; $minZ=PHP_FLOAT_MAX; $maxZ=0.0; $count=0;
$handle = fopen($path, 'r'); $handle = fopen($path, 'r');
while (!feof($handle) && count($segments) < $max) { while (!feof($handle) && count($segments) < $max) {
@@ -272,15 +272,19 @@ if ($action === 'gcode_paths') {
if (preg_match('/E([-\d.]+)/i', $line, $m)) $ne = (float)$m[1]; if (preg_match('/E([-\d.]+)/i', $line, $m)) $ne = (float)$m[1];
$extruding = ($cmd === 'G1' && $ne > $lastE) ? 1 : 0; $extruding = ($cmd === 'G1' && $ne > $lastE) ? 1 : 0;
if ($nx !== $x || $ny !== $y || $nz !== $z) { if ($nx !== $x || $ny !== $y || $nz !== $z) {
$segments[] = [$x, $y, $z, $nx, $ny, $nz, $extruding]; // Compact flat array: x1,y1,z1,x2,y2,z2,ext (2 decimal precision)
$segments[] = round($x,2); $segments[] = round($y,2); $segments[] = round($z,2);
$segments[] = round($nx,2); $segments[] = round($ny,2); $segments[] = round($nz,2);
$segments[] = $extruding;
$minZ = min($minZ, $nz); $minZ = min($minZ, $nz);
$maxZ = max($maxZ, $nz); $maxZ = max($maxZ, $nz);
$count++;
} }
$x=$nx; $y=$ny; $z=$nz; $x=$nx; $y=$ny; $z=$nz;
if ($ne !== $e) { $lastE = $e = $ne; } if ($ne !== $e) { $lastE = $e = $ne; }
} }
fclose($handle); fclose($handle);
pvOk(['segments' => $segments, 'min_z' => $minZ === PHP_FLOAT_MAX ? 0 : $minZ, 'max_z' => $maxZ, 'total' => count($segments)]); pvOk(['flat' => $segments, 'min_z' => round($minZ === PHP_FLOAT_MAX ? 0 : $minZ, 2), 'max_z' => round($maxZ, 2), 'total' => $count]);
} }
// ── FILE SERVE ───────────────────────────────────────────────────────────────── // ── FILE SERVE ─────────────────────────────────────────────────────────────────
+23 -25
View File
@@ -307,53 +307,51 @@ function zToColor(z, minZ, maxZ) {
document.getElementById('viewer-loading-text').textContent = 'Chargement des trajectoires…'; document.getElementById('viewer-loading-text').textContent = 'Chargement des trajectoires…';
fetch('/modules/printvault/api.php?action=gcode_paths&id=<?= $id ?>&max=300000', { document.getElementById('viewer-loading-text').textContent = 'Parsing GCode…';
fetch('/modules/printvault/api.php?action=gcode_paths&id=<?= $id ?>', {
credentials: 'same-origin', credentials: 'same-origin',
headers: {'X-Requested-With':'XMLHttpRequest','Accept':'application/json'} headers: {'X-Requested-With':'XMLHttpRequest','Accept':'application/json'}
}) })
.then(r => r.json()) .then(r => r.json())
.then(j => { .then(j => {
if (!j.ok) { document.getElementById('viewer-loading-text').textContent = j.error; return; } if (!j.ok) { document.getElementById('viewer-loading-text').textContent = j.error; return; }
const {segments, min_z, max_z} = j.data; const {flat, min_z, max_z, total} = j.data;
// Separate extrusion and travel moves document.getElementById('viewer-loading-text').textContent = 'Construction du rendu…';
const extPositions = [];
const extColors = [];
const travelPositions = [];
segments.forEach(([x1,y1,z1, x2,y2,z2, ext]) => { // flat = [x1,y1,z1,x2,y2,z2,ext, x1,y1,z1,...] stride 7
const extPos=[], extCol=[], travPos=[];
for (let i=0; i < flat.length; i+=7) {
const x1=flat[i],y1=flat[i+1],z1=flat[i+2];
const x2=flat[i+3],y2=flat[i+4],z2=flat[i+5];
const ext=flat[i+6];
if (ext) { if (ext) {
const col = zToColor(z1, min_z, max_z); const col = zToColor(z1, min_z, max_z);
extPositions.push(x1,z1,-y1, x2,z2,-y2); // swap Y/Z for Three.js extPos.push(x1,z1,-y1, x2,z2,-y2);
extColors.push(col.r,col.g,col.b, col.r,col.g,col.b); extCol.push(col.r,col.g,col.b, col.r,col.g,col.b);
} else { } else {
travelPositions.push(x1,z1,-y1, x2,z2,-y2); travPos.push(x1,z1,-y1, x2,z2,-y2);
} }
}); }
const group = new THREE.Group(); const group = new THREE.Group();
if (extPos.length) {
if (extPositions.length) {
const geo = new THREE.BufferGeometry(); const geo = new THREE.BufferGeometry();
geo.setAttribute('position', new THREE.Float32BufferAttribute(extPositions, 3)); geo.setAttribute('position', new THREE.Float32BufferAttribute(extPos, 3));
geo.setAttribute('color', new THREE.Float32BufferAttribute(extColors, 3)); geo.setAttribute('color', new THREE.Float32BufferAttribute(extCol, 3));
const mat = new THREE.LineBasicMaterial({vertexColors: true, linewidth: 1}); group.add(new THREE.LineSegments(geo, new THREE.LineBasicMaterial({vertexColors:true})));
group.add(new THREE.LineSegments(geo, mat));
} }
if (travelPositions.length) { if (travPos.length) {
const geo = new THREE.BufferGeometry(); const geo = new THREE.BufferGeometry();
geo.setAttribute('position', new THREE.Float32BufferAttribute(travelPositions, 3)); geo.setAttribute('position', new THREE.Float32BufferAttribute(travPos, 3));
const mat = new THREE.LineBasicMaterial({color: 0x334155, linewidth: 1, transparent: true, opacity: 0.3}); group.add(new THREE.LineSegments(geo, new THREE.LineBasicMaterial({color:0x334155,transparent:true,opacity:0.25})));
group.add(new THREE.LineSegments(geo, mat));
} }
scene.add(group); scene.add(group);
fitCamera(group); fitCamera(group);
document.getElementById('viewer-loading').style.display = 'none'; document.getElementById('viewer-loading').style.display = 'none';
document.getElementById('viewer-loading-text').textContent = `${total.toLocaleString()} segments`;
const total = segments.length;
const extCount = segments.filter(s=>s[6]).length;
document.getElementById('viewer-loading-text').textContent = `${total.toLocaleString()} segments (${extCount.toLocaleString()} extrusion)`;
}) })
.catch(err => { document.getElementById('viewer-loading-text').textContent = 'Erreur: '+err.message; }); .catch(err => { document.getElementById('viewer-loading-text').textContent = 'Erreur: '+err.message; });