technical debt
parent
606e83e2d6
commit
67d6bf8090
|
@ -0,0 +1,754 @@
|
||||||
|
import {
|
||||||
|
EventDispatcher,
|
||||||
|
MOUSE,
|
||||||
|
Quaternion,
|
||||||
|
Vector2,
|
||||||
|
Vector3
|
||||||
|
} from '../../../build/three.module.js';
|
||||||
|
|
||||||
|
var TrackballControls = function ( object, domElement ) {
|
||||||
|
|
||||||
|
if ( domElement === undefined ) console.warn( 'THREE.TrackballControls: The second parameter "domElement" is now mandatory.' );
|
||||||
|
if ( domElement === document ) console.error( 'THREE.TrackballControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.' );
|
||||||
|
|
||||||
|
var scope = this;
|
||||||
|
var STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4 };
|
||||||
|
|
||||||
|
this.object = object;
|
||||||
|
this.domElement = domElement;
|
||||||
|
|
||||||
|
// API
|
||||||
|
|
||||||
|
this.enabled = true;
|
||||||
|
|
||||||
|
this.screen = { left: 0, top: 0, width: 0, height: 0 };
|
||||||
|
|
||||||
|
this.rotateSpeed = 1.0;
|
||||||
|
this.zoomSpeed = 1.2;
|
||||||
|
this.panSpeed = 0.3;
|
||||||
|
|
||||||
|
this.noRotate = false;
|
||||||
|
this.noZoom = false;
|
||||||
|
this.noPan = false;
|
||||||
|
|
||||||
|
this.staticMoving = false;
|
||||||
|
this.dynamicDampingFactor = 0.2;
|
||||||
|
|
||||||
|
this.minDistance = 0;
|
||||||
|
this.maxDistance = Infinity;
|
||||||
|
|
||||||
|
this.keys = [ 65 /*A*/, 83 /*S*/, 68 /*D*/ ];
|
||||||
|
|
||||||
|
this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN };
|
||||||
|
|
||||||
|
// internals
|
||||||
|
|
||||||
|
this.target = new Vector3();
|
||||||
|
|
||||||
|
var EPS = 0.000001;
|
||||||
|
|
||||||
|
var lastPosition = new Vector3();
|
||||||
|
var lastZoom = 1;
|
||||||
|
|
||||||
|
var _state = STATE.NONE,
|
||||||
|
_keyState = STATE.NONE,
|
||||||
|
|
||||||
|
_eye = new Vector3(),
|
||||||
|
|
||||||
|
_movePrev = new Vector2(),
|
||||||
|
_moveCurr = new Vector2(),
|
||||||
|
|
||||||
|
_lastAxis = new Vector3(),
|
||||||
|
_lastAngle = 0,
|
||||||
|
|
||||||
|
_zoomStart = new Vector2(),
|
||||||
|
_zoomEnd = new Vector2(),
|
||||||
|
|
||||||
|
_touchZoomDistanceStart = 0,
|
||||||
|
_touchZoomDistanceEnd = 0,
|
||||||
|
|
||||||
|
_panStart = new Vector2(),
|
||||||
|
_panEnd = new Vector2();
|
||||||
|
|
||||||
|
// for reset
|
||||||
|
|
||||||
|
this.target0 = this.target.clone();
|
||||||
|
this.position0 = this.object.position.clone();
|
||||||
|
this.up0 = this.object.up.clone();
|
||||||
|
this.zoom0 = this.object.zoom;
|
||||||
|
|
||||||
|
// events
|
||||||
|
|
||||||
|
var changeEvent = { type: 'change' };
|
||||||
|
var startEvent = { type: 'start' };
|
||||||
|
var endEvent = { type: 'end' };
|
||||||
|
|
||||||
|
|
||||||
|
// methods
|
||||||
|
|
||||||
|
this.handleResize = function () {
|
||||||
|
|
||||||
|
var box = scope.domElement.getBoundingClientRect();
|
||||||
|
// adjustments come from similar code in the jquery offset() function
|
||||||
|
var d = scope.domElement.ownerDocument.documentElement;
|
||||||
|
scope.screen.left = box.left + window.pageXOffset - d.clientLeft;
|
||||||
|
scope.screen.top = box.top + window.pageYOffset - d.clientTop;
|
||||||
|
scope.screen.width = box.width;
|
||||||
|
scope.screen.height = box.height;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
var getMouseOnScreen = ( function () {
|
||||||
|
|
||||||
|
var vector = new Vector2();
|
||||||
|
|
||||||
|
return function getMouseOnScreen( pageX, pageY ) {
|
||||||
|
|
||||||
|
vector.set(
|
||||||
|
( pageX - scope.screen.left ) / scope.screen.width,
|
||||||
|
( pageY - scope.screen.top ) / scope.screen.height
|
||||||
|
);
|
||||||
|
|
||||||
|
return vector;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}() );
|
||||||
|
|
||||||
|
var getMouseOnCircle = ( function () {
|
||||||
|
|
||||||
|
var vector = new Vector2();
|
||||||
|
|
||||||
|
return function getMouseOnCircle( pageX, pageY ) {
|
||||||
|
|
||||||
|
vector.set(
|
||||||
|
( ( pageX - scope.screen.width * 0.5 - scope.screen.left ) / ( scope.screen.width * 0.5 ) ),
|
||||||
|
( ( scope.screen.height + 2 * ( scope.screen.top - pageY ) ) / scope.screen.width ) // screen.width intentional
|
||||||
|
);
|
||||||
|
|
||||||
|
return vector;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}() );
|
||||||
|
|
||||||
|
this.rotateCamera = ( function () {
|
||||||
|
|
||||||
|
var axis = new Vector3(),
|
||||||
|
quaternion = new Quaternion(),
|
||||||
|
eyeDirection = new Vector3(),
|
||||||
|
objectUpDirection = new Vector3(),
|
||||||
|
objectSidewaysDirection = new Vector3(),
|
||||||
|
moveDirection = new Vector3(),
|
||||||
|
angle;
|
||||||
|
|
||||||
|
return function rotateCamera() {
|
||||||
|
|
||||||
|
moveDirection.set( _moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0 );
|
||||||
|
angle = moveDirection.length();
|
||||||
|
|
||||||
|
if ( angle ) {
|
||||||
|
|
||||||
|
_eye.copy( scope.object.position ).sub( scope.target );
|
||||||
|
|
||||||
|
eyeDirection.copy( _eye ).normalize();
|
||||||
|
objectUpDirection.copy( scope.object.up ).normalize();
|
||||||
|
objectSidewaysDirection.crossVectors( objectUpDirection, eyeDirection ).normalize();
|
||||||
|
|
||||||
|
objectUpDirection.setLength( _moveCurr.y - _movePrev.y );
|
||||||
|
objectSidewaysDirection.setLength( _moveCurr.x - _movePrev.x );
|
||||||
|
|
||||||
|
moveDirection.copy( objectUpDirection.add( objectSidewaysDirection ) );
|
||||||
|
|
||||||
|
axis.crossVectors( moveDirection, _eye ).normalize();
|
||||||
|
|
||||||
|
angle *= scope.rotateSpeed;
|
||||||
|
quaternion.setFromAxisAngle( axis, angle );
|
||||||
|
|
||||||
|
_eye.applyQuaternion( quaternion );
|
||||||
|
scope.object.up.applyQuaternion( quaternion );
|
||||||
|
|
||||||
|
_lastAxis.copy( axis );
|
||||||
|
_lastAngle = angle;
|
||||||
|
|
||||||
|
} else if ( ! scope.staticMoving && _lastAngle ) {
|
||||||
|
|
||||||
|
_lastAngle *= Math.sqrt( 1.0 - scope.dynamicDampingFactor );
|
||||||
|
_eye.copy( scope.object.position ).sub( scope.target );
|
||||||
|
quaternion.setFromAxisAngle( _lastAxis, _lastAngle );
|
||||||
|
_eye.applyQuaternion( quaternion );
|
||||||
|
scope.object.up.applyQuaternion( quaternion );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
_movePrev.copy( _moveCurr );
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}() );
|
||||||
|
|
||||||
|
|
||||||
|
this.zoomCamera = function () {
|
||||||
|
|
||||||
|
var factor;
|
||||||
|
|
||||||
|
if ( _state === STATE.TOUCH_ZOOM_PAN ) {
|
||||||
|
|
||||||
|
factor = _touchZoomDistanceStart / _touchZoomDistanceEnd;
|
||||||
|
_touchZoomDistanceStart = _touchZoomDistanceEnd;
|
||||||
|
|
||||||
|
if ( scope.object.isPerspectiveCamera ) {
|
||||||
|
|
||||||
|
_eye.multiplyScalar( factor );
|
||||||
|
|
||||||
|
} else if ( scope.object.isOrthographicCamera ) {
|
||||||
|
|
||||||
|
scope.object.zoom *= factor;
|
||||||
|
scope.object.updateProjectionMatrix();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
console.warn( 'THREE.TrackballControls: Unsupported camera type' );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * scope.zoomSpeed;
|
||||||
|
|
||||||
|
if ( factor !== 1.0 && factor > 0.0 ) {
|
||||||
|
|
||||||
|
if ( scope.object.isPerspectiveCamera ) {
|
||||||
|
|
||||||
|
_eye.multiplyScalar( factor );
|
||||||
|
|
||||||
|
} else if ( scope.object.isOrthographicCamera ) {
|
||||||
|
|
||||||
|
scope.object.zoom /= factor;
|
||||||
|
scope.object.updateProjectionMatrix();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
console.warn( 'THREE.TrackballControls: Unsupported camera type' );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( scope.staticMoving ) {
|
||||||
|
|
||||||
|
_zoomStart.copy( _zoomEnd );
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
_zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
this.panCamera = ( function () {
|
||||||
|
|
||||||
|
var mouseChange = new Vector2(),
|
||||||
|
objectUp = new Vector3(),
|
||||||
|
pan = new Vector3();
|
||||||
|
|
||||||
|
return function panCamera() {
|
||||||
|
|
||||||
|
mouseChange.copy( _panEnd ).sub( _panStart );
|
||||||
|
|
||||||
|
if ( mouseChange.lengthSq() ) {
|
||||||
|
|
||||||
|
if ( scope.object.isOrthographicCamera ) {
|
||||||
|
|
||||||
|
var scale_x = ( scope.object.right - scope.object.left ) / scope.object.zoom / scope.domElement.clientWidth;
|
||||||
|
var scale_y = ( scope.object.top - scope.object.bottom ) / scope.object.zoom / scope.domElement.clientWidth;
|
||||||
|
|
||||||
|
mouseChange.x *= scale_x;
|
||||||
|
mouseChange.y *= scale_y;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
mouseChange.multiplyScalar( _eye.length() * scope.panSpeed );
|
||||||
|
|
||||||
|
pan.copy( _eye ).cross( scope.object.up ).setLength( mouseChange.x );
|
||||||
|
pan.add( objectUp.copy( scope.object.up ).setLength( mouseChange.y ) );
|
||||||
|
|
||||||
|
scope.object.position.add( pan );
|
||||||
|
scope.target.add( pan );
|
||||||
|
|
||||||
|
if ( scope.staticMoving ) {
|
||||||
|
|
||||||
|
_panStart.copy( _panEnd );
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
_panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( scope.dynamicDampingFactor ) );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}() );
|
||||||
|
|
||||||
|
this.checkDistances = function () {
|
||||||
|
|
||||||
|
if ( ! scope.noZoom || ! scope.noPan ) {
|
||||||
|
|
||||||
|
if ( _eye.lengthSq() > scope.maxDistance * scope.maxDistance ) {
|
||||||
|
|
||||||
|
scope.object.position.addVectors( scope.target, _eye.setLength( scope.maxDistance ) );
|
||||||
|
_zoomStart.copy( _zoomEnd );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( _eye.lengthSq() < scope.minDistance * scope.minDistance ) {
|
||||||
|
|
||||||
|
scope.object.position.addVectors( scope.target, _eye.setLength( scope.minDistance ) );
|
||||||
|
_zoomStart.copy( _zoomEnd );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
this.update = function () {
|
||||||
|
|
||||||
|
_eye.subVectors( scope.object.position, scope.target );
|
||||||
|
|
||||||
|
if ( ! scope.noRotate ) {
|
||||||
|
|
||||||
|
scope.rotateCamera();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! scope.noZoom ) {
|
||||||
|
|
||||||
|
scope.zoomCamera();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! scope.noPan ) {
|
||||||
|
|
||||||
|
scope.panCamera();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.object.position.addVectors( scope.target, _eye );
|
||||||
|
|
||||||
|
if ( scope.object.isPerspectiveCamera ) {
|
||||||
|
|
||||||
|
scope.checkDistances();
|
||||||
|
|
||||||
|
scope.object.lookAt( scope.target );
|
||||||
|
|
||||||
|
if ( lastPosition.distanceToSquared( scope.object.position ) > EPS ) {
|
||||||
|
|
||||||
|
scope.dispatchEvent( changeEvent );
|
||||||
|
|
||||||
|
lastPosition.copy( scope.object.position );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if ( scope.object.isOrthographicCamera ) {
|
||||||
|
|
||||||
|
scope.object.lookAt( scope.target );
|
||||||
|
|
||||||
|
if ( lastPosition.distanceToSquared( scope.object.position ) > EPS || lastZoom !== scope.object.zoom ) {
|
||||||
|
|
||||||
|
scope.dispatchEvent( changeEvent );
|
||||||
|
|
||||||
|
lastPosition.copy( scope.object.position );
|
||||||
|
lastZoom = scope.object.zoom;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
console.warn( 'THREE.TrackballControls: Unsupported camera type' );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
this.reset = function () {
|
||||||
|
|
||||||
|
_state = STATE.NONE;
|
||||||
|
_keyState = STATE.NONE;
|
||||||
|
|
||||||
|
scope.target.copy( scope.target0 );
|
||||||
|
scope.object.position.copy( scope.position0 );
|
||||||
|
scope.object.up.copy( scope.up0 );
|
||||||
|
scope.object.zoom = scope.zoom0;
|
||||||
|
|
||||||
|
scope.object.updateProjectionMatrix();
|
||||||
|
|
||||||
|
_eye.subVectors( scope.object.position, scope.target );
|
||||||
|
|
||||||
|
scope.object.lookAt( scope.target );
|
||||||
|
|
||||||
|
scope.dispatchEvent( changeEvent );
|
||||||
|
|
||||||
|
lastPosition.copy( scope.object.position );
|
||||||
|
lastZoom = scope.object.zoom;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// listeners
|
||||||
|
|
||||||
|
function onPointerDown( event ) {
|
||||||
|
|
||||||
|
if ( scope.enabled === false ) return;
|
||||||
|
|
||||||
|
switch ( event.pointerType ) {
|
||||||
|
|
||||||
|
case 'mouse':
|
||||||
|
case 'pen':
|
||||||
|
onMouseDown( event );
|
||||||
|
break;
|
||||||
|
|
||||||
|
// TODO touch
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPointerMove( event ) {
|
||||||
|
|
||||||
|
if ( scope.enabled === false ) return;
|
||||||
|
|
||||||
|
switch ( event.pointerType ) {
|
||||||
|
|
||||||
|
case 'mouse':
|
||||||
|
case 'pen':
|
||||||
|
onMouseMove( event );
|
||||||
|
break;
|
||||||
|
|
||||||
|
// TODO touch
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPointerUp( event ) {
|
||||||
|
|
||||||
|
if ( scope.enabled === false ) return;
|
||||||
|
|
||||||
|
switch ( event.pointerType ) {
|
||||||
|
|
||||||
|
case 'mouse':
|
||||||
|
case 'pen':
|
||||||
|
onMouseUp( event );
|
||||||
|
break;
|
||||||
|
|
||||||
|
// TODO touch
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function keydown( event ) {
|
||||||
|
|
||||||
|
if ( scope.enabled === false ) return;
|
||||||
|
|
||||||
|
window.removeEventListener( 'keydown', keydown );
|
||||||
|
|
||||||
|
if ( _keyState !== STATE.NONE ) {
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
} else if ( event.keyCode === scope.keys[ STATE.ROTATE ] && ! scope.noRotate ) {
|
||||||
|
|
||||||
|
_keyState = STATE.ROTATE;
|
||||||
|
|
||||||
|
} else if ( event.keyCode === scope.keys[ STATE.ZOOM ] && ! scope.noZoom ) {
|
||||||
|
|
||||||
|
_keyState = STATE.ZOOM;
|
||||||
|
|
||||||
|
} else if ( event.keyCode === scope.keys[ STATE.PAN ] && ! scope.noPan ) {
|
||||||
|
|
||||||
|
_keyState = STATE.PAN;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function keyup() {
|
||||||
|
|
||||||
|
if ( scope.enabled === false ) return;
|
||||||
|
|
||||||
|
_keyState = STATE.NONE;
|
||||||
|
|
||||||
|
window.addEventListener( 'keydown', keydown );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMouseDown( event ) {
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
if ( _state === STATE.NONE ) {
|
||||||
|
|
||||||
|
switch ( event.button ) {
|
||||||
|
|
||||||
|
case scope.mouseButtons.LEFT:
|
||||||
|
_state = STATE.ROTATE;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case scope.mouseButtons.MIDDLE:
|
||||||
|
_state = STATE.ZOOM;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case scope.mouseButtons.RIGHT:
|
||||||
|
_state = STATE.PAN;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
_state = STATE.NONE;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var state = ( _keyState !== STATE.NONE ) ? _keyState : _state;
|
||||||
|
|
||||||
|
if ( state === STATE.ROTATE && ! scope.noRotate ) {
|
||||||
|
|
||||||
|
_moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
|
||||||
|
_movePrev.copy( _moveCurr );
|
||||||
|
|
||||||
|
} else if ( state === STATE.ZOOM && ! scope.noZoom ) {
|
||||||
|
|
||||||
|
_zoomStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
|
||||||
|
_zoomEnd.copy( _zoomStart );
|
||||||
|
|
||||||
|
} else if ( state === STATE.PAN && ! scope.noPan ) {
|
||||||
|
|
||||||
|
_panStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
|
||||||
|
_panEnd.copy( _panStart );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.domElement.ownerDocument.addEventListener( 'pointermove', onPointerMove );
|
||||||
|
scope.domElement.ownerDocument.addEventListener( 'pointerup', onPointerUp );
|
||||||
|
|
||||||
|
scope.dispatchEvent( startEvent );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMouseMove( event ) {
|
||||||
|
|
||||||
|
if ( scope.enabled === false ) return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
var state = ( _keyState !== STATE.NONE ) ? _keyState : _state;
|
||||||
|
|
||||||
|
if ( state === STATE.ROTATE && ! scope.noRotate ) {
|
||||||
|
|
||||||
|
_movePrev.copy( _moveCurr );
|
||||||
|
_moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
|
||||||
|
|
||||||
|
} else if ( state === STATE.ZOOM && ! scope.noZoom ) {
|
||||||
|
|
||||||
|
_zoomEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );
|
||||||
|
|
||||||
|
} else if ( state === STATE.PAN && ! scope.noPan ) {
|
||||||
|
|
||||||
|
_panEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMouseUp( event ) {
|
||||||
|
|
||||||
|
if ( scope.enabled === false ) return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
_state = STATE.NONE;
|
||||||
|
|
||||||
|
scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove );
|
||||||
|
scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp );
|
||||||
|
|
||||||
|
scope.dispatchEvent( endEvent );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function mousewheel( event ) {
|
||||||
|
|
||||||
|
if ( scope.enabled === false ) return;
|
||||||
|
|
||||||
|
if ( scope.noZoom === true ) return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
switch ( event.deltaMode ) {
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
// Zoom in pages
|
||||||
|
_zoomStart.y -= event.deltaY * 0.025;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
// Zoom in lines
|
||||||
|
_zoomStart.y -= event.deltaY * 0.01;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// undefined, 0, assume pixels
|
||||||
|
_zoomStart.y -= event.deltaY * 0.00025;
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.dispatchEvent( startEvent );
|
||||||
|
scope.dispatchEvent( endEvent );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function touchstart( event ) {
|
||||||
|
|
||||||
|
if ( scope.enabled === false ) return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
switch ( event.touches.length ) {
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
_state = STATE.TOUCH_ROTATE;
|
||||||
|
_moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
|
||||||
|
_movePrev.copy( _moveCurr );
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: // 2 or more
|
||||||
|
_state = STATE.TOUCH_ZOOM_PAN;
|
||||||
|
var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
|
||||||
|
var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
|
||||||
|
_touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy );
|
||||||
|
|
||||||
|
var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2;
|
||||||
|
var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2;
|
||||||
|
_panStart.copy( getMouseOnScreen( x, y ) );
|
||||||
|
_panEnd.copy( _panStart );
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.dispatchEvent( startEvent );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function touchmove( event ) {
|
||||||
|
|
||||||
|
if ( scope.enabled === false ) return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
switch ( event.touches.length ) {
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
_movePrev.copy( _moveCurr );
|
||||||
|
_moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: // 2 or more
|
||||||
|
var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
|
||||||
|
var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
|
||||||
|
_touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy );
|
||||||
|
|
||||||
|
var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2;
|
||||||
|
var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2;
|
||||||
|
_panEnd.copy( getMouseOnScreen( x, y ) );
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function touchend( event ) {
|
||||||
|
|
||||||
|
if ( scope.enabled === false ) return;
|
||||||
|
|
||||||
|
switch ( event.touches.length ) {
|
||||||
|
|
||||||
|
case 0:
|
||||||
|
_state = STATE.NONE;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
_state = STATE.TOUCH_ROTATE;
|
||||||
|
_moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
|
||||||
|
_movePrev.copy( _moveCurr );
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.dispatchEvent( endEvent );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function contextmenu( event ) {
|
||||||
|
|
||||||
|
if ( scope.enabled === false ) return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dispose = function () {
|
||||||
|
|
||||||
|
scope.domElement.removeEventListener( 'contextmenu', contextmenu );
|
||||||
|
|
||||||
|
scope.domElement.removeEventListener( 'pointerdown', onPointerDown );
|
||||||
|
scope.domElement.removeEventListener( 'wheel', mousewheel );
|
||||||
|
|
||||||
|
scope.domElement.removeEventListener( 'touchstart', touchstart );
|
||||||
|
scope.domElement.removeEventListener( 'touchend', touchend );
|
||||||
|
scope.domElement.removeEventListener( 'touchmove', touchmove );
|
||||||
|
|
||||||
|
scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove );
|
||||||
|
scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp );
|
||||||
|
|
||||||
|
window.removeEventListener( 'keydown', keydown );
|
||||||
|
window.removeEventListener( 'keyup', keyup );
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
this.domElement.addEventListener( 'contextmenu', contextmenu );
|
||||||
|
|
||||||
|
this.domElement.addEventListener( 'pointerdown', onPointerDown );
|
||||||
|
this.domElement.addEventListener( 'wheel', mousewheel );
|
||||||
|
|
||||||
|
this.domElement.addEventListener( 'touchstart', touchstart );
|
||||||
|
this.domElement.addEventListener( 'touchend', touchend );
|
||||||
|
this.domElement.addEventListener( 'touchmove', touchmove );
|
||||||
|
|
||||||
|
this.domElement.ownerDocument.addEventListener( 'pointermove', onPointerMove );
|
||||||
|
this.domElement.ownerDocument.addEventListener( 'pointerup', onPointerUp );
|
||||||
|
|
||||||
|
window.addEventListener( 'keydown', keydown );
|
||||||
|
window.addEventListener( 'keyup', keyup );
|
||||||
|
|
||||||
|
this.handleResize();
|
||||||
|
|
||||||
|
// force an update at start
|
||||||
|
this.update();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
TrackballControls.prototype = Object.create( EventDispatcher.prototype );
|
||||||
|
TrackballControls.prototype.constructor = TrackballControls;
|
||||||
|
|
||||||
|
export { TrackballControls };
|
104
src/Scene.js
104
src/Scene.js
|
@ -7,7 +7,7 @@ import { Sketch } from './Sketch'
|
||||||
import Stats from '../lib/stats.module.js';
|
import Stats from '../lib/stats.module.js';
|
||||||
|
|
||||||
import { extrude } from './extrude'
|
import { extrude } from './extrude'
|
||||||
import { onHover, onPick } from './mouseEvents';
|
import { onHover, onPick, setHover } from './mouseEvents';
|
||||||
import { _vec2, _vec3, color, awaitSelection, ptObj } from './shared'
|
import { _vec2, _vec3, color, awaitSelection, ptObj } from './shared'
|
||||||
|
|
||||||
import { AxesHelper } from './axes'
|
import { AxesHelper } from './axes'
|
||||||
|
@ -50,7 +50,6 @@ export class Scene {
|
||||||
cameraDist * Math.cos(xzAngle)
|
cameraDist * Math.cos(xzAngle)
|
||||||
);
|
);
|
||||||
|
|
||||||
// const controls = new OrbitControls(camera, view1Elem);
|
|
||||||
const controls = new TrackballControls(this.camera, this.canvas);
|
const controls = new TrackballControls(this.camera, this.canvas);
|
||||||
controls.target.set(0, 0, 0);
|
controls.target.set(0, 0, 0);
|
||||||
controls.update();
|
controls.update();
|
||||||
|
@ -126,6 +125,7 @@ export class Scene {
|
||||||
this.extrude = extrude.bind(this);
|
this.extrude = extrude.bind(this);
|
||||||
this.onHover = onHover.bind(this);
|
this.onHover = onHover.bind(this);
|
||||||
this.onPick = onPick.bind(this);
|
this.onPick = onPick.bind(this);
|
||||||
|
this.setHover = setHover.bind(this);
|
||||||
this.awaitSelection = awaitSelection.bind(this);
|
this.awaitSelection = awaitSelection.bind(this);
|
||||||
|
|
||||||
this.obj3d.addEventListener('change', this.render);
|
this.obj3d.addEventListener('change', this.render);
|
||||||
|
@ -184,7 +184,7 @@ export class Scene {
|
||||||
} else if (k[0] == 'm') {
|
} else if (k[0] == 'm') {
|
||||||
|
|
||||||
entries[k] = loader.parse(state.treeEntries.byId[k])
|
entries[k] = loader.parse(state.treeEntries.byId[k])
|
||||||
console.log(entries[k])
|
// console.log(entries[k])
|
||||||
this.obj3d.add(entries[k])
|
this.obj3d.add(entries[k])
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -207,7 +207,7 @@ export class Scene {
|
||||||
this.selected = []
|
this.selected = []
|
||||||
|
|
||||||
for (let x = 0; x < this.hovered.length; x++) {
|
for (let x = 0; x < this.hovered.length; x++) {
|
||||||
const obj = this.selected[x]
|
const obj = this.hovered[x]
|
||||||
obj.material.color.set(color[obj.userData.type])
|
obj.material.color.set(color[obj.userData.type])
|
||||||
if (obj.userData.type == 'plane') {
|
if (obj.userData.type == 'plane') {
|
||||||
obj.material.opacity = 0.05
|
obj.material.opacity = 0.05
|
||||||
|
@ -221,12 +221,84 @@ export class Scene {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
hover(obj) {
|
||||||
|
|
||||||
|
if (typeof obj == 'object' && !this.selected.includes(obj)) {
|
||||||
|
|
||||||
|
if (obj.userData.type == 'plane') {
|
||||||
|
obj.material.opacity = 0.02
|
||||||
|
obj.children[0].material.color.set(color['planeBorder'])
|
||||||
|
} else {
|
||||||
|
if (obj.userData.type == 'mesh') {
|
||||||
|
obj.material.emissive.set(color.emissive)
|
||||||
|
}
|
||||||
|
obj.material.color.set(color[obj.userData.type])
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (typeof obj == 'object' && !this.selected.includes(obj)) {
|
||||||
|
|
||||||
|
if (obj.userData.type == 'plane') {
|
||||||
|
obj.material.opacity = 0.02
|
||||||
|
obj.children[0].material.color.set(color['planeBorder'])
|
||||||
|
} else {
|
||||||
|
if (obj.userData.type == 'mesh') {
|
||||||
|
obj.material.emissive.set(color.emissive)
|
||||||
|
}
|
||||||
|
obj.material.color.set(color[obj.userData.type])
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
obj.material.color.set(color[obj.userData.type])
|
||||||
|
|
||||||
|
if (obj.userData.type == 'mesh') {
|
||||||
|
obj.material.emissive.set(color.emissive)
|
||||||
|
} else if (obj.userData.type == 'plane') {
|
||||||
|
obj.material.opacity = 0.02
|
||||||
|
obj.children[0].material.color.set(color['planeBorder'])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.userData.type == 'selpoint') {
|
||||||
|
obj.visible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (typeof obj == 'object') {
|
||||||
|
|
||||||
|
if (obj.userData.type == 'plane') {
|
||||||
|
obj.material.opacity = 0.06
|
||||||
|
obj.children[0].material.color.set(hoverColor['planeBorder'])
|
||||||
|
} else {
|
||||||
|
if (obj.userData.type == 'mesh') {
|
||||||
|
obj.material.emissive.set(hoverColor.emissive)
|
||||||
|
}
|
||||||
|
obj.material.color.set(hoverColor[obj.userData.type])
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
subtract(m1, m2) {
|
subtract(m1, m2) {
|
||||||
let bspA = CSG.fromMesh(m1)
|
let bspA = CSG.fromMesh(m1)
|
||||||
let bspB = CSG.fromMesh(m2)
|
let bspB = CSG.fromMesh(m2)
|
||||||
m1.traverse(e => e.layers.disableAll())
|
m1.visible = false
|
||||||
m2.traverse(e => e.layers.disableAll())
|
m2.visible = false
|
||||||
|
m1.traverse(e => e.layers.disable(1))
|
||||||
|
m2.traverse(e => e.layers.disable(1))
|
||||||
|
|
||||||
// // Subtract one bsp from the other via .subtract... other supported modes are .union and .intersect
|
// // Subtract one bsp from the other via .subtract... other supported modes are .union and .intersect
|
||||||
|
|
||||||
|
@ -241,9 +313,8 @@ export class Scene {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const vertices = new THREE.Points(mesh.geometry, new THREE.PointsMaterial());
|
const vertices = new THREE.Points(mesh.geometry, new THREE.PointsMaterial({ size: 0 }));
|
||||||
vertices.userData.type = 'point'
|
vertices.userData.type = 'point'
|
||||||
vertices.layers.disable(0)
|
|
||||||
vertices.layers.enable(1)
|
vertices.layers.enable(1)
|
||||||
|
|
||||||
// mesh.add(line)
|
// mesh.add(line)
|
||||||
|
@ -251,6 +322,15 @@ export class Scene {
|
||||||
|
|
||||||
|
|
||||||
sc.obj3d.add(mesh)
|
sc.obj3d.add(mesh)
|
||||||
|
|
||||||
|
this.store.dispatch({
|
||||||
|
type: 'set-entry-visibility', obj: {
|
||||||
|
[m1.name]: false,
|
||||||
|
[m2.name]: false,
|
||||||
|
[mesh.name]: true,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return mesh
|
return mesh
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -333,18 +413,18 @@ async function addSketch() {
|
||||||
|
|
||||||
this.clearSelection()
|
this.clearSelection()
|
||||||
|
|
||||||
sketch.activate()
|
|
||||||
this.activeSketch = sketch
|
|
||||||
|
|
||||||
sketch.obj3d.addEventListener('change', this.render);
|
sketch.obj3d.addEventListener('change', this.render);
|
||||||
this.render()
|
|
||||||
console.log('render')
|
console.log('render')
|
||||||
this.store.dispatch({ type: 'rx-sketch', obj: sketch })
|
this.store.dispatch({ type: 'rx-sketch', obj: sketch })
|
||||||
|
|
||||||
|
sketch.activate()
|
||||||
|
|
||||||
|
this.render()
|
||||||
}
|
}
|
||||||
|
|
||||||
window.sc = new Scene(store)
|
window.sc = new Scene(store)
|
||||||
sc.loadState()
|
// sc.loadState()
|
||||||
|
|
||||||
// sc.camera.layers.enable(1)
|
// sc.camera.layers.enable(1)
|
||||||
// rc.layers.set(1)
|
// rc.layers.set(1)
|
|
@ -152,9 +152,11 @@ class Sketch {
|
||||||
|
|
||||||
this.setDimLines()
|
this.setDimLines()
|
||||||
|
|
||||||
this.obj3d.traverse(e=>e.layers.enable(0))
|
this.obj3d.traverse(e=>e.layers.enable(2))
|
||||||
|
this.obj3d.visible = true
|
||||||
this.scene.axes.matrix = this.obj3d.matrix
|
this.scene.axes.matrix = this.obj3d.matrix
|
||||||
this.scene.axes.visible = true
|
this.scene.axes.visible = true
|
||||||
|
this.scene.activeSketch = this
|
||||||
|
|
||||||
window.sketcher = this
|
window.sketcher = this
|
||||||
}
|
}
|
||||||
|
@ -165,8 +167,10 @@ class Sketch {
|
||||||
this.canvas.removeEventListener('pointermove', this.onHover)
|
this.canvas.removeEventListener('pointermove', this.onHover)
|
||||||
this.store.dispatch({ type: 'exit-sketch' })
|
this.store.dispatch({ type: 'exit-sketch' })
|
||||||
this.labelContainer.innerHTML = ""
|
this.labelContainer.innerHTML = ""
|
||||||
this.obj3d.traverse(e=>e.layers.disable(0))
|
this.obj3d.visible = false
|
||||||
|
this.obj3d.traverse(e=>e.layers.disable(2))
|
||||||
this.scene.axes.visible = false
|
this.scene.axes.visible = false
|
||||||
|
this.scene.activeSketch = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,8 @@ export async function drawDimension() {
|
||||||
|
|
||||||
line.userData.ids = selection.map(e => e.name)
|
line.userData.ids = selection.map(e => e.name)
|
||||||
|
|
||||||
|
line.layers.enable(2)
|
||||||
|
point.layers.enable(2)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,10 @@ export function drawOnClick1(e) {
|
||||||
this.toPush = drawPoint.call(this, mouseLoc)
|
this.toPush = drawPoint.call(this, mouseLoc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.toPush.forEach(element => {
|
||||||
|
element.layers.enable(2)
|
||||||
|
});
|
||||||
|
|
||||||
this.updatePoint = this.obj3d.children.length
|
this.updatePoint = this.obj3d.children.length
|
||||||
this.obj3d.add(...this.toPush)
|
this.obj3d.add(...this.toPush)
|
||||||
this.linkedObjs.set(this.l_id, [this.mode, this.toPush.map(e => e.name)])
|
this.linkedObjs.set(this.l_id, [this.mode, this.toPush.map(e => e.name)])
|
||||||
|
|
|
@ -72,12 +72,10 @@ export function extrude(sketch) {
|
||||||
|
|
||||||
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
|
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
|
||||||
|
|
||||||
|
|
||||||
// const material = new THREE.MeshLambertMaterial({
|
// const material = new THREE.MeshLambertMaterial({
|
||||||
const material = new THREE.MeshPhongMaterial({
|
const material = new THREE.MeshPhongMaterial({
|
||||||
color: color.mesh,
|
color: color.mesh,
|
||||||
emissive: color.emissive,
|
emissive: color.emissive,
|
||||||
// flatShading:true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const mesh = new THREE.Mesh(geometry, material)
|
const mesh = new THREE.Mesh(geometry, material)
|
||||||
|
@ -95,9 +93,14 @@ export function extrude(sketch) {
|
||||||
|
|
||||||
this.obj3d.add(mesh)
|
this.obj3d.add(mesh)
|
||||||
|
|
||||||
this.render()
|
|
||||||
|
|
||||||
this.store.dispatch({ type: 'rx-extrusion', mesh, sketchId: sketch.obj3d.name })
|
this.store.dispatch({ type: 'rx-extrusion', mesh, sketchId: sketch.obj3d.name })
|
||||||
|
|
||||||
|
if (this.activeSketch == sketch) {
|
||||||
|
this.activeSketch = null
|
||||||
|
sketch.deactivate()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.render()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,8 @@ export function onHover(e) {
|
||||||
raycaster.layers.set(1)
|
raycaster.layers.set(1)
|
||||||
hoverPts = raycaster.intersectObjects(this.obj3d.children, true)
|
hoverPts = raycaster.intersectObjects(this.obj3d.children, true)
|
||||||
} else {
|
} else {
|
||||||
raycaster.layers.set(0)
|
// raycaster.layers.set(0)
|
||||||
|
raycaster.layers.set(2)
|
||||||
hoverPts = raycaster.intersectObjects([...this.obj3d.children[1].children, ...this.obj3d.children])
|
hoverPts = raycaster.intersectObjects([...this.obj3d.children[1].children, ...this.obj3d.children])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,12 +59,7 @@ export function onHover(e) {
|
||||||
for (let x = 0; x < this.hovered.length; x++) { // first clear old hovers that are not selected
|
for (let x = 0; x < this.hovered.length; x++) { // first clear old hovers that are not selected
|
||||||
const obj = this.hovered[x]
|
const obj = this.hovered[x]
|
||||||
if (typeof obj == 'object' && !this.selected.includes(obj)) {
|
if (typeof obj == 'object' && !this.selected.includes(obj)) {
|
||||||
if (obj.userData.type == 'plane') {
|
setHover(obj, 0)
|
||||||
obj.material.opacity = 0.02
|
|
||||||
obj.children[0].material.color.set(color['planeBorder'])
|
|
||||||
} else {
|
|
||||||
obj.material.color.set(color[obj.userData.type])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.hovered = []
|
this.hovered = []
|
||||||
|
@ -71,37 +67,24 @@ export function onHover(e) {
|
||||||
for (let x = 0; x < idx.length; x++) {
|
for (let x = 0; x < idx.length; x++) {
|
||||||
let obj = hoverPts[idx[x]].object
|
let obj = hoverPts[idx[x]].object
|
||||||
|
|
||||||
if (this.obj3d.userData.type == 'sketch') {
|
setHover(obj, 1, false)
|
||||||
obj.material.color.set(hoverColor[obj.userData.type])
|
|
||||||
} else {
|
|
||||||
|
|
||||||
if (obj.userData.type == 'mesh') {
|
if (this.obj3d.userData.type != 'sketch' && obj.userData.type == 'point') {
|
||||||
obj.material.color.set(color['meshTempHover'])
|
ptLoc = obj.geometry.attributes.position.array
|
||||||
} else if (obj.userData.type == 'plane') {
|
.slice(
|
||||||
obj.material.opacity = 0.06
|
3 * hoverPts[idx[x]].index,
|
||||||
obj.children[0].material.color.set(hoverColor['planeBorder'])
|
3 * hoverPts[idx[x]].index + 3
|
||||||
} else if (obj.userData.type == 'point') {
|
)
|
||||||
|
// const pp = this.obj3d.children[0].children[this.fptIdx % 3]
|
||||||
ptLoc = obj.geometry.attributes.position.array
|
const pp = this.obj3d.children[0].children[0]
|
||||||
.slice(
|
pp.geometry.attributes.position.array.set(ptLoc)
|
||||||
3 * hoverPts[idx[x]].index,
|
pp.matrix = obj.parent.matrix
|
||||||
3 * hoverPts[idx[x]].index + 3
|
pp.geometry.attributes.position.needsUpdate = true
|
||||||
)
|
pp.visible = true
|
||||||
|
|
||||||
// const pp = this.obj3d.children[0].children[this.fptIdx % 3]
|
|
||||||
const pp = this.obj3d.children[0].children[0]
|
|
||||||
pp.geometry.attributes.position.array.set(ptLoc)
|
|
||||||
pp.matrix = obj.parent.matrix
|
|
||||||
pp.geometry.attributes.position.needsUpdate = true
|
|
||||||
pp.visible = true
|
|
||||||
|
|
||||||
obj = hoverPts[idx[x]].index
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
obj = hoverPts[idx[x]].index
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
this.hovered.push(obj)
|
this.hovered.push(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,14 +97,12 @@ export function onHover(e) {
|
||||||
|
|
||||||
for (let x = 0; x < this.hovered.length; x++) {
|
for (let x = 0; x < this.hovered.length; x++) {
|
||||||
const obj = this.hovered[x]
|
const obj = this.hovered[x]
|
||||||
|
|
||||||
if (typeof obj == 'object' && !this.selected.includes(obj)) {
|
if (typeof obj == 'object' && !this.selected.includes(obj)) {
|
||||||
if (obj.userData.type == 'plane') {
|
setHover(obj, 0)
|
||||||
obj.material.opacity = 0.02
|
|
||||||
obj.children[0].material.color.set(color['planeBorder'])
|
|
||||||
} else {
|
|
||||||
obj.material.color.set(color[obj.userData.type])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
this.hovered = []
|
this.hovered = []
|
||||||
|
|
||||||
|
@ -145,12 +126,9 @@ export function onPick(e) {
|
||||||
this.selected.push(obj)
|
this.selected.push(obj)
|
||||||
} else {
|
} else {
|
||||||
if (typeof obj == 'object') {
|
if (typeof obj == 'object') {
|
||||||
if (obj.userData.type == 'plane') {
|
|
||||||
obj.material.opacity = 0.06
|
setHover(obj, 1)
|
||||||
obj.children[0].material.color.set(hoverColor['planeBorder'])
|
|
||||||
} else {
|
|
||||||
obj.material.color.set(hoverColor[obj.userData.type])
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
const pp = this.obj3d.children[0].children[this.fptIdx % 3 + 1]
|
const pp = this.obj3d.children[0].children[this.fptIdx % 3 + 1]
|
||||||
const p0 = this.obj3d.children[0].children[0]
|
const p0 = this.obj3d.children[0].children[0]
|
||||||
|
@ -198,16 +176,11 @@ export function onPick(e) {
|
||||||
} else {
|
} else {
|
||||||
for (let x = 0; x < this.selected.length; x++) {
|
for (let x = 0; x < this.selected.length; x++) {
|
||||||
const obj = this.selected[x]
|
const obj = this.selected[x]
|
||||||
obj.material.color.set(color[obj.userData.type])
|
|
||||||
|
setHover(obj, 0)
|
||||||
|
|
||||||
if (obj.userData.type == 'selpoint') {
|
if (obj.userData.type == 'selpoint') {
|
||||||
obj.visible = false
|
obj.visible = false
|
||||||
} else {
|
|
||||||
if (obj.userData.type == 'plane') {
|
|
||||||
obj.material.opacity = 0.02
|
|
||||||
obj.children[0].material.color.set(color['planeBorder'])
|
|
||||||
} else {
|
|
||||||
obj.material.color.set(color[obj.userData.type])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.obj3d.dispatchEvent({ type: 'change' })
|
this.obj3d.dispatchEvent({ type: 'change' })
|
||||||
|
@ -237,22 +210,55 @@ export function onDrag(e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function setHover(obj, state, meshHover = true) {
|
||||||
|
let colObj, visible
|
||||||
|
if (state == 1) {
|
||||||
|
colObj = hoverColor
|
||||||
|
visible = true
|
||||||
|
} else {
|
||||||
|
colObj = color
|
||||||
|
visible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (obj.userData.type) {
|
||||||
|
case 'plane':
|
||||||
|
obj.material.opacity = colObj.opacity
|
||||||
|
obj.children[0].material.color.set(colObj['planeBorder'])
|
||||||
|
break;
|
||||||
|
case 'sketch':
|
||||||
|
obj.visible = visible
|
||||||
|
break;
|
||||||
|
case 'mesh':
|
||||||
|
if (meshHover) {
|
||||||
|
obj.material.emissive.set(colObj.emissive)
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
obj.material.color.set(colObj[obj.userData.type])
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// if (obj.userData.type == 'plane') {
|
||||||
|
// obj.material.opacity = colObj.opacity
|
||||||
|
// obj.children[0].material.color.set(colObj['planeBorder'])
|
||||||
|
// } else if (obj.userData.type != 'mesh') {
|
||||||
|
// obj.material.color.set(colObj[obj.userData.type])
|
||||||
|
// } else if (meshHover) {
|
||||||
|
// obj.material.emissive.set(colObj.emissive)
|
||||||
|
// obj.material.color.set(colObj[obj.userData.type])
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
export function onRelease() {
|
export function onRelease() {
|
||||||
this.canvas.removeEventListener('pointermove', this.onDrag)
|
this.canvas.removeEventListener('pointermove', this.onDrag)
|
||||||
this.canvas.removeEventListener('pointermove', this.onDragDim)
|
this.canvas.removeEventListener('pointermove', this.onDragDim)
|
||||||
this.canvas.removeEventListener('pointerup', this.onRelease)
|
this.canvas.removeEventListener('pointerup', this.onRelease)
|
||||||
|
|
||||||
// for (let x = 3; x < this.obj3d.children.length; x++) {
|
|
||||||
// const obj = this.obj3d.children[x]
|
|
||||||
// obj.geometry.computeBoundingSphere()
|
|
||||||
// }
|
|
||||||
|
|
||||||
// for (let x = 0; x < this.obj3d.children[1].children.length; x++) {
|
|
||||||
// const obj = this.obj3d.children[1].children[x]
|
|
||||||
// obj.geometry.computeBoundingSphere()
|
|
||||||
// }
|
|
||||||
|
|
||||||
this.updateBoundingSpheres()
|
this.updateBoundingSpheres()
|
||||||
|
|
||||||
if (draggedLabel) {
|
if (draggedLabel) {
|
||||||
draggedLabel.style.zIndex = 0;
|
draggedLabel.style.zIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
/* @tailwind base; */
|
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
|
|
||||||
* {
|
* {
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,19 +49,12 @@ body {
|
||||||
hover:bg-gray-500 hover:text-gray-200;
|
hover:bg-gray-500 hover:text-gray-200;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-light {
|
|
||||||
cursor: pointer;
|
|
||||||
@apply fill-current
|
|
||||||
bg-transparent text-gray-700
|
|
||||||
hover:bg-gray-300 hover:text-gray-700;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.btn-green {
|
.btn-green {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@apply fill-current
|
@apply fill-current
|
||||||
bg-transparent text-gray-200
|
bg-transparent text-gray-200
|
||||||
hover:bg-transparent hover:text-green-200;
|
hover:bg-transparent hover:text-green-400;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -13,8 +13,7 @@ import * as Icon from "./icons";
|
||||||
export const NavBar = () => {
|
export const NavBar = () => {
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const treeEntries = useSelector(state => state.treeEntries)
|
const treeEntries = useSelector(state => state.treeEntries)
|
||||||
const activeSketchId = useSelector(state => state.activeSketchId)
|
const activeSketchId = useSelector(state => state.treeEntries.activeSketchId)
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!activeSketchId) {
|
if (!activeSketchId) {
|
||||||
|
@ -35,6 +34,7 @@ export const NavBar = () => {
|
||||||
// dispatch({ type: 'update-descendents', sketch})
|
// dispatch({ type: 'update-descendents', sketch})
|
||||||
sc.activeSketch = null
|
sc.activeSketch = null
|
||||||
sc.render()
|
sc.render()
|
||||||
|
forceUpdate()
|
||||||
// sc.activeDim = this.activeSketch.obj3d.children[1].children
|
// sc.activeDim = this.activeSketch.obj3d.children[1].children
|
||||||
}, 'Finish'] :
|
}, 'Finish'] :
|
||||||
[FaEdit, sc.addSketch, 'Sketch [s]']
|
[FaEdit, sc.addSketch, 'Sketch [s]']
|
||||||
|
|
|
@ -10,10 +10,9 @@ export const preloadedState = {
|
||||||
allIds: [],
|
allIds: [],
|
||||||
tree: {},
|
tree: {},
|
||||||
order: {},
|
order: {},
|
||||||
|
visible: {},
|
||||||
|
activeSketchId: ""
|
||||||
},
|
},
|
||||||
ui: {
|
|
||||||
toolTipImmediate: false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function reducer(state = {}, action) {
|
export function reducer(state = {}, action) {
|
||||||
|
@ -24,18 +23,33 @@ export function reducer(state = {}, action) {
|
||||||
byId: { [action.obj.obj3d.name]: { $set: action.obj } },
|
byId: { [action.obj.obj3d.name]: { $set: action.obj } },
|
||||||
allIds: { $push: [action.obj.obj3d.name] },
|
allIds: { $push: [action.obj.obj3d.name] },
|
||||||
tree: { [action.obj.obj3d.name]: { $set: {} } },
|
tree: { [action.obj.obj3d.name]: { $set: {} } },
|
||||||
order: { [action.obj.obj3d.name]: { $set: state.treeEntries.allIds.length } }
|
order: { [action.obj.obj3d.name]: { $set: state.treeEntries.allIds.length } },
|
||||||
|
visible: { [action.obj.obj3d.name]: { $set: true } },
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
case 'set-entry-visibility': {
|
||||||
|
return update(state, {
|
||||||
|
treeEntries: {
|
||||||
|
visible: { $merge: action.obj },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
case 'set-active-sketch':
|
case 'set-active-sketch':
|
||||||
return update(state, {
|
return update(state, {
|
||||||
activeSketchId: { $set: action.sketch },
|
treeEntries: {
|
||||||
|
visible: { [action.sketch]: { $set: true } },
|
||||||
|
activeSketchId: { $set: action.sketch },
|
||||||
|
},
|
||||||
})
|
})
|
||||||
case 'exit-sketch':
|
case 'exit-sketch':
|
||||||
return {
|
return update(state, {
|
||||||
...state, activeSketchId: ''
|
treeEntries: {
|
||||||
}
|
activeSketchId: { $set: "" },
|
||||||
|
visible: { [state.treeEntries.activeSketchId]: { $set: false } },
|
||||||
|
},
|
||||||
|
})
|
||||||
case 'rx-extrusion':
|
case 'rx-extrusion':
|
||||||
|
|
||||||
return update(state, {
|
return update(state, {
|
||||||
|
@ -48,7 +62,10 @@ export function reducer(state = {}, action) {
|
||||||
[action.sketchId]: { [action.mesh.name]: { $set: true } },
|
[action.sketchId]: { [action.mesh.name]: { $set: true } },
|
||||||
[action.mesh.name]: { $set: {} }
|
[action.mesh.name]: { $set: {} }
|
||||||
},
|
},
|
||||||
order: { [action.mesh.name]: { $set: state.treeEntries.allIds.length } }
|
order: { [action.mesh.name]: { $set: state.treeEntries.allIds.length } },
|
||||||
|
visible: {
|
||||||
|
[action.mesh.name]: { $set: true }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
case 'rx-boolean':
|
case 'rx-boolean':
|
||||||
|
@ -62,6 +79,7 @@ export function reducer(state = {}, action) {
|
||||||
tree: {
|
tree: {
|
||||||
[action.deps[0]]: { [action.mesh.name]: { $set: true } },
|
[action.deps[0]]: { [action.mesh.name]: { $set: true } },
|
||||||
[action.deps[1]]: { [action.mesh.name]: { $set: true } },
|
[action.deps[1]]: { [action.mesh.name]: { $set: true } },
|
||||||
|
[action.mesh.name]: { $set: {} }
|
||||||
},
|
},
|
||||||
order: { [action.mesh.name]: { $set: state.treeEntries.allIds.length } }
|
order: { [action.mesh.name]: { $set: state.treeEntries.allIds.length } }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
|
|
||||||
|
|
||||||
import React, { useReducer } from 'react';
|
import React, { useReducer, useState } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
import { MdEdit, MdVisibilityOff, MdVisibility, MdDelete } from 'react-icons/md'
|
import { MdVisibilityOff, MdVisibility, MdDelete } from 'react-icons/md'
|
||||||
|
|
||||||
import { FaCube, FaEdit } from 'react-icons/fa'
|
import { FaCube, FaEdit } from 'react-icons/fa'
|
||||||
|
|
||||||
|
@ -29,89 +29,100 @@ const TreeEntry = ({ entId }) => {
|
||||||
const treeEntries = useSelector(state => state.treeEntries.byId)
|
const treeEntries = useSelector(state => state.treeEntries.byId)
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
const activeSketchId = useSelector(state => state.activeSketchId)
|
const activeSketchId = useSelector(state => state.treeEntries.activeSketchId)
|
||||||
|
// const activeSketchId = treeEntries.activeSketchId
|
||||||
|
|
||||||
let obj3d, entry;
|
const visible = useSelector(state => state.treeEntries.visible[entId])
|
||||||
|
|
||||||
|
let obj3d, sketch;
|
||||||
|
|
||||||
entry = treeEntries[entId]
|
|
||||||
|
|
||||||
if (treeEntries[entId].obj3d) {
|
if (treeEntries[entId].obj3d) {
|
||||||
obj3d = treeEntries[entId].obj3d
|
obj3d = treeEntries[entId].obj3d
|
||||||
|
sketch = treeEntries[entId]
|
||||||
} else {
|
} else {
|
||||||
obj3d = treeEntries[entId]
|
obj3d = treeEntries[entId]
|
||||||
}
|
}
|
||||||
console.log(obj3d.userData.type)
|
|
||||||
let Icon = treeIcons[obj3d.userData.type]
|
let Icon = treeIcons[obj3d.userData.type]
|
||||||
|
|
||||||
const [_, forceUpdate] = useReducer(x => x + 1, 0);
|
const [_, forceUpdate] = useReducer(x => x + 1, 0);
|
||||||
|
|
||||||
// const vis = obj3d.visible
|
|
||||||
const vis = obj3d.layers.mask & 1
|
// const vis = obj3d.layers.mask & 1
|
||||||
|
|
||||||
return <div className='btn select-none flex justify-start w-full h-7 items-center text-sm'
|
return <div className='btn select-none flex justify-start w-full h-7 items-center text-sm'
|
||||||
|
|
||||||
onDoubleClick={() => {
|
onDoubleClick={() => {
|
||||||
activeSketchId && treeEntries[activeSketchId].deactivate()
|
if (entId[0] == 's') {
|
||||||
console.log(entry)
|
activeSketchId && treeEntries[activeSketchId].deactivate()
|
||||||
entry.activate()
|
sketch.activate()
|
||||||
sc.clearSelection()
|
sc.clearSelection()
|
||||||
sc.activeSketch = entry;
|
sc.activeSketch = sketch;
|
||||||
|
}
|
||||||
|
|
||||||
|
}}
|
||||||
|
|
||||||
|
onPointerEnter={() => {
|
||||||
|
sc.setHover(obj3d, 1)
|
||||||
|
sc.render()
|
||||||
|
}}
|
||||||
|
onPointerLeave={() => {
|
||||||
|
// console.log('activeid',activeSketchId,'visstate',visState)
|
||||||
|
if (visible & entId[0] == 's') return
|
||||||
|
if (sc.selected.includes(obj3d) || activeSketchId == obj3d.name) return
|
||||||
|
sc.setHover(obj3d, 0)
|
||||||
|
sc.render()
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
if (entId[0] == 'm') {
|
||||||
|
sc.selected.push(
|
||||||
|
obj3d
|
||||||
|
)
|
||||||
|
sc.render()
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon className='h-full w-auto p-1.5' />
|
<Icon className='h-full w-auto p-1.5' />
|
||||||
<div className="btn pl-1"
|
<div className="btn pl-1">
|
||||||
onPointerEnter={() => {
|
|
||||||
if (entId[0] == 'm') {
|
|
||||||
// entry.material.color.set(color.hover)
|
|
||||||
sc.render()
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onPointerLeave={() => {
|
|
||||||
const obj = entry
|
|
||||||
if (entId[0] == 'm' && !sc.selected.includes(obj)) {
|
|
||||||
// obj.material.color.set(color.mesh)
|
|
||||||
sc.render()
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onPointerDown={() => {
|
|
||||||
if (entId[0] == 'm') {
|
|
||||||
sc.selected.push(
|
|
||||||
entry
|
|
||||||
)
|
|
||||||
sc.render()
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{entId}
|
{entId}
|
||||||
</div>
|
</div>
|
||||||
<div className='flex h-full ml-auto'>
|
<div className='flex h-full ml-auto'>
|
||||||
|
|
||||||
<MdDelete className='btn-green h-full w-auto p-1.5'
|
<MdDelete className='btn-green h-full w-auto p-1.5'
|
||||||
onClick={() => {
|
onClick={(e) => {
|
||||||
dispatch({ type: 'delete-node', id: entId })
|
dispatch({ type: 'delete-node', id: entId })
|
||||||
|
sc.render()
|
||||||
|
e.stopPropagation()
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{
|
{
|
||||||
vis ?
|
visible ?
|
||||||
<MdVisibility className='btn-green h-full w-auto p-1.5'
|
<MdVisibility className='btn-green h-full w-auto p-1.5'
|
||||||
onClick={() => {
|
onClick={(e) => {
|
||||||
obj3d.traverse((e) => e.layers.disableAll())
|
e.stopPropagation()
|
||||||
|
console.log('hide')
|
||||||
|
dispatch({ type: "set-entry-visibility", obj: {[entId]:false} })
|
||||||
|
obj3d.visible = false;
|
||||||
|
if (obj3d.userData.type == 'mesh') {
|
||||||
|
obj3d.traverse((e) => e.layers.disable(1))
|
||||||
|
}
|
||||||
|
|
||||||
sc.render()
|
sc.render()
|
||||||
forceUpdate()
|
forceUpdate()
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
:
|
:
|
||||||
<MdVisibilityOff className='btn-green h-full w-auto p-1.5'
|
<MdVisibilityOff className='btn-green h-full w-auto p-1.5'
|
||||||
onClick={() => {
|
onClick={(e) => {
|
||||||
if (obj3d.userData.type == 'sketch') {
|
e.stopPropagation()
|
||||||
obj3d.traverse((e) => e.layers.enable(0))
|
console.log('show')
|
||||||
} else {
|
obj3d.visible = true;
|
||||||
|
dispatch({ type: "set-entry-visibility", obj: {[entId]:true} })
|
||||||
|
if (obj3d.userData.type == 'mesh') {
|
||||||
obj3d.traverse((e) => {
|
obj3d.traverse((e) => {
|
||||||
e.layers.enable(0)
|
|
||||||
e.layers.enable(1)
|
e.layers.enable(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
sc.render()
|
sc.render()
|
||||||
forceUpdate()
|
forceUpdate()
|
||||||
|
|
|
@ -22,18 +22,23 @@ const color = {
|
||||||
line: 0xffffff,
|
line: 0xffffff,
|
||||||
mesh: 0x9DCFED,
|
mesh: 0x9DCFED,
|
||||||
dimension: 0x0000ff,
|
dimension: 0x0000ff,
|
||||||
|
|
||||||
plane: 0xffff00,
|
plane: 0xffff00,
|
||||||
planeBorder: 0x2e2e00,
|
planeBorder: 0x2e2e00,
|
||||||
|
opacity: 0.02
|
||||||
}
|
}
|
||||||
|
|
||||||
const hoverColor = {
|
const hoverColor = {
|
||||||
|
emissive: 0x343407,
|
||||||
point: 0x00ff00,
|
point: 0x00ff00,
|
||||||
selpoint: 0xff0000,
|
selpoint: 0xff0000,
|
||||||
line: 0x00ff00,
|
line: 0x00ff00,
|
||||||
mesh: 0xFAB601,
|
mesh: 0xFAB601,
|
||||||
dimension: 0x00ff00,
|
dimension: 0x00ff00,
|
||||||
|
|
||||||
plane: 0xffff00,
|
plane: 0xffff00,
|
||||||
planeBorder: 0x919100,
|
planeBorder: 0x919100,
|
||||||
|
opacity: 0.06
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
5
todo.txt
5
todo.txt
|
@ -12,15 +12,16 @@ boolean flesh out refresh / replace mesh
|
||||||
- hidden bodies messes up hover highlight \\ fixed
|
- hidden bodies messes up hover highlight \\ fixed
|
||||||
- add for union and intersect
|
- add for union and intersect
|
||||||
- consume skeches after extrude // done
|
- consume skeches after extrude // done
|
||||||
|
- selection hover disspates when rehovered //fixed
|
||||||
|
- boolean unable to select click //fixed
|
||||||
|
|
||||||
|
|
||||||
vertical // done
|
vertical // done
|
||||||
horizontal // done
|
horizontal // done
|
||||||
|
|
||||||
|
|
||||||
|
- hover sync between tree and work area
|
||||||
- select sketch for extrusion
|
- select sketch for extrusion
|
||||||
- boolean unable to select click
|
|
||||||
|
|
||||||
auto update extrude
|
auto update extrude
|
||||||
extrude dialogue
|
extrude dialogue
|
||||||
|
|
Loading…
Reference in New Issue