diff --git a/modules/scene/controls/CADTrackballControls.js b/modules/scene/controls/CADTrackballControls.js index 15f45e70..5444165c 100644 --- a/modules/scene/controls/CADTrackballControls.js +++ b/modules/scene/controls/CADTrackballControls.js @@ -77,7 +77,6 @@ export function CADTrackballControls( object, domElement ) { // events - var changeEvent = { type: 'change' }; var startEvent = { type: 'start' }; var endEvent = { type: 'end' }; @@ -304,7 +303,7 @@ export function CADTrackballControls( object, domElement ) { }; - this.update = function () { + this.evaluate = function () { _eye.subVectors( _this.object.position, _this.target ); @@ -332,16 +331,12 @@ export function CADTrackballControls( object, domElement ) { _this.object.lookAt( _this.target ); - if ( lastPosition.distanceToSquared( _this.object.position ) > EPS || this.projectionChanged) { - + const needsRender = lastPosition.distanceToSquared( _this.object.position ) > EPS || this.projectionChanged; + if ( needsRender) { this.projectionChanged = false; - - _this.dispatchEvent( changeEvent ); - lastPosition.copy( _this.object.position ); - } - + return needsRender; }; this.reset = function () { @@ -357,8 +352,6 @@ export function CADTrackballControls( object, domElement ) { _this.object.lookAt( _this.target ); - _this.dispatchEvent( changeEvent ); - lastPosition.copy( _this.object.position ); }; @@ -656,11 +649,7 @@ export function CADTrackballControls( object, domElement ) { window.addEventListener( 'keyup', keyup, false ); this.handleResize(); - - // force an update at start - this.update(); - -}; +} CADTrackballControls.prototype = Object.create( THREE.EventDispatcher.prototype ); CADTrackballControls.prototype.constructor = CADTrackballControls; \ No newline at end of file diff --git a/modules/scene/sceneSetup.ts b/modules/scene/sceneSetup.ts index f58a5963..1291ae35 100644 --- a/modules/scene/sceneSetup.ts +++ b/modules/scene/sceneSetup.ts @@ -16,7 +16,6 @@ import { Vector3, WebGLRenderer } from "three"; -import {TransformControls} from "three/examples/jsm/controls/TransformControls"; import {stream} from "lstream"; export default class SceneSetUp { @@ -33,10 +32,9 @@ export default class SceneSetUp { private _prevContainerWidth: number; private _prevContainerHeight: number; trackballControls: CADTrackballControls; - transformControls: TransformControls; - updateControlsAndHelpers: () => void; viewportSizeUpdate$ = stream(); - + renderRequested: boolean; + constructor(container, onRendered) { this.workingSphere = 10000; @@ -45,7 +43,8 @@ export default class SceneSetUp { this.rootGroup = this.scene; this.onRendered = onRendered; this.scene.userData.sceneSetUp = this; - + this.renderRequested = false; + this.setUpCamerasAndLights(); this.setUpControls(); @@ -55,7 +54,11 @@ export default class SceneSetUp { aspect() { return this.container.clientWidth / this.container.clientHeight; } - + + requestRender() { + this.renderRequested = true; + } + createOrthographicCamera() { let width = this.container.clientWidth; let height = this.container.clientHeight; @@ -101,7 +104,7 @@ export default class SceneSetUp { this.updateOrthographicCameraViewport(); this.renderer.setSize( this.container.clientWidth, this.container.clientHeight ); this.viewportSizeUpdate$.next(); - this.render(); + this.__render_NeverCallMeFromOutside(); } } @@ -148,8 +151,7 @@ export default class SceneSetUp { this.camera = camera; this.trackballControls.object = camera; - this.transformControls.camera = camera; - this.updateControlsAndHelpers(); + this.requestRender(); } setUpControls() { @@ -173,29 +175,7 @@ export default class SceneSetUp { trackballControls.dynamicDampingFactor = 0.3; trackballControls.keys = [ 65, 83, 68 ]; - trackballControls.addEventListener( 'change', () => this.render()); - - let transformControls: any = new TransformControls( this.camera, this.renderer.domElement ); - transformControls.addEventListener( 'change', () => this.render() ); - this.scene.add( transformControls ); - this.trackballControls = trackballControls; - this.transformControls = transformControls; - - let updateTransformControls = () => { - if (transformControls.object !== undefined) { - if (transformControls.object.parent === undefined) { - transformControls.detach(); - this.render(); - } - transformControls.update(); - } - }; - - this.updateControlsAndHelpers = function() { - trackballControls.update(); - updateTransformControls(); - }; } createRaycaster(viewX, viewY) { @@ -288,11 +268,15 @@ export default class SceneSetUp { animate() { requestAnimationFrame( () => this.animate() ); - this.updateControlsAndHelpers(); + const controlsChangedViewpoint = this.trackballControls.evaluate(); + if (controlsChangedViewpoint || this.renderRequested) { + this.__render_NeverCallMeFromOutside(); + } this.updateViewportSizeIfNeeded(); }; - render() { + private __render_NeverCallMeFromOutside() { + this.renderRequested = false; this.light.position.set(this.camera.position.x, this.camera.position.y, this.camera.position.z); this.renderer.render(this.scene, this.camera); this.onRendered(); diff --git a/web/app/cad/actions/usabilityActions.js b/web/app/cad/actions/usabilityActions.js index 64a3890d..1469040f 100644 --- a/web/app/cad/actions/usabilityActions.js +++ b/web/app/cad/actions/usabilityActions.js @@ -1,5 +1,7 @@ import Vector, {AXIS, ORIGIN} from 'math/vector'; import {RiCamera2Line} from "react-icons/ri"; +import {ViewMode} from "cad/scene/viewer"; +import {GiCube, HiCube, HiOutlineCube} from "react-icons/all"; const NEG_X = AXIS.X.negate(); const NEG_Y = AXIS.Y.negate(); @@ -192,4 +194,37 @@ export default [ noWizardFocus: true }) }, + { + id: 'ViewMode_WIREFRAME_ON', + appearance: { + label: 'wireframe', + icon: HiOutlineCube, + }, + invoke: ctx => { + ctx.services.viewer.viewMode$.next(ViewMode.WIREFRAME); + ctx.services.viewer.requestRender(); + } + }, + { + id: 'ViewMode_SHADED_ON', + appearance: { + label: 'shaded', + icon: HiCube, + }, + invoke: ctx => { + ctx.services.viewer.viewMode$.next(ViewMode.SHADED); + ctx.services.viewer.requestRender(); + } + }, + { + id: 'ViewMode_SHADED_WITH_EDGES_ON', + appearance: { + label: 'shaded with edges', + icon: GiCube, + }, + invoke: ctx => { + ctx.services.viewer.viewMode$.next(ViewMode.SHADED_WITH_EDGES); + ctx.services.viewer.requestRender(); + } + }, ] \ No newline at end of file diff --git a/web/app/cad/craft/operationPlugin.ts b/web/app/cad/craft/operationPlugin.ts index d4f21910..072c4226 100644 --- a/web/app/cad/craft/operationPlugin.ts +++ b/web/app/cad/craft/operationPlugin.ts @@ -136,7 +136,7 @@ export interface OperationDescriptor { previewGeomProvider?: (params: R) => OperationGeometryProvider, previewer?: any, form: FormDefinition | React.FunctionComponent, - defaultActiveField: string, + defaultActiveField?: string, schema?: OperationSchema, onParamsUpdate?: (params, name, value) => void, masking?: { diff --git a/web/app/cad/dom/components/ControlBar.less b/web/app/cad/dom/components/ControlBar.less index 51ada59c..111a33f9 100644 --- a/web/app/cad/dom/components/ControlBar.less +++ b/web/app/cad/dom/components/ControlBar.less @@ -20,7 +20,7 @@ .left, .right { display: flex; - align-items: baseline; + align-items: center; } .right { @@ -30,6 +30,8 @@ @border: 1px solid @border-color; .left .button { border-right: @border; + display: flex; + align-items: center; } .right .button { border-left: @border; diff --git a/web/app/cad/dom/components/PlugableControlBar.jsx b/web/app/cad/dom/components/PlugableControlBar.jsx index ad2ec0a8..2c995d97 100644 --- a/web/app/cad/dom/components/PlugableControlBar.jsx +++ b/web/app/cad/dom/components/PlugableControlBar.jsx @@ -23,17 +23,21 @@ function ButtonGroup({actions}) { class ActionButton extends React.Component { render() { - let {label, cssIcons, enabled, visible, actionId, ...props} = this.props; + let {label, cssIcons, icon, enabled, visible, actionId, ...props} = this.props; if (!visible) { return null; } + const Icon = icon ? icon : null; + if (isMenuAction(actionId)) { let onClick = props.onClick; props.onClick = e => onClick(menuAboveElementHint(this.el)); } return this.el = el} {...props} > - {cssIcons && } {label} + {cssIcons && } + {Icon && } + {label} ; } } diff --git a/web/app/cad/scene/controls/mouseEventSystemPlugin.js b/web/app/cad/scene/controls/mouseEventSystemPlugin.js index 94a5ddfa..129f6af8 100644 --- a/web/app/cad/scene/controls/mouseEventSystemPlugin.js +++ b/web/app/cad/scene/controls/mouseEventSystemPlugin.js @@ -1,11 +1,18 @@ import {printRaycastDebugInfo, RayCastDebugInfo} from "./rayCastDebug"; -import {LOG_FLAGS} from "../../logFlags"; +import {LOG_FLAGS} from "cad/logFlags"; +import {stream} from "lstream"; + +const MouseStates = { + IDLE: 'IDLE', + DOWN: 'DOWN' +} export function activate(ctx) { const {services, streams} = ctx; const domElement = services.viewer.sceneSetup.domElement(); const event = { - viewer: services.viewer + viewer: services.viewer, + mouseState: MouseStates.IDLE }; domElement.addEventListener('mousedown', mousedown, false); @@ -13,6 +20,12 @@ export function activate(ctx) { domElement.addEventListener('mousemove', mousemove, false); domElement.addEventListener('dblclick', dblclick, false); + const onMoveLogicRequest$ = stream(); + + onMoveLogicRequest$.throttle(100).attach(() => { + const hits = performRaycast(event.mouseEvent); + dispatchMousemove(event.mouseEvent, hits) + }); let performRaycast = e => { const hits = services.viewer.raycast(e, services.cadScene.workGroup.children, RayCastDebugInfo); @@ -43,6 +56,7 @@ export function activate(ctx) { } function mousedown(e) { + event.mouseState = MouseStates.DOWN; let hits = performRaycast(e); dispatchMousedown(e, hits); } @@ -69,6 +83,7 @@ export function activate(ctx) { } function mouseup(e) { + event.mouseState = MouseStates.IDLE; event.mouseEvent = e; if (toDrag) { stopDrag(e); @@ -111,8 +126,9 @@ export function activate(ctx) { if (toDrag) { toDrag.dragMove(event); } else { - let hits = performRaycast(e); - dispatchMousemove(e, hits) + if (event.mouseState === MouseStates.IDLE) { + onMoveLogicRequest$.next(); + } } } diff --git a/web/app/cad/scene/viewer.ts b/web/app/cad/scene/viewer.ts index c39c28da..7da67867 100644 --- a/web/app/cad/scene/viewer.ts +++ b/web/app/cad/scene/viewer.ts @@ -1,35 +1,33 @@ -import SceneSetup from 'scene/sceneSetup'; -import {Emitter, externalState, StateStream, stream} from "lstream"; +import {Emitter, externalState, state, StateStream, stream} from "lstream"; import SceneSetUp from "scene/sceneSetup"; +export enum ViewMode { + WIREFRAME = 'WIREFRAME', + SHADED = 'SHADED', + SHADED_WITH_EDGES = 'SHADED_WITH_EDGES' +} + export default class Viewer { sceneRendered$: Emitter = stream(); cameraMode$: StateStream; - sceneSetup: SceneSetUp; - renderRequested: boolean; + viewMode$: StateStream = state(ViewMode.SHADED_WITH_EDGES); + sceneSetup: SceneSetUp; constructor(container, onRendered) { this.cameraMode$ = externalState(() => this.getCameraMode(), mode => this.setCameraMode(mode)) - this.sceneSetup = new SceneSetup(container, onRendered); - this.renderRequested = false; + this.sceneSetup = new SceneSetUp(container, onRendered); } render() { - this.sceneSetup.render(); + this.requestRender(); } requestRender = () => { - if (this.renderRequested) { - return; - } - setTimeout(() => { - this.renderRequested = false; - this.render(); - }); + this.sceneSetup.requestRender(); }; setVisualProp = (obj, prop, value) => { diff --git a/web/app/cad/scene/views/edgeView.js b/web/app/cad/scene/views/edgeView.js index db2d0838..511958d6 100644 --- a/web/app/cad/scene/views/edgeView.js +++ b/web/app/cad/scene/views/edgeView.js @@ -1,4 +1,5 @@ import {CurveBasedView} from './curveBasedView'; +import {ViewMode} from "cad/scene/viewer"; const MarkerTable = [ { @@ -19,5 +20,8 @@ export class EdgeView extends CurveBasedView { let brepEdge = edge.brepEdge; let tess = brepEdge.data.tessellation ? brepEdge.data.tessellation : brepEdge.curve.tessellateToData(); super(ctx, edge, tess, 3, 0x000000, MarkerTable); + this.addDisposer(ctx.viewer.viewMode$.attach(mode => { + this.representation.visible = (mode === ViewMode.SHADED_WITH_EDGES || mode === ViewMode.WIREFRAME); + })); } } diff --git a/web/app/cad/scene/views/faceView.js b/web/app/cad/scene/views/faceView.js index 5b981b6d..ad006939 100644 --- a/web/app/cad/scene/views/faceView.js +++ b/web/app/cad/scene/views/faceView.js @@ -7,6 +7,7 @@ import {createSolidMaterial} from "cad/scene/views/viewUtils"; import {SketchMesh} from "cad/scene/views/shellView"; import {FACE} from "cad/model/entities"; import {setAttribute} from "scene/objectData"; +import {ViewMode} from "cad/scene/viewer"; export class SketchingView extends View { @@ -69,7 +70,6 @@ export class FaceView extends SketchingView { super(ctx, face, parent); let geom; - this.meshFaces = []; if (face.brepFace.data.tessellation) { geom = tessDataToGeom(face.brepFace.data.tessellation.data) } else { @@ -86,6 +86,10 @@ export class FaceView extends SketchingView { this.ctx.highlightService.unHighlight(this.model.id); } this.rootGroup.add(this.mesh); + + this.addDisposer(ctx.viewer.viewMode$.attach(mode => { + this.mesh.visible = (mode === ViewMode.SHADED_WITH_EDGES || mode === ViewMode.SHADED); + })); } dispose() { diff --git a/web/app/cad/workbench/menuConfig.js b/web/app/cad/workbench/menuConfig.js index 5f0fe9ae..809d9b0d 100644 --- a/web/app/cad/workbench/menuConfig.js +++ b/web/app/cad/workbench/menuConfig.js @@ -1,3 +1,5 @@ +import {GiCube} from "react-icons/all"; + export default [ { id: 'file', @@ -26,6 +28,13 @@ export default [ actions: ['StandardViewFront', 'StandardViewBack', 'StandardViewLeft', 'StandardViewRight', 'StandardViewTop', 'StandardViewBottom', 'StandardView3Way'] }, + { + id: 'viewModes', + label: 'mode', + icon: GiCube, + info: 'view/render mode', + actions: ['ViewMode_WIREFRAME_ON', 'ViewMode_SHADED_ON', 'ViewMode_SHADED_WITH_EDGES_ON'] + }, { id: 'boolean', label: 'bool', diff --git a/web/app/cad/workbench/uiConfigPlugin.js b/web/app/cad/workbench/uiConfigPlugin.js index 204de06f..3a172daf 100644 --- a/web/app/cad/workbench/uiConfigPlugin.js +++ b/web/app/cad/workbench/uiConfigPlugin.js @@ -12,7 +12,7 @@ import {Explorer} from "cad/dom/components/Explorer"; export function activate(ctx) { const {services, streams} = ctx; - streams.ui.controlBars.left.value = ['menu.file', 'menu.craft', 'menu.boolean', 'menu.primitives', 'menu.views', 'Donate', 'GitHub']; + streams.ui.controlBars.left.value = ['menu.file', 'menu.craft', 'menu.boolean', 'menu.primitives', 'menu.views', 'menu.viewModes', 'Donate', 'GitHub']; streams.ui.controlBars.right.value = [ ['Info', {label: null}], ['RefreshSketches', {label: null}],