Robot Arm Movement Simulator

Robot Arm Movement Simulator

Action Panel

Action Log

Add New Action

deg
deg
deg

Defined Actions

"; } ramsConfig.actions.forEach((action) => { const btn = document.createElement("button"); btn.type = "button"; btn.className = "rams-btn rams-btn-secondary"; btn.textContent = action.name; btn.setAttribute( "onclick", `ramsExecuteAction('${action.id}')` ); actionButtonsContainer.appendChild(btn); }); } /** * Renders the log messages. */ function renderLog() { if (!logOutput) return; logOutput.innerHTML = ramsState.log .map( (entry) => `
${entry}
` ) .join(""); } /** * Applies CSS transforms to the visual arm elements. */ function updateArmVisuals(base, arm1, arm2, gripperStatus) { if (!armBase || !armSeg1 || !armSeg2 || !gripper) return; armBase.style.transform = `translateX(-50%) rotate(${base}deg)`; armSeg1.style.transform = `rotate(${arm1}deg)`; armSeg2.style.transform = `rotate(${arm2}deg)`; if (gripperStatus === "closed") { gripper.classList.add("rams-closed"); } else { gripper.classList.remove("rams-closed"); } } /** * Renders the list of configured actions. */ function renderConfigList() { if (!configListContainer) return; configListContainer.innerHTML = ""; if (ramsConfig.actions.length === 0) { configListContainer.innerHTML = "

No actions configured.

"; return; } ramsConfig.actions.forEach((action) => { const item = document.createElement("div"); item.className = "rams-list-item"; item.innerHTML = ` ${action.name} Base: ${formatDeg( action.base )} Arm1: ${formatDeg( action.arm1 )} Arm2: ${formatDeg( action.arm2 )}
`; configListContainer.appendChild(item); }); } /** * Update navigation button states. */ function updateNavButtons() { if (!nextBtn || !prevBtn) return; if (currentTabId === "rams-tab-dashboard") { prevBtn.disabled = true; nextBtn.disabled = false; } else if (currentTabId === "rams-tab-config") { prevBtn.disabled = false; nextBtn.disabled = true; } } /** * Re-renders all UI components. */ function fullRefreshUI() { renderDashboardControls(); renderConfigList(); renderLog(); updateArmVisuals( ramsState.currentPosition.base, ramsState.currentPosition.arm1, ramsState.currentPosition.arm2, ramsState.currentPosition.gripper ); updateNavButtons(); } // === EVENT HANDLERS (Must be global for onclick) === window.ramsShowTab = (tabId, element) => { // Hide all content container .querySelectorAll(".rams-tab-content") .forEach((tab) => (tab.style.display = "none")); // Deactivate all links container .querySelectorAll(".rams-tab-link") .forEach((link) => link.classList.remove("rams-active")); // Show selected content const tabToShow = container.querySelector("#" + tabId); if (tabToShow) { tabToShow.style.display = "block"; } // Activate selected link if (element) { element.classList.add("rams-active"); } currentTabId = tabId; updateNavButtons(); }; window.ramsNavigateTabs = (isNext) => { const tabs = Array.from( container.querySelectorAll(".rams-tab-link") ); const currentIdx = tabs.findIndex((tab) => tab.classList.contains("rams-active") ); let newIdx = isNext ? currentIdx + 1 : currentIdx - 1; if (newIdx >= 0 && newIdx < tabs.length) { tabs[newIdx].click(); } }; // --- Config Tab Handlers --- window.ramsAddAction = () => { if ( !newNameInput || !newBaseInput || !newArm1Input || !newArm2Input || !newGripperSelect ) return; const name = newNameInput.value.trim(); const base = parseFloat(newBaseInput.value); const arm1 = parseFloat(newArm1Input.value); const arm2 = parseFloat(newArm2Input.value); const gripper = newGripperSelect.value; if (!name) { alert("Please enter an action name."); return; } if ( isNaN(base) || isNaN(arm1) || isNaN(arm2) ) { alert("Please enter valid numbers for all angles."); return; } const newAction = { id: "a" + Date.now(), name, base, arm1, arm2, gripper, }; ramsConfig.actions.push(newAction); newNameInput.value = ""; newBaseInput.value = "0"; newArm1Input.value = "45"; newArm2Input.value = "-30"; renderConfigList(); renderDashboardControls(); }; window.ramsRemoveAction = (id) => { if ( !confirm("Are you sure you want to remove this action?") ) { return; } ramsConfig.actions = ramsConfig.actions.filter( (a) => a.id !== id ); renderConfigList(); renderDashboardControls(); }; // --- Dashboard Tab Handlers --- window.ramsExecuteAction = (id) => { const action = ramsConfig.actions.find( (a) => a.id === id ); if (!action) { logAction(`Error: Action ID ${id} not found.`); return; } // Update state ramsState.currentPosition = { ...action }; // Update visuals updateArmVisuals( action.base, action.arm1, action.arm2, action.gripper ); // Log it logAction(`Executed: ${action.name}`); }; // --- PDF Download --- window.ramsDownloadPDF = () => { if (!jsPDF) { alert("PDF library is not loaded. Please try again."); return; } try { const doc = new jsPDF(); const pageMargin = 15; const pageWidth = doc.internal.pageSize.getWidth(); let y = 20; // 1. Title doc.setFontSize(20); doc.text( "Robot Arm Simulator Report", pageWidth / 2, y, { align: "center" } ); y += 20; // 2. Defined Actions Table doc.setFontSize(16); doc.text("Defined Action Library", pageMargin, y); y += 10; const tableHead = [ "Action Name", "Base Rotation", "Arm 1 Angle", "Arm 2 Angle", "Gripper", ]; const tableBody = ramsConfig.actions.map((a) => [ a.name, formatDeg(a.base), formatDeg(a.arm1), formatDeg(a.arm2), formatCap(a.gripper), ]); doc.autoTable({ startY: y, head: [tableHead], body: tableBody, theme: "striped", headStyles: { fillColor: [0, 95, 204] }, }); y = doc.autoTable.previous.finalY + 15; // 3. Action Log doc.setFontSize(16); doc.text("Execution Log", pageMargin, y); y += 10; doc.setFontSize(10); doc.setFont("courier"); if (ramsState.log.length === 0) { doc.text("No actions executed.", pageMargin, y); } else { ramsState.log.forEach((entry) => { if (y > 270) { doc.addPage(); y = pageMargin; } doc.text(entry, pageMargin, y); y += 5; }); } doc.save("Robot_Arm_Simulator_Report.pdf"); } catch (e) { console.error("Error generating PDF:", e); alert( "An error occurred while generating the PDF." ); } }; // === INITIALIZATION === fullRefreshUI(); logAction("Simulator initialized. Ready."); });