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

Get handle position/rotation contraints from asset definition #24

Merged
merged 7 commits into from
Feb 21, 2019
25 changes: 18 additions & 7 deletions app/commands/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,15 @@ class Commands extends Observable { // eslint-disable-line no-unused-vars
}

addPart(parent, slot, part) {
var that = this;
this._pushAction(
function(redo, data) {
if (redo)
parent.addPart(slot, part);
else
else {
parent.removePart(part);
that.notify('AnyPartRemoved', null);
}
},
[]
);
Expand Down Expand Up @@ -73,43 +76,51 @@ class Commands extends Observable { // eslint-disable-line no-unused-vars
}

addRootPart(robot, part) {
var that = this;
this._pushAction(
function(redo, data) {
if (redo)
robot.addRootPart(part);
else
else {
robot.removePart();
that.notify('AnyPartRemoved', null);
}
},
[]
);
}

removePart(part) {
var that = this;
var parent = part.parent;
var slotName = parent.slotName(part);
this._pushAction(
function(redo, data) {
if (redo)
if (redo) {
parent.removePart(part);
else
that.notify('AnyPartRemoved', null);
} else
parent.addPart(slotName, part);
},
[]
);
}

removeRootPart(robot, part) {
var that = this;
this._pushAction(
function(redo, data) {
if (redo)
if (redo) {
robot.removePart();
else
that.notify('AnyPartRemoved', null);
} else
robot.addRootPart(part);
},
[]
);
this.notify('AnyPartRemoved', null);
}

changeColor(part, color) {
var previousColor = part.color;
this._pushAction(
Expand Down
1 change: 1 addition & 0 deletions app/model/part.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ class Part extends Observable { // eslint-disable-line no-unused-vars
if (this.slots[slotName] === part)
return slotName;
}
return null;
}

serialize() {
Expand Down
5 changes: 2 additions & 3 deletions app/robot_designer.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class RobotDesigner {
this.robotMediator = new RobotMediator(this.robot);
this.robotController = new RobotController(this.assetLibrary, this.commands, this.robot);

this.robotViewer = new RobotViewer(this.robotViewerElement, this.robotController);
this.robotViewer = new RobotViewer(this.robotViewerElement, this.robotController, this.commands);
this.robotViewer.scene.add(this.robotMediator.rootObject);
this.highlightOutlinePass = this.robotViewer.highlightOutlinePass;

Expand Down Expand Up @@ -162,8 +162,7 @@ function deleteSelectedPart() { // eslint-disable-line no-unused-vars
} while (parent);
}

designer.robotViewer.selector.clearSelection();
designer.robotViewer.handle.detach();
designer.robotViewer.clearSelection();
}

function mouseMove(ev) { // eslint-disable-line no-unused-vars
Expand Down
96 changes: 59 additions & 37 deletions app/view/handle.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* global THREE */
/* global THREE, Part, Robot */

class Handle { // eslint-disable-line no-unused-vars
constructor(robotController, domElement, camera, scene, orbitControls) {
Expand Down Expand Up @@ -78,6 +78,7 @@ class Handle { // eslint-disable-line no-unused-vars
this.target.parent.remove(this.target);
this.target = null;
}
this.part = null;
}

showHandle() {
Expand Down Expand Up @@ -107,47 +108,68 @@ class Handle { // eslint-disable-line no-unused-vars
}

_updateConstraints() {
// TODO: the following should be defined in assets.json (and so Webots :-/ )
if (!this.part)
return;
var asset = this.part.asset;
if (asset.root) {
this.control.rotationSnap = null;
this.control.translationSnap = null;
this.control.showX = true;
this.control.showY = true;
this.control.showZ = true;
} else if (asset.slotType === 'tinkerbots') {
this.control.rotationSnap = Math.PI / 2.0;
this.control.translationSnap = null;
if (this.mode === 'rotate') {
this.control.showX = false;
this.control.showY = false;
this.control.showZ = true;
} else {
this.control.showX = false;
this.control.showY = false;
this.control.showZ = false;
}
} else if (asset.slotType.startsWith('lego')) {
this.control.rotationSnap = Math.PI / 2.0;
this.control.translationSnap = 0.02;
if (this.mode === 'rotate') {
this.control.showX = false;
this.control.showY = false;
this.control.showZ = true;
} else {

var parentPart = this.part.parent;
console.assert(parentPart);

if (this.mode !== 'select') {
if (parentPart instanceof Robot) {
this.control.rotationSnap = null;
this.control.translationSnap = null;
this.control.showX = true;
this.control.showY = true;
this.control.showZ = false;
this.control.showZ = true;
return;
}

if (parentPart instanceof Part) {
var slotName = parentPart.slotName(this.part);
if (slotName) {
var slotData = parentPart.asset.slots[slotName];
this.control.rotationSnap = slotData.rotationSnap === -1 ? null : slotData.rotationSnap;
this.control.translationSnap = slotData.translationSnap === -1 ? null : slotData.translationSnap;
if (this.mode === 'rotate') {
if (this.control.rotationSnap === 0) {
this.control.showX = false;
this.control.showY = false;
this.control.showZ = false;
} else if (slotData.rotationGizmoVisibility === undefined) {
this.control.showX = true;
this.control.showY = true;
this.control.showZ = true;
} else {
this.control.showX = slotData.rotationGizmoVisibility[0];
this.control.showY = slotData.rotationGizmoVisibility[1];
this.control.showZ = slotData.rotationGizmoVisibility[2];
}
return;
} else if (this.mode === 'translate') {
if (this.control.translationSnap === 0) {
this.control.showX = false;
this.control.showY = false;
this.control.showZ = false;
} else if (slotData.translationHandleVisibility === undefined) {
this.control.showX = true;
this.control.showY = true;
this.control.showZ = true;
} else {
this.control.showX = slotData.translationHandleVisibility[0];
this.control.showY = slotData.translationHandleVisibility[1];
this.control.showZ = slotData.translationHandleVisibility[2];
}
return;
}
}
}
} else {
// normally unreachable.
this.control.rotationSnap = null;
this.control.translationSnap = null;
this.control.showX = false;
this.control.showY = false;
this.control.showZ = false;
}

// select mode or invalid structure.
this.control.rotationSnap = null;
this.control.translationSnap = null;
this.control.showX = false;
this.control.showY = false;
this.control.showZ = false;
}
}
10 changes: 9 additions & 1 deletion app/view/robot_viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
// 6. mouse interactions

class RobotViewer { // eslint-disable-line no-unused-vars
constructor(robotViewerElement, robotController) {
constructor(robotViewerElement, robotController, commands) {
this.robotViewerElement = robotViewerElement;
this.robotController = robotController;

Expand Down Expand Up @@ -64,6 +64,9 @@ class RobotViewer { // eslint-disable-line no-unused-vars
this.selector = new Selector(this.selectionOutlinePass);
this.handle = new Handle(this.robotController, this.robotViewerElement, this.camera, this.scene, this.controls);

// reset selection and handles when any part is removed
commands.addObserver('AnyPartRemoved', () => this.clearSelection());

this.gpuPicker = new THREE.GPUPicker({renderer: this.renderer, debug: false});
this.gpuPicker.setFilter(function(object) {
return object instanceof THREE.Mesh && 'x3dType' in object.userData;
Expand Down Expand Up @@ -171,4 +174,9 @@ class RobotViewer { // eslint-disable-line no-unused-vars
}
this.handle.showHandle();
}

clearSelection() {
this.selector.clearSelection();
this.handle.detach();
}
}
60 changes: 60 additions & 0 deletions assets/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
## Assets Definition

The assets defining the robot parts have to be listed one after the other in the `assets.json` file.
This section describes the supported properties.

Here is a simple example of valid `asset.json`:
```
{
"tinkerbots/base": {
"proto": "TinkerbotsBase",
"icon": "/robot-designer/assets/models/tinkerbots/base/icon.png",
"root": true,
"slots": {
"upSlot": {
"type": "tinkerbots",
"translation": "0 0 0.02",
"rotationSnap": 1.5708,
"rotationGizmoVisibility": [false, false, true],
"translationSnap": 0
}
}
}
}
```

### Part Properties

The part is identified by a name that should represent the path in which the part model stored in a X3D file named `model.x3d` is located.
These properties are mandatory and need to be specified for each part:

- `proto`: string that specifies the name of model used for the export. This should for example match the Webots PROTO name.

- `icon`: string that specifies the path to the model icon to be shown in the part browser.

- `slots`: list of slots specifying where other parts can be connected. The slot properties are defined in the next section.

Other optional properties:

* `root`: boolean that specifies if the part is the main robot core. Default value is *false*.

* `slotType`: string that specifies the slot type of the current part.

### Slot Properties

In order to have a user-friendly slot snap system, slots have different properties.
It is important that the slot identifier matched the Webots PROTO model slot name in order to have a fully working export mechanism.

- `type`: string that specifies the slot type of the current part. Only parts having a matching `slotType` can be connected.

- `translation`: 3D vector string that specifies the slot position: for example *"0 0 0.02"*.

- `rotation`: 3D vector string that specifies the slot orientation using Euler axis-angle format: for example *"0 0 1 1.5708"*.

- `translationSnap`: size of translation step used when moving the connected part using the translation handles: for example *0.01*. Default value is *-1* that corresponds to an infinitesimal step size. Setting `translationSnap` to *0* will disable moving the connected part.

- `translationHandleVisibility`: boolean array that specifies which of the *x*, *y*, or *z* translation axis are enabled: for example *[false, false, true]* will only enable translation on the *z*-axis. Default value is *[true, true, true]*.

- `rotationSnap`: size of rotation step used when moving the connected part using the rotation gizmo: for example *1.5708*. Default value is *-1* that corresponds to an infinitesimal step size. Setting `rotationSnap` to *0* will disable rotating the connected part.

- `rotationGizmoVisibility`: boolean array that specifies which of the *x*, *y*, or *z* rotation axis are enabled: for example *[false, false, true]* will only enable rotation on the *z*-axis. Default value is *[true, true, true]*.
Loading