LeRobot-Arena / src /lib /components /panel /SettingsPanel.svelte
blanchon's picture
squash: initial commit
3aea7c6
raw
history blame
11.8 kB
<script lang="ts">
import { environment } from '$lib/runes/env.svelte';
import type IUrdfJoint from '$lib/components/scene/robot/URDF/interfaces/IUrdfJoint';
interface Props {}
let {}: Props = $props();
// Helper function to get all robots
const robots = $derived(Object.entries(environment.robots));
// Helper function to convert degrees to radians
function degreesToRadians(degrees: number): number {
return degrees * (Math.PI / 180);
}
// Helper function to convert radians to degrees
function radiansToDegrees(radians: number): number {
return radians * (180 / Math.PI);
}
// Helper function to get current joint rotation value in degrees
function getJointRotationValue(joint: any): number {
const axis = joint.axis_xyz || [0, 0, 1];
const rotation = joint.rotation || [0, 0, 0];
// Find the primary axis and get the rotation value
for (let i = 0; i < 3; i++) {
if (Math.abs(axis[i]) > 0.001) {
return radiansToDegrees(rotation[i] / axis[i]);
}
}
return 0;
}
// Helper function to update joint rotation using axis
function updateJointRotation(robotId: string, jointIndex: number, degrees: number): void {
const robot = environment.robots[robotId];
if (!robot?.robot.joints[jointIndex]) return;
const joint = robot.robot.joints[jointIndex];
const radians = degreesToRadians(degrees);
const axis = joint.axis_xyz || [0, 0, 1];
// Calculate rotation based on axis
joint.rotation = [
radians * axis[0],
radians * axis[1],
radians * axis[2]
];
}
// Helper function to get joint limits
function getJointLimits(joint: IUrdfJoint): { min: number; max: number } {
if (joint.limit) {
return {
min: joint.limit.lower ? radiansToDegrees(joint.limit.lower) : -180,
max: joint.limit.upper ? radiansToDegrees(joint.limit.upper) : 180
};
}
return joint.type === 'continuous' ? { min: -360, max: 360 } : { min: -180, max: 180 };
}
</script>
<div class="space-y-6">
<h3 class="text-lg font-semibold text-slate-100 mb-4">Robot Joint Settings</h3>
{#if robots.length === 0}
<div class="text-slate-400 text-sm">
No robots in the environment. Create a robot first using the Control Panel.
</div>
{:else}
{#each robots as [robotId, robotState]}
<div class="space-y-4 p-4 bg-slate-700/50 rounded-lg border border-slate-600">
<h4 class="text-md font-medium text-slate-200 border-b border-slate-600 pb-2">
Robot: <span class="text-sky-400">{robotId.slice(0, 8)}...</span>
</h4>
{#if robotState.robot.joints.length === 0}
<div class="text-slate-400 text-sm">No joints found for this robot.</div>
{:else}
<div class="text-slate-400 text-sm">
Joints:
</div>
<div class="space-y-4">
{#each robotState.robot.joints as joint, jointIndex}
{#if joint.type === 'revolute'}
{@const currentValue = getJointRotationValue(joint)}
{@const limits = getJointLimits(joint)}
{@const axis = joint.axis_xyz || [0, 0, 1]}
<div class="space-y-3 p-3 bg-slate-800/50 rounded border border-slate-600">
<div class="flex justify-between items-start">
<div>
<h5 class="text-sm font-medium text-slate-300">
<span class="text-yellow-400">{joint.name || `Joint ${jointIndex}`}</span>
<span class="text-xs text-slate-400 ml-2">({joint.type})</span>
</h5>
<div class="text-xs text-slate-500 mt-1">
Axis: [{axis[0].toFixed(1)}, {axis[1].toFixed(1)}, {axis[2].toFixed(1)}]
</div>
</div>
<div class="text-right">
<div class="text-sky-400 font-mono text-sm">{currentValue.toFixed(1)}°</div>
<div class="text-xs text-slate-500">
{limits.min.toFixed(0)}° to {limits.max.toFixed(0)}°
</div>
</div>
</div>
<!-- Single Rotation Control -->
<div class="space-y-2">
<input
type="range"
min={limits.min}
max={limits.max}
step="1"
value={currentValue}
oninput={(e) => updateJointRotation(robotId, jointIndex, parseFloat(e.currentTarget.value))}
class="w-full h-2 bg-slate-600 rounded-lg appearance-none cursor-pointer slider"
/>
</div>
<!-- Reset Joint Button -->
<button
onclick={() => updateJointRotation(robotId, jointIndex, 0)}
class="text-xs px-3 py-1 bg-slate-600 hover:bg-slate-500 text-slate-200 rounded transition-colors"
>
Reset to 0°
</button>
</div>
{:else if joint.type === 'continuous'}
{@const currentValue = getJointRotationValue(joint)}
{@const limits = getJointLimits(joint)}
{@const axis = joint.axis_xyz || [0, 0, 1]}
<div class="space-y-3 p-3 bg-slate-800/50 rounded border border-slate-600">
<div class="flex justify-between items-start">
<div>
<h5 class="text-sm font-medium text-slate-300">
<span class="text-yellow-400">{joint.name || `Joint ${jointIndex}`}</span>
<span class="text-xs text-slate-400 ml-2">({joint.type})</span>
</h5>
<div class="text-xs text-slate-500 mt-1">
Axis: [{axis[0].toFixed(1)}, {axis[1].toFixed(1)}, {axis[2].toFixed(1)}]
</div>
</div>
<div class="text-right">
<div class="text-sky-400 font-mono text-sm">{currentValue.toFixed(1)}°</div>
<div class="text-xs text-slate-500">
{limits.min.toFixed(0)}° to {limits.max.toFixed(0)}°
</div>
</div>
</div>
</div>
{:else if joint.type === 'fixed'}
<div class="p-2 bg-slate-800/30 rounded border border-slate-700">
<div class="text-xs text-slate-500">
<span class="text-slate-400">{joint.name || `Joint ${jointIndex}`}</span>
<span class="ml-2">(fixed joint)</span>
</div>
</div>
{/if}
{/each}
</div>
{#if robotState.urdfConfig.compoundMovements}
<div class="text-slate-400 text-sm">
Compound Movements:
</div>
<div class="space-y-4">
{#each robotState.urdfConfig.compoundMovements as movement}
<div class="text-slate-400 text-sm">
{movement.name}
{#each movement.dependents as dependent}
<div class="text-slate-400 text-sm">
{dependent.joint}
</div>
{/each}
</div>
{/each}
</div>
{/if}
{/if}
<!-- Reset All Joints Button -->
<button
onclick={() => {
robotState.robot.joints.forEach((joint, index) => {
if (joint.type === 'revolute') {
updateJointRotation(robotId, index, 0);
} else if (joint.type === 'continuous') {
updateJointRotation(robotId, index, 0);
}
});
}}
class="w-full px-3 py-2 bg-sky-600 hover:bg-sky-500 text-white rounded transition-colors text-sm font-medium"
>
Reset All Joints to 0°
</button>
</div>
{/each}
{/if}
</div>
<style>
.slider::-webkit-slider-thumb {
appearance: none;
height: 16px;
width: 16px;
border-radius: 50%;
background: #38bdf8; /* sky-400 */
cursor: pointer;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
border: 2px solid #0f172a; /* slate-900 */
}
.slider::-moz-range-thumb {
height: 16px;
width: 16px;
border-radius: 50%;
background: #38bdf8; /* sky-400 */
cursor: pointer;
border: 2px solid #0f172a; /* slate-900 */
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
}
.slider::-webkit-slider-track {
background: #475569; /* slate-600 */
border-radius: 4px;
height: 8px;
}
.slider::-moz-range-track {
background: #475569; /* slate-600 */
border-radius: 4px;
height: 8px;
border: none;
}
.slider:hover::-webkit-slider-thumb {
background: #0ea5e9; /* sky-500 */
transform: scale(1.05);
}
.slider:hover::-moz-range-thumb {
background: #0ea5e9; /* sky-500 */
transform: scale(1.05);
}
</style>