@tteja/node-red-dashboard-2-robot-arm 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +142 -0
- package/docs/ARCHITECTURE.md +51 -0
- package/examples/01-inject-demo.json +133 -0
- package/examples/02-controller-to-robot.json +72 -0
- package/examples/03-rosbridge-live.json +54 -0
- package/examples/04-controller-and-ros2.json +72 -0
- package/examples/05-dashboard2.json +143 -0
- package/lib/normalize.js +146 -0
- package/lib/robot-runtime.js +144 -0
- package/lib/rosbridge.js +210 -0
- package/models/ur5e/LICENSE +33 -0
- package/models/ur5e/README.md +15 -0
- package/models/ur5e/joints.json +12 -0
- package/models/ur5e/meshes/base.stl +0 -0
- package/models/ur5e/meshes/forearm.stl +0 -0
- package/models/ur5e/meshes/shoulder.stl +0 -0
- package/models/ur5e/meshes/upperarm.stl +0 -0
- package/models/ur5e/meshes/wrist1.stl +0 -0
- package/models/ur5e/meshes/wrist2.stl +0 -0
- package/models/ur5e/meshes/wrist3.stl +0 -0
- package/models/ur5e/ur5e.urdf +138 -0
- package/models/ur5e-gripper/LICENSE +33 -0
- package/models/ur5e-gripper/README-ur5e.md +15 -0
- package/models/ur5e-gripper/README.md +6 -0
- package/models/ur5e-gripper/joints.json +13 -0
- package/models/ur5e-gripper/meshes/base.stl +0 -0
- package/models/ur5e-gripper/meshes/forearm.stl +0 -0
- package/models/ur5e-gripper/meshes/shoulder.stl +0 -0
- package/models/ur5e-gripper/meshes/upperarm.stl +0 -0
- package/models/ur5e-gripper/meshes/wrist1.stl +0 -0
- package/models/ur5e-gripper/meshes/wrist2.stl +0 -0
- package/models/ur5e-gripper/meshes/wrist3.stl +0 -0
- package/models/ur5e-gripper/ur5e_gripper.urdf +182 -0
- package/nodes/controller.html +76 -0
- package/nodes/controller.js +237 -0
- package/nodes/robot.html +109 -0
- package/nodes/robot.js +347 -0
- package/nodes/ui-robot-arm.html +82 -0
- package/nodes/ui-robot-arm.js +25 -0
- package/nodes/ui-robot-controller.html +81 -0
- package/nodes/ui-robot-controller.js +25 -0
- package/package.json +97 -0
- package/resources/controller.html +64 -0
- package/resources/controller.js +114 -0
- package/resources/pendant.html +66 -0
- package/resources/pendant.js +129 -0
- package/resources/robot-arm-widgets.umd.js +2 -0
- package/resources/viewer.html +54 -0
- package/resources/viewer.js +195 -0
- package/ui/components/UIRobotArm.vue +63 -0
- package/ui/components/UIRobotController.vue +62 -0
- package/ui/index.js +2 -0
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
(function(){"use strict";try{if(typeof document<"u"){var e=document.createElement("style");e.appendChild(document.createTextNode(".ui-robot-arm-wrapper[data-v-78e5dc1c]{width:100%;height:100%;min-height:280px;display:flex;flex-direction:column;overflow:hidden;border-radius:6px;background:#0f1419}.ui-robot-arm-frame[data-v-78e5dc1c]{flex:1;width:100%;height:100%;min-height:280px;border:0;background:#0f1419}.ui-robot-arm-empty[data-v-78e5dc1c]{padding:16px;color:#8b9bb4;font-size:13px}.ui-robot-controller-wrapper[data-v-49006e59]{width:100%;height:100%;min-height:280px;display:flex;flex-direction:column;overflow:hidden;border-radius:6px;background:#0f1419}.ui-robot-controller-frame[data-v-49006e59]{flex:1;width:100%;height:100%;min-height:280px;border:0;background:#0f1419}.ui-robot-controller-empty[data-v-49006e59]{padding:16px;color:#8b9bb4;font-size:13px}")),document.head.appendChild(e)}}catch(o){console.error("vite-plugin-css-injected-by-js",o)}})();
|
|
2
|
+
(function(t,e){typeof exports=="object"&&typeof module<"u"?e(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],e):(t=typeof globalThis<"u"?globalThis:t||self,e(t["robot-arm-widgets"]={},t.Vue))})(this,(function(t,e){"use strict";const r=(c,o)=>{const i=c.__vccOpts||c;for(const[a,l]of o)i[a]=l;return i},n={name:"UIRobotArm",inject:["$dataTracker"],props:{id:{type:String,required:!0},props:{type:Object,default:()=>({})},state:{type:Object,default:()=>({enabled:!1,visible:!1})}},computed:{robotId(){return this.props&&this.props.robot||""},viewUrl(){return this.robotId?`/robot-arm/view/${encodeURIComponent(this.robotId)}`:""}},created(){this.$dataTracker(this.id)}},d={class:"ui-robot-arm-wrapper"},p=["src"],b={key:1,class:"ui-robot-arm-empty"};function f(c,o,i,a,l,s){return e.openBlock(),e.createElementBlock("div",d,[s.robotId?(e.openBlock(),e.createElementBlock("iframe",{key:0,class:"ui-robot-arm-frame",src:s.viewUrl,title:"Robot arm 3D view",allow:"fullscreen"},null,8,p)):(e.openBlock(),e.createElementBlock("div",b,[...o[0]||(o[0]=[e.createTextVNode(" Select a ",-1),e.createElementVNode("strong",null,"robot",-1),e.createTextVNode(" node in the widget properties. ",-1)])]))])}const m=r(n,[["render",f],["__scopeId","data-v-78e5dc1c"]]),u={name:"UIRobotController",inject:["$dataTracker"],props:{id:{type:String,required:!0},props:{type:Object,default:()=>({})},state:{type:Object,default:()=>({enabled:!1,visible:!1})}},computed:{robotId(){return this.props&&this.props.robot||""},pendantUrl(){return this.robotId?`/robot-arm/pendant/${encodeURIComponent(this.robotId)}`:""}},created(){this.$dataTracker(this.id)}},_={class:"ui-robot-controller-wrapper"},h=["src"],k={key:1,class:"ui-robot-controller-empty"};function I(c,o,i,a,l,s){return e.openBlock(),e.createElementBlock("div",_,[s.robotId?(e.openBlock(),e.createElementBlock("iframe",{key:0,class:"ui-robot-controller-frame",src:s.pendantUrl,title:"Robot teach pendant"},null,8,h)):(e.openBlock(),e.createElementBlock("div",k,[...o[0]||(o[0]=[e.createTextVNode(" Select a ",-1),e.createElementVNode("strong",null,"robot",-1),e.createTextVNode(" node in the widget properties. ",-1)])]))])}const w=r(u,[["render",I],["__scopeId","data-v-49006e59"]]);t.UIRobotArm=m,t.UIRobotController=w,Object.defineProperty(t,Symbol.toStringTag,{value:"Module"})}));(function(){var t=window["robot-arm-widgets"]||{};function e(r,n){var d=window[r]||(window[r]={});t[n]&&(d[n]=t[n])}e("ui-ur5e-arm","UIRobotArm"),e("ui-ur5e-controller","UIRobotController")})();
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>Robot Arm Viewer</title>
|
|
7
|
+
<style>
|
|
8
|
+
* { box-sizing: border-box; }
|
|
9
|
+
html, body { margin: 0; height: 100%; overflow: hidden; background: #0f1419; color: #e7ecf1; font-family: system-ui, sans-serif; }
|
|
10
|
+
#bar {
|
|
11
|
+
display: flex; align-items: center; gap: 12px; padding: 8px 14px;
|
|
12
|
+
background: #1a2332; border-bottom: 1px solid #2a3548; font-size: 13px;
|
|
13
|
+
}
|
|
14
|
+
#bar .badge {
|
|
15
|
+
padding: 2px 8px; border-radius: 999px; background: #243044; font-size: 11px;
|
|
16
|
+
}
|
|
17
|
+
#bar .badge.ok { background: #1e4d3a; }
|
|
18
|
+
#canvas-wrap { position: absolute; inset: 40px 0 0 0; overflow: hidden; }
|
|
19
|
+
canvas { display: block; width: 100% !important; height: 100% !important; max-width: 100%; max-height: 100%; }
|
|
20
|
+
#err { color: #f88; padding: 12px; display: none; }
|
|
21
|
+
</style>
|
|
22
|
+
</head>
|
|
23
|
+
<body>
|
|
24
|
+
<div id="bar">
|
|
25
|
+
<strong>robot-arm</strong>
|
|
26
|
+
<span class="badge" id="src">source: —</span>
|
|
27
|
+
<span class="badge" id="rb">ros: —</span>
|
|
28
|
+
<span class="badge" id="unit">unit: __UNIT__</span>
|
|
29
|
+
<span class="badge" id="preset">__PRESET__</span>
|
|
30
|
+
<span style="margin-left:auto;opacity:.7">node __NODE_ID__</span>
|
|
31
|
+
</div>
|
|
32
|
+
<div id="err"></div>
|
|
33
|
+
<div id="canvas-wrap"></div>
|
|
34
|
+
<script>
|
|
35
|
+
window.__ROBOT_ARM__ = {
|
|
36
|
+
nodeId: '__NODE_ID__',
|
|
37
|
+
preset: '__PRESET__',
|
|
38
|
+
urdf: '__URDF__',
|
|
39
|
+
unit: '__UNIT__'
|
|
40
|
+
};
|
|
41
|
+
</script>
|
|
42
|
+
<script type="importmap">
|
|
43
|
+
{
|
|
44
|
+
"imports": {
|
|
45
|
+
"three": "https://cdn.jsdelivr.net/npm/three@0.186.1/build/three.module.js",
|
|
46
|
+
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.186.1/examples/jsm/",
|
|
47
|
+
"three/examples/jsm/": "https://cdn.jsdelivr.net/npm/three@0.186.1/examples/jsm/",
|
|
48
|
+
"urdf-loader": "https://cdn.jsdelivr.net/npm/urdf-loader@0.13.1/src/URDFLoader.js"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
</script>
|
|
52
|
+
<script type="module" src="/robot-arm/static/viewer.js"></script>
|
|
53
|
+
</body>
|
|
54
|
+
</html>
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
|
3
|
+
import URDFLoader from 'urdf-loader';
|
|
4
|
+
|
|
5
|
+
const cfg = window.__ROBOT_ARM__ || {};
|
|
6
|
+
const nodeId = cfg.nodeId;
|
|
7
|
+
const preset = cfg.preset || 'ur5e';
|
|
8
|
+
const urdfFile = cfg.urdf || (preset === 'ur5e-gripper' ? 'ur5e_gripper.urdf' : 'ur5e.urdf');
|
|
9
|
+
const errEl = document.getElementById('err');
|
|
10
|
+
const srcEl = document.getElementById('src');
|
|
11
|
+
const rbEl = document.getElementById('rb');
|
|
12
|
+
|
|
13
|
+
function showErr(msg) {
|
|
14
|
+
errEl.style.display = 'block';
|
|
15
|
+
errEl.textContent = msg;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const wrap = document.getElementById('canvas-wrap');
|
|
19
|
+
const scene = new THREE.Scene();
|
|
20
|
+
scene.background = new THREE.Color(0x0f1419);
|
|
21
|
+
|
|
22
|
+
const camera = new THREE.PerspectiveCamera(45, 1, 0.01, 100);
|
|
23
|
+
const viewDir = new THREE.Vector3(0.9, 0.55, 0.9);
|
|
24
|
+
|
|
25
|
+
const renderer = new THREE.WebGLRenderer({ antialias: true });
|
|
26
|
+
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
|
|
27
|
+
wrap.appendChild(renderer.domElement);
|
|
28
|
+
|
|
29
|
+
const controls = new OrbitControls(camera, renderer.domElement);
|
|
30
|
+
controls.enablePan = false;
|
|
31
|
+
controls.target.set(0, 0, 0);
|
|
32
|
+
controls.update();
|
|
33
|
+
|
|
34
|
+
scene.add(new THREE.AmbientLight(0xffffff, 0.55));
|
|
35
|
+
const dir = new THREE.DirectionalLight(0xffffff, 0.85);
|
|
36
|
+
dir.position.set(2, 3, 1);
|
|
37
|
+
scene.add(dir);
|
|
38
|
+
const grid = new THREE.GridHelper(2, 20, 0x334466, 0x1e2a3a);
|
|
39
|
+
scene.add(grid);
|
|
40
|
+
|
|
41
|
+
let robot = null;
|
|
42
|
+
let poseFramed = false;
|
|
43
|
+
|
|
44
|
+
// Keep the base (robot origin) at the center of the panel and pull the
|
|
45
|
+
// camera back until the whole arm fits the current width and height.
|
|
46
|
+
function frameBase(keepAngle) {
|
|
47
|
+
if (!robot) return;
|
|
48
|
+
const w = wrap.clientWidth;
|
|
49
|
+
const h = wrap.clientHeight;
|
|
50
|
+
if (w < 2 || h < 2) return;
|
|
51
|
+
camera.aspect = w / h;
|
|
52
|
+
renderer.setSize(w, h, false);
|
|
53
|
+
renderer.domElement.style.width = '100%';
|
|
54
|
+
renderer.domElement.style.height = '100%';
|
|
55
|
+
robot.updateMatrixWorld(true);
|
|
56
|
+
const origin = new THREE.Vector3();
|
|
57
|
+
robot.getWorldPosition(origin);
|
|
58
|
+
const box = new THREE.Box3().setFromObject(robot);
|
|
59
|
+
if (box.isEmpty()) return;
|
|
60
|
+
const corner = new THREE.Vector3();
|
|
61
|
+
let radius = 0.25;
|
|
62
|
+
for (const x of [box.min.x, box.max.x]) {
|
|
63
|
+
for (const y of [box.min.y, box.max.y]) {
|
|
64
|
+
for (const z of [box.min.z, box.max.z]) {
|
|
65
|
+
corner.set(x, y, z);
|
|
66
|
+
radius = Math.max(radius, corner.distanceTo(origin));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const vFov = THREE.MathUtils.degToRad(camera.fov);
|
|
71
|
+
const hFov = 2 * Math.atan(Math.tan(vFov / 2) * camera.aspect);
|
|
72
|
+
const distance = Math.max(radius / Math.tan(vFov / 2), radius / Math.tan(hFov / 2)) * 1.35;
|
|
73
|
+
if (keepAngle) {
|
|
74
|
+
viewDir.copy(camera.position).sub(controls.target);
|
|
75
|
+
if (viewDir.lengthSq() < 1e-8) viewDir.set(0.9, 0.55, 0.9);
|
|
76
|
+
} else {
|
|
77
|
+
viewDir.set(0.9, 0.55, 0.9);
|
|
78
|
+
}
|
|
79
|
+
viewDir.normalize();
|
|
80
|
+
controls.target.copy(origin);
|
|
81
|
+
camera.position.copy(origin).addScaledVector(viewDir, distance);
|
|
82
|
+
camera.near = Math.max(distance / 200, 0.001);
|
|
83
|
+
camera.far = Math.max(distance * 20, 10);
|
|
84
|
+
camera.updateProjectionMatrix();
|
|
85
|
+
const floor = Math.max(radius * 2.4, 0.6);
|
|
86
|
+
grid.scale.set(floor / 2, 1, floor / 2);
|
|
87
|
+
controls.update();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function resize() {
|
|
91
|
+
frameBase(true);
|
|
92
|
+
}
|
|
93
|
+
window.addEventListener('resize', resize);
|
|
94
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
95
|
+
new ResizeObserver(() => frameBase(true)).observe(wrap);
|
|
96
|
+
}
|
|
97
|
+
resize();
|
|
98
|
+
|
|
99
|
+
function animate() {
|
|
100
|
+
requestAnimationFrame(animate);
|
|
101
|
+
controls.update();
|
|
102
|
+
renderer.render(scene, camera);
|
|
103
|
+
}
|
|
104
|
+
animate();
|
|
105
|
+
|
|
106
|
+
function applyJoints(joints) {
|
|
107
|
+
if (!robot || !joints) return;
|
|
108
|
+
for (const [name, value] of Object.entries(joints)) {
|
|
109
|
+
const j = robot.joints && robot.joints[name];
|
|
110
|
+
if (j) {
|
|
111
|
+
try {
|
|
112
|
+
j.setJointValue(value);
|
|
113
|
+
} catch (_) {
|
|
114
|
+
if (typeof j.setJointValue === 'function') j.setJointValue(value);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (joints.finger_joint != null && robot.joints && robot.joints.finger_joint_right) {
|
|
119
|
+
try {
|
|
120
|
+
robot.joints.finger_joint_right.setJointValue(joints.finger_joint);
|
|
121
|
+
} catch (_) { /* ignore */ }
|
|
122
|
+
}
|
|
123
|
+
if (!poseFramed) {
|
|
124
|
+
poseFramed = true;
|
|
125
|
+
frameBase(false);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function loadUrdf() {
|
|
130
|
+
const base = `/robot-arm/models/${encodeURIComponent(preset)}/`;
|
|
131
|
+
const url = `${base}${encodeURIComponent(urdfFile)}`;
|
|
132
|
+
const loader = new URDFLoader();
|
|
133
|
+
// Relative mesh paths in URDF are "meshes/foo.stl" → resolve under the preset dir.
|
|
134
|
+
loader.workingPath = base;
|
|
135
|
+
loader.packages = {
|
|
136
|
+
'': base,
|
|
137
|
+
ur5e: base,
|
|
138
|
+
'ur5e-gripper': base
|
|
139
|
+
};
|
|
140
|
+
return new Promise((resolve, reject) => {
|
|
141
|
+
loader.load(
|
|
142
|
+
url,
|
|
143
|
+
(result) => resolve(result),
|
|
144
|
+
undefined,
|
|
145
|
+
(e) => reject(e)
|
|
146
|
+
);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
robot = await loadUrdf();
|
|
152
|
+
// URDF Z-up → Three.js Y-up
|
|
153
|
+
robot.rotation.x = -Math.PI / 2;
|
|
154
|
+
robot.traverse((obj) => {
|
|
155
|
+
if (!obj.isMesh || !obj.material) return;
|
|
156
|
+
const mats = Array.isArray(obj.material) ? obj.material : [obj.material];
|
|
157
|
+
for (const mat of mats) {
|
|
158
|
+
mat.side = THREE.DoubleSide;
|
|
159
|
+
if ('metalness' in mat) {
|
|
160
|
+
mat.metalness = 0.35;
|
|
161
|
+
mat.roughness = 0.45;
|
|
162
|
+
}
|
|
163
|
+
mat.needsUpdate = true;
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
scene.add(robot);
|
|
167
|
+
frameBase(false);
|
|
168
|
+
} catch (e) {
|
|
169
|
+
showErr('URDF load failed: ' + (e && e.message ? e.message : String(e)));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function onState(data) {
|
|
173
|
+
if (!data) return;
|
|
174
|
+
srcEl.textContent = 'source: ' + (data.source || '—');
|
|
175
|
+
srcEl.classList.toggle('ok', !!data.source);
|
|
176
|
+
rbEl.textContent = 'ros: ' + (data.rosbridge || '—');
|
|
177
|
+
rbEl.classList.toggle('ok', data.rosbridge === 'connected');
|
|
178
|
+
applyJoints(data.joints);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const es = new EventSource(`/robot-arm/api/${encodeURIComponent(nodeId)}/state?stream=1`);
|
|
182
|
+
es.onmessage = (ev) => {
|
|
183
|
+
try {
|
|
184
|
+
onState(JSON.parse(ev.data));
|
|
185
|
+
} catch (_) { /* ignore */ }
|
|
186
|
+
};
|
|
187
|
+
es.onerror = () => {
|
|
188
|
+
es.close();
|
|
189
|
+
setInterval(async () => {
|
|
190
|
+
try {
|
|
191
|
+
const r = await fetch(`/robot-arm/api/${encodeURIComponent(nodeId)}/state`);
|
|
192
|
+
onState(await r.json());
|
|
193
|
+
} catch (_) { /* ignore */ }
|
|
194
|
+
}, 200);
|
|
195
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="ui-robot-arm-wrapper">
|
|
3
|
+
<iframe
|
|
4
|
+
v-if="robotId"
|
|
5
|
+
class="ui-robot-arm-frame"
|
|
6
|
+
:src="viewUrl"
|
|
7
|
+
title="Robot arm 3D view"
|
|
8
|
+
allow="fullscreen"
|
|
9
|
+
/>
|
|
10
|
+
<div v-else class="ui-robot-arm-empty">
|
|
11
|
+
Select a <strong>robot</strong> node in the widget properties.
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<script>
|
|
17
|
+
export default {
|
|
18
|
+
name: 'UIRobotArm',
|
|
19
|
+
inject: ['$dataTracker'],
|
|
20
|
+
props: {
|
|
21
|
+
id: { type: String, required: true },
|
|
22
|
+
props: { type: Object, default: () => ({}) },
|
|
23
|
+
state: { type: Object, default: () => ({ enabled: false, visible: false }) }
|
|
24
|
+
},
|
|
25
|
+
computed: {
|
|
26
|
+
robotId () {
|
|
27
|
+
return (this.props && this.props.robot) || ''
|
|
28
|
+
},
|
|
29
|
+
viewUrl () {
|
|
30
|
+
return this.robotId ? `/robot-arm/view/${encodeURIComponent(this.robotId)}` : ''
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
created () {
|
|
34
|
+
this.$dataTracker(this.id)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<style scoped>
|
|
40
|
+
.ui-robot-arm-wrapper {
|
|
41
|
+
width: 100%;
|
|
42
|
+
height: 100%;
|
|
43
|
+
min-height: 280px;
|
|
44
|
+
display: flex;
|
|
45
|
+
flex-direction: column;
|
|
46
|
+
overflow: hidden;
|
|
47
|
+
border-radius: 6px;
|
|
48
|
+
background: #0f1419;
|
|
49
|
+
}
|
|
50
|
+
.ui-robot-arm-frame {
|
|
51
|
+
flex: 1;
|
|
52
|
+
width: 100%;
|
|
53
|
+
height: 100%;
|
|
54
|
+
min-height: 280px;
|
|
55
|
+
border: 0;
|
|
56
|
+
background: #0f1419;
|
|
57
|
+
}
|
|
58
|
+
.ui-robot-arm-empty {
|
|
59
|
+
padding: 16px;
|
|
60
|
+
color: #8b9bb4;
|
|
61
|
+
font-size: 13px;
|
|
62
|
+
}
|
|
63
|
+
</style>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="ui-robot-controller-wrapper">
|
|
3
|
+
<iframe
|
|
4
|
+
v-if="robotId"
|
|
5
|
+
class="ui-robot-controller-frame"
|
|
6
|
+
:src="pendantUrl"
|
|
7
|
+
title="Robot teach pendant"
|
|
8
|
+
/>
|
|
9
|
+
<div v-else class="ui-robot-controller-empty">
|
|
10
|
+
Select a <strong>robot</strong> node in the widget properties.
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
</template>
|
|
14
|
+
|
|
15
|
+
<script>
|
|
16
|
+
export default {
|
|
17
|
+
name: 'UIRobotController',
|
|
18
|
+
inject: ['$dataTracker'],
|
|
19
|
+
props: {
|
|
20
|
+
id: { type: String, required: true },
|
|
21
|
+
props: { type: Object, default: () => ({}) },
|
|
22
|
+
state: { type: Object, default: () => ({ enabled: false, visible: false }) }
|
|
23
|
+
},
|
|
24
|
+
computed: {
|
|
25
|
+
robotId () {
|
|
26
|
+
return (this.props && this.props.robot) || ''
|
|
27
|
+
},
|
|
28
|
+
pendantUrl () {
|
|
29
|
+
return this.robotId ? `/robot-arm/pendant/${encodeURIComponent(this.robotId)}` : ''
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
created () {
|
|
33
|
+
this.$dataTracker(this.id)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<style scoped>
|
|
39
|
+
.ui-robot-controller-wrapper {
|
|
40
|
+
width: 100%;
|
|
41
|
+
height: 100%;
|
|
42
|
+
min-height: 280px;
|
|
43
|
+
display: flex;
|
|
44
|
+
flex-direction: column;
|
|
45
|
+
overflow: hidden;
|
|
46
|
+
border-radius: 6px;
|
|
47
|
+
background: #0f1419;
|
|
48
|
+
}
|
|
49
|
+
.ui-robot-controller-frame {
|
|
50
|
+
flex: 1;
|
|
51
|
+
width: 100%;
|
|
52
|
+
height: 100%;
|
|
53
|
+
min-height: 280px;
|
|
54
|
+
border: 0;
|
|
55
|
+
background: #0f1419;
|
|
56
|
+
}
|
|
57
|
+
.ui-robot-controller-empty {
|
|
58
|
+
padding: 16px;
|
|
59
|
+
color: #8b9bb4;
|
|
60
|
+
font-size: 13px;
|
|
61
|
+
}
|
|
62
|
+
</style>
|
package/ui/index.js
ADDED