Operator for Advanced Handle Placement

Geometry Handles in HOOPS Communicator are a very useful tool to interactively manipulate parts of a model. The default UI of HOOPS Communicator places a collection of handles either at the center of the bounding box of a selected entity or at the word space coordinate of the picked 3D point. In both cases the handles will always be oriented along the major axis in world space and not along the local axis of the selected part or subassembly.

This new operator allows the user to place a set of translation and rotation handles along either the axis defined by a highlighted straight line or the center line of an arc. If there is no line highlighted the handles will be oriented along the surface normal of the picked face. If a set of nodes is already selected when the handles are placed those nodes will become the handle targets and can be manipulated together by the newly placed handles.

This type of handle placement can be useful in particular when creating animations or defining work instructions.


To activate the operator simply add the code below:

const myOperator = new HandlePlacementOperator(hwv);
let myOperatorHandle = hwv.operatorManager.registerCustomOperator(myOperator);
hwv.operatorManager.push(myOperatorHandle);

This will add the new operator to the top of the operator stack. In the current implementation its functionality is activated by holding down the shift key while interacting with the model. By clicking on either a highlighted line or a face the handles will be activated. Holding down the alt key as well when placing the handles will insert the default handle group which can be useful in some cases.


This is the main code for the new operator It has no external dependencies except for the relevant HOOPS Web Viewer libraries. Simply save it into a separate file and include it with your other js files.

class HandlePlacementOperator extends Communicator.Operator.OperatorBase {

    constructor(viewer) {
        super();
        this._viewer = viewer;

        this._activeHighlight = null;
        this._currentSelItem = null;
    }

    onMouseDown(event) {

        if (!event.shiftDown())
            return;

        const handleOperator = this._viewer.operatorManager.getOperator(Communicator.OperatorId.Handle);
        handleOperator.removeHandles();

        let selectionItem = this._currentSelItem;
        let nodeId = selectionItem.getNodeId();
        let faceEntity = selectionItem.getFaceEntity();
        let lineEntity = selectionItem.getLineEntity();

        if (lineEntity !== null || faceEntity != null) {
            let r = this._viewer.selectionManager.getResults();
            let snodeIds;
            if (r.length > 0) {
                snodeIds = [];
                for (let i = 0; i < r.length; i++)
                    snodeIds.push(r[i].getNodeId());
            }
            else
                snodeIds = [nodeId];

            if (lineEntity != null) {
                let points = lineEntity.getPoints();
                if (points.length === 2) {
                    let axis = Communicator.Point3.subtract(points[1], points[0]);
                    let length_1 = axis.length();
                    let position = points[0].copy().add(axis.normalize().scale(length_1 / 2));

                    this._addAxisTranslationHandle(position, axis, snodeIds);
                    this._addAxisTranslationHandle(position, axis.copy().scale(-1), snodeIds);
                    this._addAxisRotationHandle(position, axis, snodeIds);
                }
                else {
                    this._getArcCenter(selectionItem).then((pt) => {

                        let mat = this._viewer.model.getNodeNetMatrix(this._viewer.model.getNodeParent(nodeId));
                        let p1 = mat.transform(new Communicator.Point3(0, 0, 0));
                        let p2 = mat.transform(pt.normal);

                        let axis = Communicator.Point3.subtract(p2, p1);

                        let position = pt.center;
                        this._addAxisTranslationHandle(position, axis, snodeIds);
                        this._addAxisTranslationHandle(position, axis.copy().scale(-1), snodeIds);
                        this._addAxisRotationHandle(position, axis, snodeIds);
                    });

                }
            }
            else {
                let axis = faceEntity.getNormal();
                let length_1 = axis.length();
                let position = faceEntity.getPosition().copy();

                if (event.altDown())
                    this._addMainHandle(snodeIds, position);
                else {
                    this._addAxisTranslationHandle(position, axis, snodeIds);
                    this._addAxisTranslationHandle(position, axis.copy().scale(-1), snodeIds);
                    this._addAxisRotationHandle(position, axis, snodeIds);
                }
            }

        }
        event.setHandled(true);
    }

    onMouseMove(event) {
        if (event.shiftDown()) {
            let position = event.getPosition();

            let view = this._viewer.view;
            let config = new Communicator.PickConfig(Communicator.SelectionMask.Line);

            view.pickFromPoint(position, config).then((selectionItem) => {
                this._currentSelItem = selectionItem;

                let nodeId = selectionItem.getNodeId();

                if (nodeId !== null) {
                    if (this._viewer.model.getNodeType(nodeId) !== Communicator.NodeType.BodyInstance) {
                        return;
                    }

                    if (this._activeHighlight !== null) {
                        if (!selectionItem.equals(this._activeHighlight)) {
                            this._setNodeLineHighlighted(this._activeHighlight, false);
                            this._setNodeLineHighlighted(selectionItem, true);
                        }
                    } else {
                        this._setNodeLineHighlighted(selectionItem, true);
                    }
                } else {

                    if (this._activeHighlight !== null) {
                        this._setNodeLineHighlighted(this._activeHighlight, false);
                    }
                }
            });

        }
        else {
            if (this._activeHighlight !== null) {
                this._setNodeLineHighlighted(this._activeHighlight, false);
            }
        }
    }

    onMouseUp(event) {

        if (event.shiftDown())
            event.setHandled(true);
    }

    _setNodeLineHighlighted(selectionItem, highlighted) {
        if (selectionItem !== null) {
            let nodeId = selectionItem.getNodeId();
            let lineEntity = selectionItem.getLineEntity();

            if (nodeId !== null) {
                if (lineEntity !== null) {
                    let lineId = lineEntity.getLineId();
                    this._viewer.model._setNodeLineHighlighted(nodeId, lineId, highlighted);
                    if (highlighted) {
                        this._activeHighlight = selectionItem;
                    } else {
                        this._activeHighlight = null;
                    }
                }
            }
        }
    }

    _getArcCenter(selectionItem) {
        let nodeId = selectionItem.getNodeId();
        let lineEntity = selectionItem.getLineEntity();

        if (nodeId !== null && lineEntity !== null) {
            let model = this._viewer.model;

            return model.getEdgeProperty(nodeId, lineEntity.getLineId()).then((subentityProperty) => {
                if (subentityProperty instanceof Communicator.SubentityProperties.CircleElement) {
                    const center = subentityProperty.origin;
                    const matrix = model.getNodeNetMatrix(nodeId);
                    matrix.transform(center, center);
                    return { center: center, normal: subentityProperty.normal };
                } else if (subentityProperty instanceof Communicator.SubentityProperties.LineElement) {
                    const points = lineEntity.getPoints();
                    if (points.length === 2) {
                        return Communicator.Point3.add(points[1], points[0]).scale(.5);
                    }
                }
                return null;
            });
        }
        return Promise.resolve(null);
    }

    _addMainHandle(nodeIds, pos) {
        let d = this._viewer.operatorManager.getOperator(Communicator.OperatorId.Handle);
        d.addHandles(nodeIds, pos);
        d.showHandles();
    }

    _addAxisRotationHandle(position, axis, nodeIds, color) {
        if (!color) {
            color = Communicator.Color.red();
        }
        let d = this._viewer.operatorManager.getOperator(Communicator.OperatorId.Handle);
        d.addAxisRotationHandle(position, axis, color);
        d.setNodeIds(nodeIds);
        d.showHandles();
    }
    _addAxisTranslationHandle(position, axis, nodeIds, color) {
        if (!color) {
            color = Communicator.Color.red();
        }
        let d = this._viewer.operatorManager.getOperator(Communicator.OperatorId.Handle);
        d.addAxisTranslationHandle(position.copy(), axis, color);
        d.setNodeIds(nodeIds);
        d.showHandles();
    }
}
2 Likes