Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Arm widget #131

Merged
merged 5 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions lib/src/components/arm/arm.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import '../../gen/common/v1/common.pb.dart';
import '../../gen/component/arm/v1/arm.pb.dart';
import '../../resource/base.dart';
import '../../robot/client.dart';

Expand All @@ -13,11 +12,11 @@ abstract class Arm extends Resource {
/// Move the end of the arm to the [Pose] specified.
Future<void> moveToPosition(Pose pose, {Map<String, dynamic>? extra});

/// Move each joint on the arm to the corresponding position specified in [JointPositions]
Future<void> moveToJointPositions(JointPositions positions, {Map<String, dynamic>? extra});
/// Move each joint on the arm to the corresponding position specified in [positions]
Future<void> moveToJointPositions(List<double> positions, {Map<String, dynamic>? extra});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, good catch here; definitely agree with preferring native types where possible.


/// Get the [JointPositions] representing the current position of the arm
Future<JointPositions> jointPositions({Map<String, dynamic>? extra});
/// Get the [List] representing the current position of the arm
Future<List<double>> jointPositions({Map<String, dynamic>? extra});

/// Stops all motion of the arm. It is assumed that the arm stops immediately.
Future<void> stop({Map<String, dynamic>? extra});
Expand Down
8 changes: 4 additions & 4 deletions lib/src/components/arm/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ class ArmClient extends Arm implements ResourceRPCClient {
}

@override
Future<void> moveToJointPositions(JointPositions positions, {Map<String, dynamic>? extra}) async {
Future<void> moveToJointPositions(List<double> positions, {Map<String, dynamic>? extra}) async {
final request = MoveToJointPositionsRequest()
..name = name
..positions = positions
..positions = (JointPositions()..values.addAll(positions))
..extra = extra?.toStruct() ?? Struct();
await client.moveToJointPositions(request);
}
Expand All @@ -57,12 +57,12 @@ class ArmClient extends Arm implements ResourceRPCClient {
}

@override
Future<JointPositions> jointPositions({Map<String, dynamic>? extra}) async {
Future<List<double>> jointPositions({Map<String, dynamic>? extra}) async {
final request = GetJointPositionsRequest()
..name = name
..extra = extra?.toStruct() ?? Struct();
final response = await client.getJointPositions(request);
return response.positions;
return response.positions.values;
}

@override
Expand Down
4 changes: 2 additions & 2 deletions lib/src/components/arm/service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,13 @@ class ArmService extends ArmServiceBase {
Future<GetJointPositionsResponse> getJointPositions(ServiceCall call, GetJointPositionsRequest request) async {
final arm = _armFromManager(request.name);
final jointPositions = await arm.jointPositions(extra: request.extra.toMap());
return GetJointPositionsResponse()..positions = jointPositions;
return GetJointPositionsResponse()..positions = (JointPositions()..values.addAll(jointPositions));
}

@override
Future<MoveToJointPositionsResponse> moveToJointPositions(ServiceCall call, MoveToJointPositionsRequest request) async {
final arm = _armFromManager(request.name);
await arm.moveToJointPositions(request.positions, extra: request.extra.toMap());
await arm.moveToJointPositions(request.positions.values, extra: request.extra.toMap());
return MoveToJointPositionsResponse();
}

Expand Down
12 changes: 11 additions & 1 deletion lib/widgets/button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ enum ViamButtonFillStyle {

/// The size class of the button.
enum ViamButtonSizeClass {
xxs,
xs,
small,
medium,
Expand All @@ -94,6 +95,8 @@ enum ViamButtonSizeClass {
/// The font size of the button, based on size class
double get fontSize {
switch (this) {
case xxs:
return 10;
benjirewis marked this conversation as resolved.
Show resolved Hide resolved
case xs:
return 10;
case small:
Expand All @@ -110,6 +113,8 @@ enum ViamButtonSizeClass {
/// The padding of the button, based on size class
EdgeInsets get padding {
switch (this) {
case xxs:
return const EdgeInsets.all(0);
case xs:
return const EdgeInsets.symmetric(vertical: 4, horizontal: 8);
case small:
Expand All @@ -125,7 +130,12 @@ enum ViamButtonSizeClass {

/// The style of the button, based on size class
ButtonStyle get style {
return ButtonStyle(textStyle: MaterialStatePropertyAll(TextStyle(fontSize: fontSize)), padding: MaterialStatePropertyAll(padding));
return ButtonStyle(
textStyle: MaterialStatePropertyAll(TextStyle(fontSize: fontSize)),
padding: MaterialStatePropertyAll(padding),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
minimumSize: const MaterialStatePropertyAll(Size.zero),
);
}
}

Expand Down
168 changes: 168 additions & 0 deletions lib/widgets/resources/arm.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:viam_sdk/viam_sdk.dart';
import 'package:viam_sdk/widgets.dart';

/// A widget to control an [Arm].
class ViamArmWidget extends StatefulWidget {
/// The [Arm]
final Arm arm;

const ViamArmWidget({
Key? key,
required this.arm,
}) : super(key: key);

@override
State<ViamArmWidget> createState() => _ViamArmWidgetState();
}

enum _PoseField { x, y, z, theta, oX, oY, oZ }

class _ViamArmWidgetState extends State<ViamArmWidget> {
Pose endPosition = Pose();
List<double> jointPositions = [];

Future<void> _getPositions() async {
final ep = await widget.arm.endPosition();
final jp = await widget.arm.jointPositions();
setState(() {
jointPositions = jp;
endPosition = ep;
});
}

@override
void initState() {
super.initState();
_getPositions();
}

Future<void> updateEndPosition(_PoseField field, double increment) async {
final ep = endPosition;
switch (field) {
case _PoseField.x:
ep.x += increment;
case _PoseField.y:
ep.y += increment;
case _PoseField.z:
ep.z += increment;
case _PoseField.theta:
ep.theta += increment;
case _PoseField.oX:
ep.oX += increment;
case _PoseField.oY:
ep.oY += increment;
case _PoseField.oZ:
ep.oZ += increment;
}
Comment on lines +68 to +85
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✨ love it, thank you!


await widget.arm.moveToPosition(ep);
await _getPositions();
}

Future<void> updateJointPosition(int joint, double increment) async {
final jp = jointPositions;
jp[joint] += increment;
await widget.arm.moveToJointPositions(jp);
await _getPositions();
}

TableRow _getEndPositionRow(_PoseField field) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Can we put this in it's own widget instead of a helper function? See here for a bit more of a deep dive on why. Basically it's more performant when the widget tree rebuilds.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@njooma and I spoke offline, I was mistaken and TableRow is not a Widget (<--- WHAT?!) so I was mistaken. this helper function in this case is fine :)

double value;
switch (field) {
case _PoseField.x:
value = endPosition.x;
case _PoseField.y:
value = endPosition.y;
case _PoseField.z:
value = endPosition.z;
case _PoseField.theta:
value = endPosition.theta;
case _PoseField.oX:
value = endPosition.oX;
case _PoseField.oY:
value = endPosition.oY;
case _PoseField.oZ:
value = endPosition.oZ;
}

return TableRow(children: [
TableCell(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 2),
child: Text('${field.name[0].toUpperCase()}${field.name.substring(1).toLowerCase()}', textAlign: TextAlign.end))),
TableCell(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 2),
child: ViamButton(onPressed: () => updateEndPosition(field, -10), text: '--', size: ViamButtonSizeClass.small))),
TableCell(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 2),
child: ViamButton(onPressed: () => updateEndPosition(field, -1), text: '-', size: ViamButtonSizeClass.small))),
TableCell(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 2), child: Text(value.toStringAsFixed(2), textAlign: TextAlign.center))),
TableCell(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 2),
child: ViamButton(onPressed: () => updateEndPosition(field, 1), text: '+', size: ViamButtonSizeClass.small))),
TableCell(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 2, vertical: 1),
child: ViamButton(onPressed: () => updateEndPosition(field, 10), text: '++', size: ViamButtonSizeClass.small))),
]);
}

@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Text('End Positions (mm)', style: TextStyle(fontWeight: FontWeight.bold)),
Table(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[minor] This Table is a BEEFY widget, can we break it out into its own widget?

columnWidths: const {0: IntrinsicColumnWidth()},
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
children: _PoseField.values.map((e) => _getEndPositionRow(e)).toList(),
),
const SizedBox(height: 16),
const Text('Joints (degrees)', style: TextStyle(fontWeight: FontWeight.bold)),
Table(
columnWidths: const {0: IntrinsicColumnWidth()},
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
children: jointPositions
.mapIndexed((index, element) => TableRow(children: [
clintpurser marked this conversation as resolved.
Show resolved Hide resolved
TableCell(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 2), child: Text('Joint $index', textAlign: TextAlign.end))),
TableCell(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 2),
child:
ViamButton(onPressed: () => updateJointPosition(index, -10), text: '--', size: ViamButtonSizeClass.small))),
TableCell(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 2),
child:
ViamButton(onPressed: () => updateJointPosition(index, -1), text: '-', size: ViamButtonSizeClass.small))),
TableCell(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 2),
child: Text(element.toStringAsFixed(2), textAlign: TextAlign.center))),
TableCell(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 2),
child: ViamButton(onPressed: () => updateJointPosition(index, 1), text: '+', size: ViamButtonSizeClass.small))),
TableCell(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 2, vertical: 1),
child:
ViamButton(onPressed: () => updateJointPosition(index, 10), text: '++', size: ViamButtonSizeClass.small))),
]))
.toList(),
)
],
);
}
}
2 changes: 1 addition & 1 deletion lib/widgets/resources/base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ class _ViamBaseWidgetState extends State<ViamBaseWidget> {

@override
void initState() {
camera = widget.cameras.firstOrNull;
super.initState();
camera = widget.cameras.firstOrNull;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[q] why move this after the super init state? Was it getting overwritten or something?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've just been doing it wrong the entire time. From the documentation:

Implementations of this method should start with a call to the inherited method, as in super.initState().

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, you might see changes to super.dispose() in other PRs (not in this one) for the inverse reason:

Implementations of this method should end with a call to the inherited method, as in super.dispose().

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool; makes intuitive sense too that you want to do the inherited construction/destruction before the custom class construction/destruction.

}

Widget _buildCamera() {
Expand Down
16 changes: 8 additions & 8 deletions test/unit_test/components/arm_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import '../../test_utils.dart';

class FakeArm extends Arm {
bool isStopped = true;
JointPositions armJointPositions = JointPositions()..values.addAll([0, 0, 0]);
List<double> armJointPositions = [0, 0, 0];
Pose armEndPosition = Pose()
..x = 0
..y = 0
Expand Down Expand Up @@ -46,13 +46,13 @@ class FakeArm extends Arm {
}

@override
Future<JointPositions> jointPositions({Map<String, dynamic>? extra}) async {
Future<List<double>> jointPositions({Map<String, dynamic>? extra}) async {
this.extra = extra;
return armJointPositions;
}

@override
Future<void> moveToJointPositions(JointPositions positions, {Map<String, dynamic>? extra}) async {
Future<void> moveToJointPositions(List<double> positions, {Map<String, dynamic>? extra}) async {
this.extra = extra;
armJointPositions = positions;
isStopped = false;
Expand Down Expand Up @@ -86,7 +86,7 @@ void main() {
});

test('moveToJointPositions', () async {
final expected = JointPositions()..values.addAll([1, 1, 1]);
final expected = [1.0, 1.0, 1.0];
await arm.moveToJointPositions(expected);
expect(arm.armJointPositions, expected);
});
Expand Down Expand Up @@ -168,15 +168,15 @@ void main() {
final client = ArmServiceClient(channel);
final request = GetJointPositionsRequest()..name = name;
final response = await client.getJointPositions(request);
expect(response.positions, arm.armJointPositions);
expect(response.positions.values, arm.armJointPositions);
});

test('moveToJointPositions', () async {
final client = ArmServiceClient(channel);
final expected = JointPositions()..values.addAll([1, 1, 1]);
final expected = [1.0, 1.0, 1.0];
final request = MoveToJointPositionsRequest()
..name = name
..positions = expected;
..positions = (JointPositions()..values.addAll(expected));
await client.moveToJointPositions(request);
expect(arm.armJointPositions, expected);
});
Expand Down Expand Up @@ -258,7 +258,7 @@ void main() {

test('moveToJointPositions', () async {
final client = ArmClient(name, channel);
final expected = JointPositions()..values.addAll([1, 1, 1]);
final expected = [1.0, 1.0, 1.0];
await client.moveToJointPositions(expected);
expect(arm.armJointPositions, expected);
});
Expand Down