Sequence editor. Layout fixes
This commit is contained in:
@@ -13,8 +13,8 @@
|
||||
*/
|
||||
|
||||
import BLE from '../ble.js';
|
||||
import IK from '../kinematics.js';
|
||||
import UI from '../ui.js';
|
||||
import IK from '../kinematics.js';
|
||||
import UI from '../ui.js';
|
||||
|
||||
// ── SVG viewport ──────────────────────────────────────────────────
|
||||
// We map the real workspace onto the SVG canvas.
|
||||
@@ -96,37 +96,37 @@ function renderArm(theta1, theta2, isGhost = false) {
|
||||
// Motor base SVG positions — M1 at +d2, M2 at -d2 (PositionControl.cpp convention)
|
||||
const motor1sx = SV.wx(+IK.ARM.d2); const motor1sy = SV.wy(0);
|
||||
const motor2sx = SV.wx(-IK.ARM.d2); const motor2sy = SV.wy(0);
|
||||
const elbow1sx = SV.wx(e1.x); const elbow1sy = SV.wy(e1.y);
|
||||
const elbow2sx = SV.wx(e2.x); const elbow2sy = SV.wy(e2.y);
|
||||
const endsx = SV.wx(endX); const endsy = SV.wy(endY);
|
||||
const elbow1sx = SV.wx(e1.x); const elbow1sy = SV.wy(e1.y);
|
||||
const elbow2sx = SV.wx(e2.x); const elbow2sy = SV.wy(e2.y);
|
||||
const endsx = SV.wx(endX); const endsy = SV.wy(endY);
|
||||
|
||||
// Guard: forward() can return NaN for degenerate angles – skip render
|
||||
if (!isFinite(endX) || !isFinite(endY)) return { endsx: SV.OX, endsy: SV.OY };
|
||||
|
||||
if (isGhost) {
|
||||
setLine(ghost1Prox, motor1sx, motor1sy, elbow1sx, elbow1sy);
|
||||
setLine(ghost1Dist, elbow1sx, elbow1sy, endsx, endsy);
|
||||
setLine(ghost1Dist, elbow1sx, elbow1sy, endsx, endsy);
|
||||
setLine(ghost2Prox, motor2sx, motor2sy, elbow2sx, elbow2sy);
|
||||
setLine(ghost2Dist, elbow2sx, elbow2sy, endsx, endsy);
|
||||
setLine(ghost2Dist, elbow2sx, elbow2sy, endsx, endsy);
|
||||
setCircle(ghostElbow1, elbow1sx, elbow1sy);
|
||||
setCircle(ghostElbow2, elbow2sx, elbow2sy);
|
||||
setCircle(ghostEnd, endsx, endsy);
|
||||
setCircle(ghostEnd, endsx, endsy);
|
||||
ghostLabel.setAttribute('x', endsx + 10);
|
||||
ghostLabel.setAttribute('y', endsy);
|
||||
} else {
|
||||
setLine(arm1Prox, motor1sx, motor1sy, elbow1sx, elbow1sy);
|
||||
setLine(arm1Dist, elbow1sx, elbow1sy, endsx, endsy);
|
||||
setLine(arm1Dist, elbow1sx, elbow1sy, endsx, endsy);
|
||||
setLine(arm2Prox, motor2sx, motor2sy, elbow2sx, elbow2sy);
|
||||
setLine(arm2Dist, elbow2sx, elbow2sy, endsx, endsy);
|
||||
setLine(arm2Dist, elbow2sx, elbow2sy, endsx, endsy);
|
||||
setCircle(elbow1, elbow1sx, elbow1sy);
|
||||
setCircle(elbow2, elbow2sx, elbow2sy);
|
||||
setCircle(endEff, endsx, endsy);
|
||||
setCircle(endEff, endsx, endsy);
|
||||
|
||||
// Crosshairs
|
||||
xhH.setAttribute('x1', endsx - 13); xhH.setAttribute('y1', endsy);
|
||||
xhH.setAttribute('x2', endsx + 13); xhH.setAttribute('y2', endsy);
|
||||
xhV.setAttribute('x1', endsx); xhV.setAttribute('y1', endsy - 13);
|
||||
xhV.setAttribute('x2', endsx); xhV.setAttribute('y2', endsy + 13);
|
||||
xhV.setAttribute('x1', endsx); xhV.setAttribute('y1', endsy - 13);
|
||||
xhV.setAttribute('x2', endsx); xhV.setAttribute('y2', endsy + 13);
|
||||
|
||||
coordLabel.textContent = `(${endX.toFixed(1)}, ${endY.toFixed(1)}) mm`;
|
||||
updateReadouts(theta1, theta2, endX, endY);
|
||||
@@ -142,8 +142,8 @@ function renderArmFromSteps() {
|
||||
}
|
||||
|
||||
function updateReadouts(t1, t2, ex, ey) {
|
||||
if (elCurrX) elCurrX.textContent = isFinite(ex) ? ex.toFixed(1) : '–';
|
||||
if (elCurrY) elCurrY.textContent = isFinite(ey) ? ey.toFixed(1) : '–';
|
||||
if (elCurrX) elCurrX.textContent = isFinite(ex) ? ex.toFixed(1) : '–';
|
||||
if (elCurrY) elCurrY.textContent = isFinite(ey) ? ey.toFixed(1) : '–';
|
||||
if (elTheta1) elTheta1.textContent = IK.radToDeg(t1).toFixed(1) + '°';
|
||||
if (elTheta2) elTheta2.textContent = IK.radToDeg(t2).toFixed(1) + '°';
|
||||
if (elSteps1) elSteps1.textContent = steps1;
|
||||
@@ -154,14 +154,14 @@ function updateReadouts(t1, t2, ex, ey) {
|
||||
// ── Ghost arm show/hide ───────────────────────────────────────────
|
||||
function showGhost(theta1, theta2, labelText) {
|
||||
const ghosts = [ghost1Prox, ghost1Dist, ghost2Prox, ghost2Dist,
|
||||
ghostElbow1, ghostElbow2, ghostEnd, ghostLabel];
|
||||
ghostElbow1, ghostElbow2, ghostEnd, ghostLabel];
|
||||
ghosts.forEach(el => el.style.display = '');
|
||||
renderArm(theta1, theta2, true);
|
||||
ghostLabel.textContent = labelText;
|
||||
}
|
||||
function hideGhost() {
|
||||
[ghost1Prox, ghost1Dist, ghost2Prox, ghost2Dist,
|
||||
ghostElbow1, ghostElbow2, ghostEnd, ghostLabel]
|
||||
ghostElbow1, ghostElbow2, ghostEnd, ghostLabel]
|
||||
.forEach(el => el.style.display = 'none');
|
||||
}
|
||||
|
||||
@@ -220,7 +220,7 @@ async function moveToXY(targetX, targetY) {
|
||||
|
||||
async function jogMotor(motor, delta) {
|
||||
if (motor === 1) steps1 += delta;
|
||||
else steps2 += delta;
|
||||
else steps2 += delta;
|
||||
renderArmFromSteps();
|
||||
const cmd = `S${motor}${delta >= 0 ? '+' : ''}${delta}`;
|
||||
BLE.isConnected()
|
||||
@@ -270,7 +270,7 @@ function buildZones() {
|
||||
// Motor positions — M1 at +d2, M2 at -d2
|
||||
const m1x = SV.wx(+IK.ARM.d2);
|
||||
const m2x = SV.wx(-IK.ARM.d2);
|
||||
const my = SV.wy(0);
|
||||
const my = SV.wy(0);
|
||||
|
||||
// Scale ruler
|
||||
const r0x = SV.wx(0); const r0y = SV.wy(-20);
|
||||
@@ -288,7 +288,7 @@ function buildZones() {
|
||||
fill="rgba(255,82,82,0.12)"
|
||||
stroke="rgba(255,82,82,0.55)" stroke-width="1.5"
|
||||
rx="3"/>
|
||||
<text x="${SV.OX}" y="${ey + eh/2 + 4}" text-anchor="middle"
|
||||
<text x="${SV.OX}" y="${ey + eh / 2 + 4}" text-anchor="middle"
|
||||
font-size="8" fill="rgba(255,82,82,0.7)" font-family="monospace">
|
||||
NO-GO ZONE
|
||||
</text>
|
||||
@@ -310,7 +310,7 @@ function buildZones() {
|
||||
stroke="rgba(255,255,255,0.25)" stroke-width="1"/>
|
||||
<line x1="${r1x}" y1="${r0y - 4}" x2="${r1x}" y2="${r0y + 4}"
|
||||
stroke="rgba(255,255,255,0.25)" stroke-width="1"/>
|
||||
<text x="${(r0x + r1x)/2}" y="${r0y + 12}" text-anchor="middle"
|
||||
<text x="${(r0x + r1x) / 2}" y="${r0y + 12}" text-anchor="middle"
|
||||
font-size="8" fill="rgba(255,255,255,0.3)" font-family="monospace">50 mm</text>
|
||||
|
||||
<!-- Motor markers -->
|
||||
@@ -372,7 +372,9 @@ function buildHTML() {
|
||||
.quick-steps button { flex:1; min-width:30px; padding:4px 2px; font-size:0.68rem;
|
||||
border-radius:var(--radius-xs); }
|
||||
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.jog-compact { grid-template-columns: 1fr; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="section-page fade-in">
|
||||
@@ -586,8 +588,8 @@ function buildHTML() {
|
||||
<button class="btn btn-ghost btn-sm" style="flex:1" id="s1-cw">CW ▶</button>
|
||||
</div>
|
||||
<div class="quick-steps">
|
||||
${[10,50,100,512].map(n=>`<button class="btn btn-ghost" data-motor="1" data-steps="${n}">+${n}</button>`).join('')}
|
||||
${[10,50,100,512].map(n=>`<button class="btn btn-ghost" data-motor="1" data-steps="-${n}">-${n}</button>`).join('')}
|
||||
${[10, 50, 100, 512].map(n => `<button class="btn btn-ghost" data-motor="1" data-steps="${n}">+${n}</button>`).join('')}
|
||||
${[10, 50, 100, 512].map(n => `<button class="btn btn-ghost" data-motor="1" data-steps="-${n}">-${n}</button>`).join('')}
|
||||
</div>
|
||||
<button class="btn btn-danger btn-sm btn-full" id="s1-zero">Home M1</button>
|
||||
</div>
|
||||
@@ -602,8 +604,8 @@ function buildHTML() {
|
||||
<button class="btn btn-ghost btn-sm" style="flex:1" id="s2-cw">CW ▶</button>
|
||||
</div>
|
||||
<div class="quick-steps">
|
||||
${[10,50,100,512].map(n=>`<button class="btn btn-ghost" data-motor="2" data-steps="${n}">+${n}</button>`).join('')}
|
||||
${[10,50,100,512].map(n=>`<button class="btn btn-ghost" data-motor="2" data-steps="-${n}">-${n}</button>`).join('')}
|
||||
${[10, 50, 100, 512].map(n => `<button class="btn btn-ghost" data-motor="2" data-steps="${n}">+${n}</button>`).join('')}
|
||||
${[10, 50, 100, 512].map(n => `<button class="btn btn-ghost" data-motor="2" data-steps="-${n}">-${n}</button>`).join('')}
|
||||
</div>
|
||||
<button class="btn btn-danger btn-sm btn-full" id="s2-zero">Home M2</button>
|
||||
</div>
|
||||
@@ -635,30 +637,30 @@ const StepperTestSection = {
|
||||
container.innerHTML = buildHTML();
|
||||
|
||||
// ── Cache refs ──────────────────────────────────────────────
|
||||
svgEl = document.getElementById('scara-svg');
|
||||
arm1Prox = document.getElementById('arm1-prox');
|
||||
arm1Dist = document.getElementById('arm1-dist');
|
||||
arm2Prox = document.getElementById('arm2-prox');
|
||||
arm2Dist = document.getElementById('arm2-dist');
|
||||
elbow1 = document.getElementById('elbow1');
|
||||
elbow2 = document.getElementById('elbow2');
|
||||
endEff = document.getElementById('end-eff');
|
||||
coordLabel= document.getElementById('coord-label');
|
||||
xhH = document.getElementById('xh-h');
|
||||
xhV = document.getElementById('xh-v');
|
||||
svgEl = document.getElementById('scara-svg');
|
||||
arm1Prox = document.getElementById('arm1-prox');
|
||||
arm1Dist = document.getElementById('arm1-dist');
|
||||
arm2Prox = document.getElementById('arm2-prox');
|
||||
arm2Dist = document.getElementById('arm2-dist');
|
||||
elbow1 = document.getElementById('elbow1');
|
||||
elbow2 = document.getElementById('elbow2');
|
||||
endEff = document.getElementById('end-eff');
|
||||
coordLabel = document.getElementById('coord-label');
|
||||
xhH = document.getElementById('xh-h');
|
||||
xhV = document.getElementById('xh-v');
|
||||
|
||||
ghost1Prox = document.getElementById('ghost-1p');
|
||||
ghost1Dist = document.getElementById('ghost-1d');
|
||||
ghost2Prox = document.getElementById('ghost-2p');
|
||||
ghost2Dist = document.getElementById('ghost-2d');
|
||||
ghost1Prox = document.getElementById('ghost-1p');
|
||||
ghost1Dist = document.getElementById('ghost-1d');
|
||||
ghost2Prox = document.getElementById('ghost-2p');
|
||||
ghost2Dist = document.getElementById('ghost-2d');
|
||||
ghostElbow1 = document.getElementById('ghost-e1');
|
||||
ghostElbow2 = document.getElementById('ghost-e2');
|
||||
ghostEnd = document.getElementById('ghost-e1');
|
||||
ghostLabel = document.getElementById('ghost-label');
|
||||
targetMarker= document.getElementById('target-marker');
|
||||
ghostEnd = document.getElementById('ghost-e1');
|
||||
ghostLabel = document.getElementById('ghost-label');
|
||||
targetMarker = document.getElementById('target-marker');
|
||||
|
||||
elCurrX = document.getElementById('curr-x');
|
||||
elCurrY = document.getElementById('curr-y');
|
||||
elCurrX = document.getElementById('curr-x');
|
||||
elCurrY = document.getElementById('curr-y');
|
||||
elTheta1 = document.getElementById('curr-t1');
|
||||
elTheta2 = document.getElementById('curr-t2');
|
||||
elSteps1 = document.getElementById('curr-s1');
|
||||
@@ -669,7 +671,7 @@ const StepperTestSection = {
|
||||
function pointerToWorld(e) {
|
||||
const rect = svgEl.getBoundingClientRect();
|
||||
const sx = (e.clientX - rect.left) * (SV.W / rect.width);
|
||||
const sy = (e.clientY - rect.top) * (SV.H / rect.height);
|
||||
const sy = (e.clientY - rect.top) * (SV.H / rect.height);
|
||||
return { wx: SV.svgToWorldX(sx), wy: SV.svgToWorldY(sy), sx, sy };
|
||||
}
|
||||
|
||||
@@ -717,9 +719,9 @@ const StepperTestSection = {
|
||||
|
||||
// ── Jog CW / CCW ────────────────────────────────────────────
|
||||
const gs = (n) => parseInt(document.getElementById(`jog-steps-${n}`)?.value ?? '50', 10) || 50;
|
||||
document.getElementById('s1-cw') .addEventListener('click', () => jogMotor(1, +gs(1)));
|
||||
document.getElementById('s1-cw').addEventListener('click', () => jogMotor(1, +gs(1)));
|
||||
document.getElementById('s1-ccw').addEventListener('click', () => jogMotor(1, -gs(1)));
|
||||
document.getElementById('s2-cw') .addEventListener('click', () => jogMotor(2, +gs(2)));
|
||||
document.getElementById('s2-cw').addEventListener('click', () => jogMotor(2, +gs(2)));
|
||||
document.getElementById('s2-ccw').addEventListener('click', () => jogMotor(2, -gs(2)));
|
||||
document.getElementById('s1-zero').addEventListener('click', () => zeroMotor(1));
|
||||
document.getElementById('s2-zero').addEventListener('click', () => zeroMotor(2));
|
||||
@@ -738,7 +740,7 @@ const StepperTestSection = {
|
||||
clearTimeout(spdT);
|
||||
spdT = setTimeout(() => {
|
||||
const cmd = `SPD:${e.target.value}`;
|
||||
BLE.isConnected() ? BLE.write(cmd).catch(() => {}) : UI.log(`[sim] ${cmd}`, 'info');
|
||||
BLE.isConnected() ? BLE.write(cmd).catch(() => { }) : UI.log(`[sim] ${cmd}`, 'info');
|
||||
}, 400);
|
||||
});
|
||||
document.getElementById('accel-slider').addEventListener('input', (e) => {
|
||||
@@ -746,7 +748,7 @@ const StepperTestSection = {
|
||||
clearTimeout(accT);
|
||||
accT = setTimeout(() => {
|
||||
const cmd = `ACC:${e.target.value}`;
|
||||
BLE.isConnected() ? BLE.write(cmd).catch(() => {}) : UI.log(`[sim] ${cmd}`, 'info');
|
||||
BLE.isConnected() ? BLE.write(cmd).catch(() => { }) : UI.log(`[sim] ${cmd}`, 'info');
|
||||
}, 400);
|
||||
});
|
||||
|
||||
@@ -757,12 +759,12 @@ const StepperTestSection = {
|
||||
function updateBadge() {
|
||||
if (!simBadge) return;
|
||||
const live = BLE.isConnected();
|
||||
simBadge.textContent = live ? 'LIVE' : 'SIMULATION';
|
||||
simBadge.textContent = live ? 'LIVE' : 'SIMULATION';
|
||||
simBadge.style.background = live ? 'rgba(0,230,118,0.12)' : 'rgba(255,202,40,0.12)';
|
||||
simBadge.style.color = live ? 'var(--accent-green)' : 'var(--accent-amber)';
|
||||
simBadge.style.border = live ? '1px solid rgba(0,230,118,0.3)' : '1px solid rgba(255,202,40,0.25)';
|
||||
simBadge.style.color = live ? 'var(--accent-green)' : 'var(--accent-amber)';
|
||||
simBadge.style.border = live ? '1px solid rgba(0,230,118,0.3)' : '1px solid rgba(255,202,40,0.25)';
|
||||
}
|
||||
BLE.on('connected', updateBadge);
|
||||
BLE.on('connected', updateBadge);
|
||||
BLE.on('disconnected', updateBadge);
|
||||
updateBadge();
|
||||
|
||||
@@ -778,7 +780,7 @@ const StepperTestSection = {
|
||||
document.addEventListener('ble:status', onBLEStatus);
|
||||
|
||||
eventCleanup = [
|
||||
() => BLE.off('connected', updateBadge),
|
||||
() => BLE.off('connected', updateBadge),
|
||||
() => BLE.off('disconnected', updateBadge),
|
||||
() => document.removeEventListener('ble:status', onBLEStatus),
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user