Skip to content

Commit 8d51e41

Browse files
[APP-9878] Add joint angles (#439)
* add joint positions widget * arm_new generic screen * Change label from 'Joint Angles' to 'Joint Positions' * Implement joint position functionality (#441) * use theme for colors * update joint angle limits
1 parent 5424e7f commit 8d51e41

File tree

2 files changed

+197
-0
lines changed

2 files changed

+197
-0
lines changed

lib/widgets/resources/arm_new.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'package:flutter/material.dart';
22

33
import '../../viam_sdk.dart';
4+
import 'arm_widgets/joint_positions_widget.dart';
45
import 'arm_widgets/pose_widget.dart';
56

67
/// A widget to control an [Arm].
@@ -17,6 +18,7 @@ class ViamArmWidgetNew extends StatelessWidget {
1718
Widget build(BuildContext context) {
1819
return Column(
1920
children: [
21+
JointPositionsWidget(arm: arm),
2022
PoseWidget(arm: arm),
2123
],
2224
);
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter/services.dart';
3+
4+
import '../../../viam_sdk.dart' as viam;
5+
6+
class JointPositionsWidget extends StatefulWidget {
7+
final viam.Arm arm;
8+
const JointPositionsWidget({super.key, required this.arm});
9+
10+
@override
11+
State<JointPositionsWidget> createState() => _JointPositionsWidgetState();
12+
}
13+
14+
bool _isLive = false;
15+
16+
class _JointPositionsWidgetState extends State<JointPositionsWidget> {
17+
List<double> _jointValues = [];
18+
19+
@override
20+
void initState() {
21+
super.initState();
22+
_getJointPositions();
23+
}
24+
25+
Future<void> _getJointPositions() async {
26+
_jointValues = await widget.arm.jointPositions();
27+
setState(() {});
28+
}
29+
30+
Future<void> _setJointPositions() async {
31+
await widget.arm.moveToJointPositions(_jointValues);
32+
}
33+
34+
@override
35+
Widget build(BuildContext context) {
36+
return Column(
37+
children: [
38+
Divider(),
39+
Text(
40+
'Joint Positions',
41+
style: TextStyle(
42+
fontWeight: FontWeight.bold,
43+
),
44+
),
45+
Divider(),
46+
Padding(
47+
padding: const EdgeInsets.all(20.0),
48+
child: Column(
49+
mainAxisSize: MainAxisSize.min,
50+
children: _jointValues.isEmpty
51+
? [CircularProgressIndicator.adaptive()]
52+
: List.generate(_jointValues.length, (index) {
53+
return _BuildJointControlRow(index: index, arm: widget.arm, startJointValues: _jointValues);
54+
}),
55+
),
56+
),
57+
Padding(
58+
padding: const EdgeInsets.fromLTRB(20.0, 0, 20.0, 20.0),
59+
child: Row(
60+
spacing: 8,
61+
children: [
62+
Switch(
63+
value: _isLive,
64+
onChanged: (newValue) {
65+
setState(() {
66+
_isLive = newValue;
67+
});
68+
},
69+
),
70+
Text(
71+
'Live',
72+
),
73+
Spacer(),
74+
OutlinedButton.icon(
75+
onPressed: _isLive ? null : _setJointPositions,
76+
label: Text('Execute'),
77+
icon: Icon(Icons.play_arrow),
78+
),
79+
],
80+
),
81+
),
82+
],
83+
);
84+
}
85+
}
86+
87+
class _BuildJointControlRow extends StatefulWidget {
88+
final int index;
89+
final viam.Arm arm;
90+
final List<double> startJointValues;
91+
const _BuildJointControlRow({required this.index, required this.arm, required this.startJointValues});
92+
93+
@override
94+
State<_BuildJointControlRow> createState() => _BuildJointControlRowState();
95+
}
96+
97+
class _BuildJointControlRowState extends State<_BuildJointControlRow> {
98+
static const double _minPosition = -359.0;
99+
static const double _maxPosition = 359.0;
100+
101+
List<double> _jointValues = [];
102+
List<TextEditingController> _textControllers = [];
103+
104+
@override
105+
void initState() {
106+
_jointValues = widget.startJointValues;
107+
_textControllers = List.generate(
108+
_jointValues.length,
109+
(index) => TextEditingController(text: _jointValues[index].toStringAsFixed(1)),
110+
);
111+
super.initState();
112+
}
113+
114+
@override
115+
void dispose() {
116+
for (final controller in _textControllers) {
117+
controller.dispose();
118+
}
119+
super.dispose();
120+
}
121+
122+
void _updateJointValue(int index, double value) {
123+
final clampedValue = value.clamp(_minPosition, _maxPosition);
124+
125+
setState(() {
126+
_jointValues[index] = clampedValue;
127+
final formattedValue = clampedValue.toStringAsFixed(1);
128+
if (_textControllers[index].text != formattedValue) {
129+
_textControllers[index].text = formattedValue;
130+
_textControllers[index].selection = TextSelection.fromPosition(
131+
TextPosition(offset: _textControllers[index].text.length),
132+
);
133+
}
134+
});
135+
136+
if (_isLive) {
137+
widget.arm.moveToJointPositions(_jointValues);
138+
}
139+
}
140+
141+
@override
142+
Widget build(BuildContext context) {
143+
return Padding(
144+
padding: const EdgeInsets.symmetric(vertical: 8.0),
145+
child: Row(
146+
children: [
147+
SizedBox(
148+
width: 30,
149+
child: Text(
150+
'J${widget.index + 1}',
151+
style: Theme.of(context).textTheme.titleMedium,
152+
),
153+
),
154+
Expanded(
155+
child: Slider(
156+
value: _jointValues[widget.index],
157+
min: _minPosition,
158+
max: _maxPosition,
159+
divisions: (_maxPosition - _minPosition).toInt(),
160+
onChanged: (newValue) => _updateJointValue(widget.index, newValue),
161+
),
162+
),
163+
SizedBox(
164+
width: 70,
165+
child: TextField(
166+
controller: _textControllers[widget.index],
167+
textAlign: TextAlign.center,
168+
keyboardType: const TextInputType.numberWithOptions(decimal: true),
169+
inputFormatters: [
170+
FilteringTextInputFormatter.allow(RegExp(r'^\d+\.?\d{0,1}')),
171+
],
172+
onSubmitted: (newValue) {
173+
final parsedValue = double.tryParse(newValue) ?? _jointValues[widget.index];
174+
_updateJointValue(widget.index, parsedValue);
175+
},
176+
),
177+
),
178+
const SizedBox(width: 8),
179+
IconButton(
180+
icon: const Icon(Icons.remove),
181+
onPressed: () {
182+
_updateJointValue(widget.index, _jointValues[widget.index] - 1.0);
183+
},
184+
),
185+
IconButton(
186+
icon: const Icon(Icons.add),
187+
onPressed: () {
188+
_updateJointValue(widget.index, _jointValues[widget.index] + 1.0);
189+
},
190+
),
191+
],
192+
),
193+
);
194+
}
195+
}

0 commit comments

Comments
 (0)