Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 63 additions & 38 deletions app.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

101 changes: 63 additions & 38 deletions index.template.html
Original file line number Diff line number Diff line change
Expand Up @@ -26867,38 +26867,71 @@ <h2>Esporta PDF in scala</h2>
for(var r=0;r<rows;r++) a.push(base+(r<extra?1:0));
return a;
});
var P=2*Math.asin(Math.min(0.5, 56/STR_R0)); /* passo angolare ~ corda 112 cm */
var P0=2*Math.asin(Math.min(0.5, 56/STR_R0)); /* passo angolare ~ corda 112 cm */
var m0=split.reduce(function(a,s){ return a+s[0]; },0); /* posti in prima fila */
var secCount=[0,0,0,0]; /* numerazione progressiva per sezione: ogni leggio ha un nome */
for(var r=0;r<rows;r++){
var rowSeats=[];
secs.forEach(function(s,i){
var k=split[i][r];
for(var j=0;j<k;j++){ secCount[i]++; rowSeats.push({type:s[2], lbl:s[3]+" "+secCount[i], vsec:(i===0?1:i===1?2:0)}); }
});
var m=rowSeats.length; if(!m) continue;
var R=STR_R0+STR_DR*r;
for(var idx=0; idx<m; idx++){
var a=(idx-(m-1)/2)*P, x=R*Math.sin(a), y=-R*Math.cos(a);
var o={type:rowSeats[idx].type, x:Math.round(x), y:Math.round(y),
rot:Math.round(a*180/Math.PI), label:rowSeats[idx].lbl};
if(rowSeats[idx].vsec) o.vsec=rowSeats[idx].vsec;
out.push(o);
var nb=counts.cb||0, outerRows=nb ? rows+1 : rows;
function build(P, DR){
var res=[], secCount=[0,0,0,0]; /* numerazione progressiva per sezione: ogni leggio ha un nome */
for(var r=0;r<rows;r++){
var rowSeats=[];
secs.forEach(function(s,i){
var k=split[i][r];
for(var j=0;j<k;j++){ secCount[i]++; rowSeats.push({type:s[2], lbl:s[3]+" "+secCount[i], vsec:(i===0?1:i===1?2:0)}); }
});
var m=rowSeats.length; if(!m) continue;
var R=STR_R0+DR*r;
for(var idx=0; idx<m; idx++){
var a=(idx-(m-1)/2)*P, x=R*Math.sin(a), y=-R*Math.cos(a);
var o={type:rowSeats[idx].type, x:Math.round(x), y:Math.round(y),
rot:Math.round(a*180/Math.PI), label:rowSeats[idx].lbl};
if(rowSeats[idx].vsec) o.vsec=rowSeats[idx].vsec;
res.push(o);
}
}
}
/* Contrabbassi: arco corto dietro/destra dei violoncelli */
var nb=counts.cb||0, outerRows=rows;
if(nb){
var R=STR_R0+STR_DR*rows, a0=(m0-1)/2*P*0.55;
for(var j=0;j<nb;j++){
var a=a0+j*P, x=R*Math.sin(a), y=-R*Math.cos(a);
out.push({type:"contrabbasso", x:Math.round(x), y:Math.round(y),
rot:Math.round(a*180/Math.PI), label:"Contrabbasso "+(j+1)});
/* Contrabbassi: arco corto dietro/destra dei violoncelli */
if(nb){
var Rb=STR_R0+DR*rows, a0=(m0-1)/2*P*0.55;
for(var jb=0;jb<nb;jb++){
var ab=a0+jb*P, xb=Rb*Math.sin(ab), yb=-Rb*Math.cos(ab);
res.push({type:"contrabbasso", x:Math.round(xb), y:Math.round(yb),
rot:Math.round(ab*180/Math.PI), label:"Contrabbasso "+(jb+1)});
}
}
outerRows=rows+1;
return res;
}
/* il passo di partenza metteva i leggii ruotati (viola 100×115, violoncello 110×160) con gli angoli uno nell'altro e le file
a 150 cm con il violoncello profondo 160: si allarga passo e distanza fra le file, a piccoli scatti, solo finché le
postazioni NON si toccano davvero (rettangoli ruotati) */
var seats=build(P0, STR_DR), DRok=STR_DR;
if(strHaClash(seats)){
var found=false;
for(var dr=STR_DR; dr<=STR_DR+40 && !found; dr+=5){
for(var gp=0; gp<=32 && !found; gp++){
var cand=build(P0+gp*0.25*Math.PI/180, dr);
if(!strHaClash(cand)){ seats=cand; DRok=dr; found=true; }
}
} /* nessuna combinazione libera: resta il disegno di partenza e l'avviso di sovrapposizione è giusto */
}
return STR_R0+STR_DR*outerRows;
seats.forEach(function(o){ out.push(o); });
return STR_R0+DRok*outerRows;
}
/* quante coppie di postazioni (x, y, rot in gradi, dimensioni del tipo) si sovrappongono davvero? SAT su rettangoli ruotati; shrink = cm tolti per lato (tolleranza) */
function strContaClash(seats, shrink, stopAt1){
var R=seats.map(function(o){
var z=odim(o), c=Math.cos((o.rot||0)*Math.PI/180), s=Math.sin((o.rot||0)*Math.PI/180);
return {x:o.x, y:o.y, hw:z.w/2-shrink, hd:z.d/2-shrink, ux:c, uy:s, vx:-s, vy:c};
});
function proj(r, ax, ay){ return Math.abs(r.ux*ax+r.uy*ay)*r.hw+Math.abs(r.vx*ax+r.vy*ay)*r.hd; }
var n=0;
for(var i=0;i<R.length;i++) for(var j=i+1;j<R.length;j++){
var a=R[i], b=R[j], dx=b.x-a.x, dy=b.y-a.y, sep=false;
[[a.ux,a.uy],[a.vx,a.vy],[b.ux,b.uy],[b.vx,b.vy]].forEach(function(ax){
if(!sep && Math.abs(dx*ax[0]+dy*ax[1])>=proj(a,ax[0],ax[1])+proj(b,ax[0],ax[1])) sep=true; });
if(!sep){ n++; if(stopAt1) return n; }
}
return n;
}
function strHaClash(seats){ return strContaClash(seats, 0, true)>0; }

function buildOrchestraOut(counts){
var out=[];
Expand Down Expand Up @@ -27036,17 +27069,9 @@ <h2>Esporta PDF in scala</h2>
var m=measureOrchestraOut(out), C={x:stageW/2, y:stageD-fp};
var tooSmall = (C.y+m.minY<0 || C.y+m.maxY>stageD || C.x-m.maxX<0 || C.x+m.maxX>stageW);
/* gli elementi di sfondo (pedane, tappeti) possono stare sotto altri: non contano come sovrapposizione */
var boxes = out.map(function(o){
var z=odim(o);
return {x:C.x+o.x, y:C.y+o.y, r:Math.max(24, Math.max(z.w,z.d)*0.48), bg:(TYPES[o.type].z||2)<=1};
});
var overlaps=0;
for(var bi=0; bi<boxes.length; bi++){
for(var bj=bi+1; bj<boxes.length; bj++){
if(boxes[bi].bg || boxes[bj].bg) continue;
if(Math.hypot(boxes[bi].x-boxes[bj].x, boxes[bi].y-boxes[bj].y)<boxes[bi].r+boxes[bj].r) overlaps++;
}
}
var fg=out.filter(function(o){ return (TYPES[o.type].z||2)>1; });
/* sovrapposizione VERA: rettangoli ruotati (prima cerchi di raggio 0,48 × il lato maggiore: un violoncello 110×160 «toccava» il vicino senza toccarlo) */
var overlaps=strContaClash(fg, 1);
return {tooSmall:tooSmall, overlaps:overlaps, relevantOverlaps:overlaps, measure:m};
}

Expand Down
22 changes: 22 additions & 0 deletions test/engines.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -32378,6 +32378,28 @@ t("Import organico: si trova dalla ricerca («importa organico»), il menu File
ok(!/fetch\(|supabase|localStorage|sessionStorage/.test(src), "niente rete, niente archivio: il testo incollato resta sulla pagina");
});

t("Import organico (fix 13/11): le formule di archi non creano sovrapposizioni vere né l'avviso; il conteggio usa i rettangoli, non i cerchi", () => {
const salvaDoc = A.document, el = { textContent: "", hidden: true };
A.document = { body: { classList: { contains: () => false } }, getElementById: (id) => (id === "orchWarnText" || id === "orchWarnModal" || id === "orgImpModal" ? el : null) };
const warn = () => el.textContent;
for (const f of ["1-1-1-1", "2-2-1-1", "4-3-2-2-1", "8-6-4-4-2", "12-12-6-6-4", "7-6-4-4-2", "fl2-o2-cl2-fg2"]) {
const out = A.buildOrchestraOut(impC(f)), m = A.measureOrchestraOut(out);
eq(A.assessOut(out, 4000, 3000, A.frontPad(m)).overlaps, 0, f + ": nessuna sovrapposizione");
eq(A.strContaClash(out.filter((o) => o.type !== "pedana"), 0), 0, f + ": nemmeno a filo (geometria vera)");
}
A.loadDoc({ titolo: "Prova", stage: { w: 4000, d: 3000, blocks: [{ x: 0, y: 0, w: 4000, d: 3000 }] }, inputs: [], outputs: [], items: [] });
A.orgImportApplica(impC("2-2-1-1"), { clear: true, fit: true });
eq(!!A.pendingOut, false, "2-2-1-1: nessun avviso, i posti si creano subito");
eq(A.state.items.length > 0, true, "e i posti ci sono");
/* avviso vero, plurale giusto */
A.loadDoc({ titolo: "Prova", stage: { w: 4000, d: 3000, blocks: [{ x: 0, y: 0, w: 4000, d: 3000 }] }, inputs: [], outputs: [], items: [] });
A.placeOut([{ type: "flauto", x: 0, y: 0 }, { type: "flauto", x: 10, y: 0 }], false, false, false);
ok(/crea 1 sovrapposizione tra/.test(warn()), "una sola: «1 sovrapposizione» — " + warn());
A.placeOut([{ type: "flauto", x: 0, y: 0 }, { type: "flauto", x: 10, y: 0 }, { type: "flauto", x: 20, y: 0 }], false, false, false);
ok(/crea 3 sovrapposizioni tra/.test(warn()), "più d'una: «3 sovrapposizioni» — " + warn());
A.document = salvaDoc;
});

console.log("\n" + (fail === 0 ? "✓ TUTTI VERDI" : "✗ " + fail + " FALLITI") + " — " + pass + " passati, " + fail + " falliti.");
process.exit(fail === 0 ? 0 : 1);

Loading