diff --git a/modules/gems/objects.js b/modules/gems/objects.js index e1e208ae..df51f84a 100644 --- a/modules/gems/objects.js +++ b/modules/gems/objects.js @@ -1 +1,6 @@ export const EMPTY_OBJECT = Object.freeze({}); + +export function clone(object) { + return JSON.parse(JSON.stringify(object)); +} + diff --git a/modules/lstream/base.js b/modules/lstream/base.js index 3d25c250..6349a201 100644 --- a/modules/lstream/base.js +++ b/modules/lstream/base.js @@ -18,8 +18,11 @@ export class StreamBase { return new ScanStream(this, initAccumulator); } - remember() { - let stateStream = new StateStream(undefined); + remember(initialValue, usingStream) { + if (!usingStream) { + usingStream = StateStream; + } + let stateStream = new usingStream(initialValue); this.attach(v => stateStream.next(v)); return stateStream; } diff --git a/modules/lstream/disableable.js b/modules/lstream/disableable.js new file mode 100644 index 00000000..dfba6b18 --- /dev/null +++ b/modules/lstream/disableable.js @@ -0,0 +1,12 @@ +import {StateStream} from './state'; + +export class DisableableState extends StateStream { + + disabled = false; + + next(value) { + if (!this._disabled) { + super.next(value); + } + } +} \ No newline at end of file diff --git a/modules/renders/index.js b/modules/renders/index.js new file mode 100644 index 00000000..87251e9e --- /dev/null +++ b/modules/renders/index.js @@ -0,0 +1,16 @@ + +const renderXZY = (x, y, z) => `[${x}, ${y}, ${z}]`; + +export function renderPoint(point) { + if (arguments.length > 1) { + let [x, y, z] = arguments; + return renderXZY(x, y, z); + } else if (Array.isArray(point)) { + let [x, y, z] = point; + return renderXZY(x, y, z); + } else { + let {x, y, z} = point; + return renderXZY(x, y, z); + } +} + diff --git a/modules/scene/cameraControlRenderer.js b/modules/scene/cameraControlRenderer.js index a2601ef7..df67e668 100644 --- a/modules/scene/cameraControlRenderer.js +++ b/modules/scene/cameraControlRenderer.js @@ -7,7 +7,14 @@ import {AXIS} from '../../web/app/math/l3space'; export default function(container) { function createBasisArrow(axis, color) { - return new MeshArrow(axis, color, 1, 0.3, 0.15, 0.02); + return new MeshArrow({ + dir: axis, + color, + length: 1, + headLength: 0.3, + headWidth: 0.15, + lineWidth: 0.02 + }); } let xAxis = createBasisArrow(AXIS.X, 0xFF0000); diff --git a/modules/scene/objects/auxiliary.js b/modules/scene/objects/auxiliary.js index 0193111a..31e33916 100644 --- a/modules/scene/objects/auxiliary.js +++ b/modules/scene/objects/auxiliary.js @@ -1,6 +1,9 @@ import DPR from 'dpr'; import {ArrowHelper, CylinderBufferGeometry, Mesh, MeshBasicMaterial, Object3D, Vector3} from 'three'; import {createMeshLineGeometry} from './meshLine'; +import {Sphere} from 'three/src/math/Sphere'; +import {Matrix4} from 'three/src/math/Matrix4'; +import {Ray} from 'three/src/math/Ray'; export function createArrow(length, arrowLength, arrowHead, axis, color, opacity, materialMixins) { let arrow = new ArrowHelper(new Vector3().copy(axis), new Vector3(0, 0, 0), length, color, arrowLength, arrowHead); @@ -24,7 +27,7 @@ let lineGeometry = null; export class MeshArrow extends Object3D { - constructor(dir, color, length, headLength, headWidth, lineWidth) { + constructor({dir, color, length, headLength, headWidth, lineWidth, materialCreate, createHandle, handleMaterial}) { super(); if (color === undefined) color = 0xffff00; @@ -32,7 +35,8 @@ export class MeshArrow extends Object3D { if (headLength === undefined) headLength = 0.2 * length; if (headWidth === undefined) headWidth = 0.2 * headLength; if (lineWidth === undefined) lineWidth = 0.2 * headWidth; - + if (materialCreate === undefined) materialCreate = params => new MeshBasicMaterial(params); + if (!tipGeometry) { tipGeometry = new CylinderBufferGeometry(0, 0.5, 1, 5, 1); tipGeometry.translate(0, -0.5, 0); @@ -41,15 +45,21 @@ export class MeshArrow extends Object3D { // dir is assumed to be normalized - let cone = new Mesh(tipGeometry, new MeshBasicMaterial({color})); - let line = new Mesh(lineGeometry, new MeshBasicMaterial({color})); - - line.matrixAutoUpdate = false; + let cone = new Mesh(tipGeometry, materialCreate({color})); cone.matrixAutoUpdate = false; - - this.add(line); this.add(cone); + let line = new Mesh(lineGeometry, materialCreate({color})); + line.matrixAutoUpdate = false; + this.add(line); + + let handle = null; + if (createHandle) { + handle = new Mesh(lineGeometry, handleMaterial? handleMaterial() : new MeshBasicMaterial()); + handle.matrixAutoUpdate = false; + this.add(handle); + } + if (dir.y > 0.99999) { this.quaternion.set(0, 0, 0, 1); } else if (dir.y < -0.99999) { @@ -65,16 +75,25 @@ export class MeshArrow extends Object3D { line.scale.set(lineWidth, Math.max(0, length - headLength), lineWidth); line.updateMatrix(); + if (handle) { + handle.scale.set(lineWidth * 5, length, lineWidth * 5); + handle.updateMatrix(); + } + cone.scale.set(headWidth, headLength, headWidth); cone.position.y = length; cone.updateMatrix(); this.cone = cone; this.line = line; + this.handle = handle; } dispose() { this.cone.material.dispose(); this.line.material.dispose(); + if (this.handle) { + this.handle.material.dispose(); + } } } diff --git a/modules/scene/objects/primitiveObjects.js b/modules/scene/objects/primitiveObjects.js new file mode 100644 index 00000000..e16d0e51 --- /dev/null +++ b/modules/scene/objects/primitiveObjects.js @@ -0,0 +1,18 @@ +import {BoxGeometry, Mesh, SphereGeometry} from 'three'; + +export class SphereObject3D extends Mesh { + + constructor(material) { + super(sphereGeometry, material); + } +} + +export class BoxObject3D extends Mesh { + + constructor(material) { + super(boxGeometry, material); + } +} + +const sphereGeometry = new SphereGeometry( 1 ); +const boxGeometry = new BoxGeometry( 1, 1, 1 ); \ No newline at end of file diff --git a/modules/scene/objects/raycastabeArea.js b/modules/scene/objects/raycastabeArea.js new file mode 100644 index 00000000..4c097194 --- /dev/null +++ b/modules/scene/objects/raycastabeArea.js @@ -0,0 +1,30 @@ +import {Object3D, Vector3} from 'three'; + +export default class RaycastableArea extends Object3D { + + constructor(getCenter, getRadius) { + super(); + this._vec = new Vector3(); + this.getCenter = getCenter; + this.getRadius = getRadius; + } + + raycast(raycaster, intersects ) { + //need to apply world matrix + let center = this.getCenter(); + let radius = this.getRadius(); + let ray = raycaster.ray; + let vec = this._vec; + let proj = vec.copy(center).subtract(ray.center).dot(ray.dir); + vec.copy(ray.dir).multiplyScalar(proj).add(ray.center); + + let distSq = vec.distanceToSquared(center); + if (distSq <= radius * this) { + intersects.push({ + distance: Math.sqrt(distSq), + point: vec.clone(), + object: this + }); + } + } +} \ No newline at end of file diff --git a/modules/scene/sceneGraph.js b/modules/scene/sceneGraph.js index cce8cc38..3125a4d0 100644 --- a/modules/scene/sceneGraph.js +++ b/modules/scene/sceneGraph.js @@ -20,5 +20,16 @@ export function clearGroup(group) { group.remove(o); } } - + +export function findAncestor( obj, predicate, includeItself ) { + let parent = includeItself ? obj : obj.parent; + if ( parent !== null ) { + if (predicate(parent)) { + return parent; + } else { + return findAncestor( parent, predicate, false ) + } + } + return null; +} diff --git a/modules/scene/sceneSetup.js b/modules/scene/sceneSetup.js index b5533e31..3dca4017 100644 --- a/modules/scene/sceneSetup.js +++ b/modules/scene/sceneSetup.js @@ -6,6 +6,7 @@ export default class SceneSetUp { constructor(container, onRendered) { + this.workingSphere = 10000; this.container = container; this.scene = new THREE.Scene(); this.rootGroup = this.scene; @@ -35,7 +36,7 @@ export default class SceneSetUp { } createPerspectiveCamera() { - this.pCamera = new THREE.PerspectiveCamera( 500*75, this.aspect(), 0.1, 10000 ); + this.pCamera = new THREE.PerspectiveCamera( 60, this.aspect(), 0.1, 10000 ); this.pCamera.position.z = 1000; this.pCamera.position.x = -1000; this.pCamera.position.y = 300; @@ -159,17 +160,35 @@ export default class SceneSetUp { }; } - raycast(event, group) { + createRaycaster(viewX, viewY) { let raycaster = new THREE.Raycaster(); raycaster.linePrecision = 12 * (this._zoomMeasure() * 0.8); - let x = ( event.offsetX / this.container.clientWidth ) * 2 - 1; - let y = - ( event.offsetY / this.container.clientHeight ) * 2 + 1; + let x = ( viewX / this.container.clientWidth ) * 2 - 1; + let y = - ( viewY / this.container.clientHeight ) * 2 + 1; let mouse = new THREE.Vector3( x, y, 1 ); raycaster.setFromCamera( mouse, this.camera ); - return raycaster.intersectObjects( group.children, true ); + return raycaster; + } + + raycast(event, objects) { + let raycaster = this.createRaycaster(event.offsetX, event.offsetY); + return raycaster.intersectObjects( objects, true ); } + modelToScreen(pos) { + let width = this.container.clientWidth, height = this.container.clientHeight; + let widthHalf = width / 2, heightHalf = height / 2; + + let vector = new THREE.Vector3(); + vector.copy(pos); + vector.project(this.camera); + + vector.x = ( vector.x * widthHalf ) + widthHalf; + vector.y = - ( vector.y * heightHalf ) + heightHalf; + return vector; + } + lookAt(obj) { let box = new THREE.Box3(); box.setFromObject(obj); diff --git a/modules/ui/components/controls/Button.less b/modules/ui/components/controls/Button.less index ae77c0a9..8c764b6a 100644 --- a/modules/ui/components/controls/Button.less +++ b/modules/ui/components/controls/Button.less @@ -19,4 +19,8 @@ .danger { .button-behavior(@color-danger) -} \ No newline at end of file +} + +.minor { + .button-behavior(@color-neutral) +} diff --git a/modules/ui/components/controls/CheckboxControl.jsx b/modules/ui/components/controls/CheckboxControl.jsx index 6aa83d8f..f510abdc 100644 --- a/modules/ui/components/controls/CheckboxControl.jsx +++ b/modules/ui/components/controls/CheckboxControl.jsx @@ -1,13 +1,11 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import InputControl from './InputControl'; export default class CheckboxControl extends React.Component { render() { - let {onChange, initValue} = this.props; + let {onChange, value} = this.props; return onChange(e.target.value)} /> } } diff --git a/modules/ui/components/controls/NumberControl.jsx b/modules/ui/components/controls/NumberControl.jsx index d104747b..4bfede66 100644 --- a/modules/ui/components/controls/NumberControl.jsx +++ b/modules/ui/components/controls/NumberControl.jsx @@ -5,10 +5,10 @@ import InputControl from './InputControl'; export default class NumberControl extends React.Component { render() { - let {onChange, initValue} = this.props; + let {onChange, value} = this.props; return this.input = input} /> } @@ -54,7 +54,7 @@ NumberControl.propTypes = { min: PropTypes.number, max: PropTypes.number, accelerator: PropTypes.number, - initValue: PropTypes.number.isRequired, + value: PropTypes.number.isRequired, onChange: PropTypes.func.isRequired }; diff --git a/modules/ui/components/controls/RadioButtons.jsx b/modules/ui/components/controls/RadioButtons.jsx index 74f5ae50..abaf1dde 100644 --- a/modules/ui/components/controls/RadioButtons.jsx +++ b/modules/ui/components/controls/RadioButtons.jsx @@ -12,7 +12,7 @@ export default class RadioButtons extends React.Component { getChildContext() { return { radioButtonsGroupName: this.groupId, - radioButtonsInitValue: this.props.initValue, + radioButtonsValue: this.props.value, radioButtonsOnChange: this.props.onChange } } @@ -20,13 +20,13 @@ export default class RadioButtons extends React.Component { groupId = 'group_' + GROUP_COUNTER++; } -export function RadioButton({value, label}, {radioButtonsGroupName, radioButtonsInitValue, radioButtonsOnChange}) { +export function RadioButton({value, label}, {radioButtonsGroupName, radioButtonsValue, radioButtonsOnChange}) { let onChange = e => { radioButtonsOnChange(e.target.value) }; label = label || value; return @@ -35,7 +35,7 @@ export function RadioButton({value, label}, {radioButtonsGroupName, radioButtons RadioButtons.childContextTypes = { radioButtonsGroupName: PropTypes.string.isRequired, - radioButtonsInitValue: PropTypes.string.isRequired, + radioButtonsValue: PropTypes.string.isRequired, radioButtonsOnChange: PropTypes.func.isRequired }; diff --git a/modules/ui/components/controls/ReadOnlyValueControl.jsx b/modules/ui/components/controls/ReadOnlyValueControl.jsx new file mode 100644 index 00000000..a5eba70f --- /dev/null +++ b/modules/ui/components/controls/ReadOnlyValueControl.jsx @@ -0,0 +1,9 @@ +import React from 'react'; + +export default class ReadOnlyValueControl extends React.Component { + + render() { + let {value} = this.props; + return {value}; + } +} diff --git a/web/app/brep/io/brepIO.js b/web/app/brep/io/brepIO.js index 5c568fed..0189eaf7 100644 --- a/web/app/brep/io/brepIO.js +++ b/web/app/brep/io/brepIO.js @@ -77,6 +77,9 @@ function readSurface(s, inverted, face) { function readCurve(curve) { switch (curve.TYPE) { + case 'B-SPLINE': + console.dir(curve); + case 'CONIC': //... case 'LINE': diff --git a/web/app/cad/craft/cadRegistryPlugin.js b/web/app/cad/craft/cadRegistryPlugin.js index 226115d0..098ea7a0 100644 --- a/web/app/cad/craft/cadRegistryPlugin.js +++ b/web/app/cad/craft/cadRegistryPlugin.js @@ -1,16 +1,18 @@ -import {EDGE, FACE, SKETCH_OBJECT} from '../scene/entites'; +import {DATUM, EDGE, FACE, SKETCH_OBJECT} from '../scene/entites'; +import {MShell} from '../model/mshell'; export function activate({streams, services}) { streams.cadRegistry = { - shellIndex: streams.craft.models.map(models => models.reduce((i, v)=> i.set(v.id, v), new Map())).remember() + shells: streams.craft.models.map(models => models.filter(m => m instanceof MShell)).remember(), + modelIndex: streams.craft.models.map(models => models.reduce((i, v)=> i.set(v.id, v), new Map())).remember() }; - streams.cadRegistry.update = streams.cadRegistry.shellIndex; + streams.cadRegistry.update = streams.cadRegistry.modelIndex; function getAllShells() { - return streams.craft.models.value; + return streams.cadRegistry.shells.value; } function findFace(faceId) { @@ -46,19 +48,27 @@ export function activate({streams, services}) { return null; } + function findDatum(datumId) { + return streams.cadRegistry.modelIndex.value.get(datumId)||null; + } + function findEntity(entity, id) { switch (entity) { case FACE: return findFace(id); case EDGE: return findEdge(id); case SKETCH_OBJECT: return findSketchObject(id); + case DATUM: return findDatum(id); default: throw 'unsupported'; } } services.cadRegistry = { - getAllShells, findFace, findEdge, findSketchObject, findEntity, - get shellIndex() { - return streams.cadRegistry.shellIndex.value; + getAllShells, findFace, findEdge, findSketchObject, findEntity, findDatum, + get modelIndex() { + return streams.cadRegistry.modelIndex.value; + }, + get models() { + return streams.craft.models.value; } } } diff --git a/web/app/cad/craft/craftHistoryUtils.js b/web/app/cad/craft/craftHistoryUtils.js index 50ff3551..c3b46a8d 100644 --- a/web/app/cad/craft/craftHistoryUtils.js +++ b/web/app/cad/craft/craftHistoryUtils.js @@ -19,11 +19,8 @@ export function addModification({history, pointer}, request) { } } -export function stepOverridingParams({history, pointer}, params) { - history[pointer + 1] = { - type: history[pointer + 1].type, - params - }; +export function stepOverriding({history, pointer}, request) { + history[pointer + 1] = request; return { history, pointer: ++pointer diff --git a/web/app/cad/craft/craftPlugin.js b/web/app/cad/craft/craftPlugin.js index 3ca66685..ccffcc85 100644 --- a/web/app/cad/craft/craftPlugin.js +++ b/web/app/cad/craft/craftPlugin.js @@ -1,6 +1,7 @@ -import {addModification} from './craftHistoryUtils'; +import {addModification, stepOverriding} from './craftHistoryUtils'; import {state, stream} from 'lstream'; import {MShell} from '../model/mshell'; +import {MDatum} from '../model/mdatum'; export function activate({streams, services}) { @@ -13,10 +14,33 @@ export function activate({streams, services}) { update: stream() }; - function modify(request) { - streams.craft.modifications.update(modifications => addModification(modifications, request)); + let preRun = null; + + function modifyWithPreRun(request, modificationsUpdater, onAccepted) { + preRun = { + request + }; + try { + preRun.result = runRequest(request); + if (onAccepted) { + onAccepted(); + } + modificationsUpdater(request); + } finally { + preRun = null; + } } + function modify(request, onAccepted) { + modifyWithPreRun(request, + request => streams.craft.modifications.update(modifications => addModification(modifications, request)), onAccepted); + } + + function modifyInHistoryAndStep(request, onAccepted) { + modifyWithPreRun(request, + request => streams.craft.modifications.update(modifications => stepOverriding(modifications, request)), onAccepted); + } + function reset(modifications) { streams.craft.modifications.next({ history: modifications, @@ -24,8 +48,25 @@ export function activate({streams, services}) { }); } + function runRequest(request) { + let op = services.operation.get(request.type); + if (!op) { + throw(`unknown operation ${request.type}`); + } + + return op.run(request.params, services); + } + + function runOrGetPreRunResults(request) { + if (preRun !== null && preRun.request === request) { + return preRun.result; + } else { + return runRequest(request); + } + } + services.craft = { - modify, reset + modify, modifyInHistoryAndStep, reset, runRequest }; streams.craft.modifications.pairwise().attach(([prev, curr]) => { @@ -35,6 +76,7 @@ export function activate({streams, services}) { beginIndex = prev.pointer + 1; } else { MShell.ID_COUNTER = 0; + MDatum.ID_COUNTER = 0; beginIndex = 0; streams.craft.models.next([]); } @@ -43,14 +85,7 @@ export function activate({streams, services}) { let {history, pointer} = curr; for (let i = beginIndex; i <= pointer; i++) { let request = history[i]; - - let op = services.operation.get(request.type); - if (!op) { - console.log(`unknown operation ${request.type}`); - } - - let {outdated, created} = op.run(request.params, services); - + let {outdated, created} = runOrGetPreRunResults(request); outdated.forEach(m => models.delete(m)); created.forEach(m => models.add(m)); streams.craft.models.next(Array.from(models).sort(m => m.id)); diff --git a/web/app/cad/craft/datum/create/CreateDatumWizard.jsx b/web/app/cad/craft/datum/create/CreateDatumWizard.jsx new file mode 100644 index 00000000..7730ba4f --- /dev/null +++ b/web/app/cad/craft/datum/create/CreateDatumWizard.jsx @@ -0,0 +1,13 @@ +import React from 'react'; +import {Group} from '../../wizard/components/form/Form'; +import {NumberField} from '../../wizard/components/form/Fields'; +import SingleEntity from '../../wizard/components/form/SingleEntity'; + +export default function CreateDatumWizard() { + return + + + + + ; +} \ No newline at end of file diff --git a/web/app/cad/craft/datum/create/createDatumOpSchema.js b/web/app/cad/craft/datum/create/createDatumOpSchema.js new file mode 100644 index 00000000..fc740e09 --- /dev/null +++ b/web/app/cad/craft/datum/create/createDatumOpSchema.js @@ -0,0 +1,19 @@ +export default { + originatingFace: { + type: 'face', + defaultValue: {type: 'selection'}, + optional: true + }, + x: { + type: 'number', + defaultValue: 0 + }, + y: { + type: 'number', + defaultValue: 0 + }, + z: { + type: 'number', + defaultValue: 0 + }, +} \ No newline at end of file diff --git a/web/app/cad/craft/datum/create/createDatumOperation.js b/web/app/cad/craft/datum/create/createDatumOperation.js new file mode 100644 index 00000000..5fa5cf50 --- /dev/null +++ b/web/app/cad/craft/datum/create/createDatumOperation.js @@ -0,0 +1,84 @@ +import DatumWizard from './CreateDatumWizard'; +import schema from './createDatumOpSchema'; +import {renderPoint} from 'renders'; +import DatumObject3D from '../datumObject'; +import * as SceneGraph from 'scene/sceneGraph'; +import CSys from '../../../../math/csys'; +import {MDatum} from '../../../model/mdatum'; + +function updateCSys(csys, params, findFace) { + csys.move(0, 0, 0); + if (params.face) { + const face = findFace(params.face); + if (face) { + csys.copy(face.csys); + } + } + + csys.origin.x += params.x; + csys.origin.y += params.y; + csys.origin.z += params.z; +} + +function create(params, {cadRegistry}) { + let csys = CSys.origin(); + updateCSys(csys, params, cadRegistry.findFace); + + return { + outdated: [], + created: [new MDatum(csys)] + } +} + +function previewer(ctx, initialParams, updateParams) { + + let datum3D = new DatumObject3D(CSys.origin(), ctx.services.viewer); + + datum3D.onMove = (begin, end, delta) => { + updateParams(params => { + + params.x = end.x; + params.y = end.y; + params.z = end.z; + if (params.face) { + let face = ctx.services.cadRegistry.findFace(params.face); + if (face) { + params.x -= face.csys.origin.x; + params.y -= face.csys.origin.y; + params.z -= face.csys.origin.z; + } + } + }) + }; + + function update(params) { + updateCSys(datum3D.csys, params, ctx.services.cadRegistry.findFace); + } + + function dispose() { + SceneGraph.removeFromGroup(ctx.services.cadScene.workGroup, datum3D); + datum3D.dispose(); + } + + update(initialParams); + SceneGraph.addToGroup(ctx.services.cadScene.workGroup, datum3D); + + return { + update, dispose + } +} + +export default { + id: 'DATUM_CREATE', + label: 'Create Datum', + icon: 'img/cad/plane', + info: 'originates a new datum from origin or off of a selected face', + paramsInfo: renderPoint, + previewer, + run: create, + form: DatumWizard, + schema +}; + + + diff --git a/web/app/cad/craft/datum/csysObject.js b/web/app/cad/craft/datum/csysObject.js new file mode 100644 index 00000000..470a421b --- /dev/null +++ b/web/app/cad/craft/datum/csysObject.js @@ -0,0 +1,84 @@ +import { + Geometry, Line, LineBasicMaterial, MeshBasicMaterial, MeshLambertMaterial, Object3D, Quaternion, + Vector3 +} from 'three'; +import {AXIS} from '../../../math/l3space'; +import {MeshArrow} from 'scene/objects/auxiliary'; +import {OnTopOfAll} from 'scene/materialMixins'; + +export default class CSysObject3D extends Object3D { + + constructor(csys, sceneSetup, arrowParams) { + super(); + + this.csys = csys; + this.sceneSetup = sceneSetup; + + function createBasisArrow(axis, color) { + let meshArrow = new MeshArrow({ + dir: axis, + color, + length: CSYS_SIZE_MODEL, + headLength: 30, + headWidth: 15, + lineWidth: 2, + materialCreate: p => new MeshLambertMaterial(p), + ...arrowParams + }); + return meshArrow; + } + + this.xAxis = createBasisArrow(AXIS.X, 0xFF0000); + this.yAxis = createBasisArrow(AXIS.Y, 0x00FF00); + this.zAxis = createBasisArrow(AXIS.Z, 0x0000FF); + + this.add(this.xAxis); + this.add(this.yAxis); + this.add(this.zAxis); + } + + updateMatrix() { + let {origin: o, x, y, z} = this.csys; + + let k = this.viewScaleFactor(); + this.matrix.set( + k*x.x, k*y.x, k*z.x, o.x, + k*x.y, k*y.y, k*z.y, o.y, + k*x.z, k*y.z, k*z.z, o.z, + 0, 0, 0, 1 + ); + + // this.scale.set(k, k, k); + // super.updateMatrix(); + } + + viewScaleFactor() { + let container = this.sceneSetup.container; + let viewHeight = container.clientHeight; + let camera = this.sceneSetup.camera; + + if (camera.isOrthographicCamera) { + return viewHeight / (camera.top - camera.bottom) / camera.zoom * 2; + } else { + let p = new Vector3().copy(this.csys.origin); + let cp = new Vector3().copy(camera.position); + let z = p.sub(cp).length(); + let tanHFov = Math.atan((camera.fov / 2) / 180 * Math.PI); + let fitUnits = tanHFov * z * 2; + + let modelTakingPart = CSYS_SIZE_MODEL / fitUnits; + let modelActualSizePx = viewHeight * modelTakingPart; + return SIZE_PX / modelActualSizePx; + } + } + + dispose() { + this.xAxis.dispose(); + this.yAxis.dispose(); + this.zAxis.dispose(); + } +} + +export const CSYS_SIZE_MODEL = 100; + +const SIZE_PX = 50; diff --git a/web/app/cad/craft/datum/datumObject.js b/web/app/cad/craft/datum/datumObject.js new file mode 100644 index 00000000..e616cf4b --- /dev/null +++ b/web/app/cad/craft/datum/datumObject.js @@ -0,0 +1,148 @@ +import {Geometry, Line, LineBasicMaterial, MeshBasicMaterial, Object3D, Vector3} from 'three'; + + +import CSysObject3D from './csysObject'; +import {NOOP} from 'gems/func'; +import {findAncestor} from '../../../../../modules/scene/sceneGraph'; + +export default class DatumObject3D extends Object3D { + + static AXIS = { + X: 1, + Y: 2, + Z: 3, + }; + + constructor(csys, viewer) { + super(); + this.viewer = viewer; + this.csys = csys.clone(); + this.csysObj = new CSysObject3D(this.csys, this.viewer.sceneSetup, { + createHandle: true, + handleMaterial: () => new MeshBasicMaterial({ + transparent: true, + opacity: 0.5, + color: 0xAA8439, + visible: false + }) + }); + addOnHoverBehaviour(this.csysObj.xAxis.handle, this.viewer); + addOnHoverBehaviour(this.csysObj.yAxis.handle, this.viewer); + addOnHoverBehaviour(this.csysObj.zAxis.handle, this.viewer); + this.add(this.csysObj); + this.exitEditMode = NOOP; + this.beingDraggedAxis = null; + } + + setMoveMode(axis) { + this.exitEditMode(); + let dir, color; + if (axis === DatumObject3D.AXIS.X) { + dir = this.csys.x; + color = 0xff0000; + } else if (axis === DatumObject3D.AXIS.Y) { + dir = this.csys.y; + color = 0x00ff00; + } else if (axis === DatumObject3D.AXIS.Z) { + dir = this.csys.z; + color = 0x0000ff; + } else { + return; + } + + this.beingDraggedAxis = dir; + + let ext = dir.multiply(this.viewer.sceneSetup.workingSphere); + + const material = new LineBasicMaterial({color}); + let geometry = new Geometry(); + + geometry.vertices.push(new Vector3().copy(this.csys.origin.minus(ext))); + geometry.vertices.push(new Vector3().copy(this.csys.origin.plus(ext))); + + let line = new Line(geometry, material); + this.add(line); + + this.exitEditMode = () => { + this.beingDraggedAxis = null; + this.remove(line); + geometry.dispose(); + material.dispose(); + this.exitEditMode = NOOP; + } + } + + dragStart(e, axis) { + this.dragInfo = { + csysOrigin: this.csys.origin.copy(), + originViewCoord: this.viewer.sceneSetup.modelToScreen(this.csys.origin), + startX: e.offsetX, + startY: e.offsetY, + }; + switch (axis) { + case this.csysObj.xAxis: + this.setMoveMode(DatumObject3D.AXIS.X); + break; + case this.csysObj.yAxis: + this.setMoveMode(DatumObject3D.AXIS.Y); + break; + case this.csysObj.zAxis: + default: + this.setMoveMode(DatumObject3D.AXIS.Z); + break; + } + } + + dragMove(e) { + if (this.beingDraggedAxis) { + let dir = this.beingDraggedAxis; + + let traveledX = e.offsetX - this.dragInfo.startX; + let traveledY = e.offsetY - this.dragInfo.startY; + + let raycaster = this.viewer.sceneSetup.createRaycaster(this.dragInfo.originViewCoord.x + traveledX, this.dragInfo.originViewCoord.y + traveledY); + + this.csys.origin.setV(this.dragInfo.csysOrigin); + + //see nurbs-ext - rays intersection + let zRef = dir.cross(raycaster.ray.direction); + + let n2 = zRef.cross(raycaster.ray.direction)._normalize(); + + let u = n2.dot(this.csys.origin.minus(raycaster.ray.origin)._negate()) / n2.dot(dir); + + let delta = dir.multiply(u); + this.csys.origin._plus(delta); + this.viewer.requestRender(); + + this.onMove(this.dragInfo.csysOrigin, this.csys.origin, delta); + } + } + + onMove(begin, end, delta) { + } + + dragDrop(e) { + this.exitEditMode(); + this.viewer.requestRender(); + } + + dispose() { + this.exitEditMode(); + this.csysObj.dispose(); + } +} + +function addOnHoverBehaviour(handle, viewer) { + handle.onMouseDown = function(e, hits, startDrag) { + let datum = this.parent.parent.parent; + startDrag(datum); + datum.dragStart(e, this.parent); + }; + handle.onMouseEnter = function() { + viewer.setVisualProp(handle.material, 'visible', true); + }; + handle.onMouseLeave = function() { + viewer.setVisualProp(handle.material, 'visible', false); + }; +} \ No newline at end of file diff --git a/web/app/cad/craft/datum/move/MoveDatumWizard.jsx b/web/app/cad/craft/datum/move/MoveDatumWizard.jsx new file mode 100644 index 00000000..eb1fe1b2 --- /dev/null +++ b/web/app/cad/craft/datum/move/MoveDatumWizard.jsx @@ -0,0 +1,14 @@ +import React from 'react'; +import {Group} from '../../wizard/components/form/Form'; +import {CheckboxField, NumberField, ReadOnlyValueField} from '../../wizard/components/form/Fields'; +import ReadOnlyValueControl from 'ui/components/controls/ReadOnlyValueControl'; + +export default function MoveDatumWizard() { + return + + + + + + ; +} \ No newline at end of file diff --git a/web/app/cad/craft/datum/move/moveDatumOpSchema.js b/web/app/cad/craft/datum/move/moveDatumOpSchema.js new file mode 100644 index 00000000..6b212ee8 --- /dev/null +++ b/web/app/cad/craft/datum/move/moveDatumOpSchema.js @@ -0,0 +1,21 @@ +export default { + datum: { + type: 'datum' + }, + x: { + type: 'number', + defaultValue: 0 + }, + y: { + type: 'number', + defaultValue: 0 + }, + z: { + type: 'number', + defaultValue: 0 + }, + copy: { + type: 'boolean', + defaultValue: false + } +} \ No newline at end of file diff --git a/web/app/cad/craft/datum/move/moveDatumOperation.js b/web/app/cad/craft/datum/move/moveDatumOperation.js new file mode 100644 index 00000000..188268c4 --- /dev/null +++ b/web/app/cad/craft/datum/move/moveDatumOperation.js @@ -0,0 +1,80 @@ +import schema from './moveDatumOpSchema'; +import {renderPoint} from 'renders'; +import {MDatum} from '../../../model/mdatum'; +import MoveDatumWizard from './MoveDatumWizard'; +import {NOOP} from 'gems/func'; + + +function move(params, {cadRegistry}) { + + let mDatum = cadRegistry.findDatum(params.datum); + + let csys = mDatum.csys.clone(); + csys.origin.x += params.x; + csys.origin.y += params.y; + csys.origin.z += params.z; + + return { + outdated: [mDatum], + created: [new MDatum(csys)] + } +} + +function previewer(ctx, initialParams, updateParams) { + + let mDatum = ctx.services.cadRegistry.findDatum(initialParams.datum); + if (!mDatum) { + return null; + } + let view = mDatum.ext.view; + if (!view) { + return null; + } + + let datum3D = view.rootGroup; + datum3D.beginOperation(); + datum3D.onMove = (begin, end, delta) => { + updateParams(params => { + params.x = end.x - mDatum.csys.origin.x; + params.y = end.y - mDatum.csys.origin.y; + params.z = end.z - mDatum.csys.origin.z; + }) + }; + + function update(params) { + datum3D.csys.origin.setV(mDatum.csys.origin); + datum3D.csys.origin.x += params.x; + datum3D.csys.origin.y += params.y; + datum3D.csys.origin.z += params.z; + } + + function dispose() { + datum3D.csys.copy(mDatum.csys); + datum3D.finishOperation(); + datum3D.operationStarted = false; + datum3D.exitEditMode(); + datum3D.applyMove = NOOP; + } + + + update(initialParams); + + return { + update, dispose + } +} + +export default { + id: 'DATUM_MOVE', + label: 'Move Datum', + icon: 'img/cad/plane', + info: 'moves a datum', + paramsInfo: renderPoint, + previewer, + run: move, + form: MoveDatumWizard, + schema +}; + + + diff --git a/web/app/cad/craft/operationPlugin.js b/web/app/cad/craft/operationPlugin.js index 1f57125a..b7f0241a 100644 --- a/web/app/cad/craft/operationPlugin.js +++ b/web/app/cad/craft/operationPlugin.js @@ -20,7 +20,7 @@ export function activate(context) { let opAction = { id: id, appearance, - invoke: () => services.wizard.open({type: id}), + invoke: () => services.wizard.open(id), ...actionParams }; actions.push(opAction); diff --git a/web/app/cad/craft/ui/HistoryTimeline.jsx b/web/app/cad/craft/ui/HistoryTimeline.jsx index 4f98efb7..6293fc44 100644 --- a/web/app/cad/craft/ui/HistoryTimeline.jsx +++ b/web/app/cad/craft/ui/HistoryTimeline.jsx @@ -12,11 +12,11 @@ import {combine} from 'lstream'; import {EMPTY_OBJECT} from '../../../../../modules/gems/objects'; import {aboveElement} from '../../../../../modules/ui/positionUtils'; -@connect(streams => combine(streams.craft.modifications, streams.operation.registry, streams.wizard) - .map(([modifications, operationRegistry, wizard]) => ({ +@connect(streams => combine(streams.craft.modifications, streams.operation.registry, streams.wizard.insertOperation) + .map(([modifications, operationRegistry, insertOperationReq]) => ({ ...modifications, operationRegistry, - inProgressOperation: wizard&&wizard.type, + inProgressOperation: insertOperationReq.type, getOperation: type => operationRegistry[type]||EMPTY_OBJECT }))) @mapContext(({streams}) => ({ diff --git a/web/app/cad/craft/ui/ObjectExplorer.jsx b/web/app/cad/craft/ui/ObjectExplorer.jsx index d8a0202a..117bd363 100644 --- a/web/app/cad/craft/ui/ObjectExplorer.jsx +++ b/web/app/cad/craft/ui/ObjectExplorer.jsx @@ -5,11 +5,13 @@ import Fa from '../../../../../modules/ui/components/Fa'; import {constant} from '../../../../../modules/lstream'; import ls from './ObjectExplorer.less'; import cx from 'classnames'; +import {MShell} from '../../model/mshell'; +import {MDatum} from '../../model/mdatum'; export default connect(streams => streams.craft.models.map(models => ({models}))) (function ObjectExplorer({models}) { - return models.map(m => + return models.map(m => (m instanceof MShell) ?
{ m.faces.map(f => @@ -23,7 +25,7 @@ export default connect(streams => streams.craft.models.map(models => ({models})) {m.edges.map(e => )}
-
); +
: (m instanceof MDatum) ? : null); }); diff --git a/web/app/cad/craft/wizard/components/HistoryWizard.jsx b/web/app/cad/craft/wizard/components/HistoryWizard.jsx deleted file mode 100644 index 33e4d27a..00000000 --- a/web/app/cad/craft/wizard/components/HistoryWizard.jsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react'; -import connect from 'ui/connect'; -import Wizard from './Wizard'; -import {finishHistoryEditing, stepOverridingParams} from '../../craftHistoryUtils'; -import {NOOP} from 'gems/func'; -import decoratorChain from 'ui/decoratorChain'; -import mapContext from 'ui/mapContext'; - -function HistoryWizard({history, pointer, step, cancel, offset, getOperation, previewerCreator, createValidator}) { - - -} - -export default decoratorChain( - connect(streams => streams.craft.modifications), - mapContext(({streams, services}) => ({ - step: params => streams.craft.modifications.update(modifications => stepOverridingParams(modifications, params)), - cancel: () => streams.craft.modifications.update(modifications => finishHistoryEditing(modifications)), - })) -)(HistoryWizard); - diff --git a/web/app/cad/craft/wizard/components/Wizard.jsx b/web/app/cad/craft/wizard/components/Wizard.jsx index 647b6cae..9a828cff 100644 --- a/web/app/cad/craft/wizard/components/Wizard.jsx +++ b/web/app/cad/craft/wizard/components/Wizard.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import PropTypes from 'prop-types'; import Window from 'ui/components/Window'; import Stack from 'ui/components/Stack'; import Button from 'ui/components/controls/Button'; @@ -7,63 +6,65 @@ import ButtonGroup from 'ui/components/controls/ButtonGroup'; import ls from './Wizard.less'; import CadError from '../../../../utils/errors'; -import {createPreviewer} from '../../../preview/scenePreviewer'; import {FormContext} from './form/Form'; +import connect from 'ui/connect'; +import mapContext from 'ui/mapContext'; -export default class Wizard extends React.PureComponent { +@connect(streams => streams.wizard.workingRequest) +@mapContext(ctx => ({ + updateParam: (name, value) => { + let workingRequest$ = ctx.streams.wizard.workingRequest; + if (workingRequest$.value.params && workingRequest$.value.type) { + workingRequest$.mutate(data => data.params[name] = value) + } + } +})) +export default class Wizard extends React.Component { state = { hasError: false, validationErrors: [], }; - updatePreview() { - if (this.previewer) { - this.previewer.update(this.props.params); - } - } - - componentDidMount() { - this.previewer = this.props.createPreviewer(); - this.updatePreview(); - } - - componentDidUpdate() { - this.previewer.dispose(); - this.previewer = this.props.createPreviewer(); - this.updatePreview(); - } - - componentWillUnmount() { - this.previewer.dispose() - } componentDidCatch() { this.setState({hasInternalError: true}); } - + render() { if (this.state.hasInternalError) { return operation error; } - let {left, type} = this.props; + + let {left, type, params, resolveOperation, updateParam} = this.props; + if (!type) { + return null; + } + + let operation = resolveOperation(type); + if (!operation) { + console.error('unknown operation ' + type); + return null; + } + + let title = (operation.label || type).toUpperCase(); let formContext = { - data: this.props.params, + data: params, validationErrors: this.state.validationErrors, - onChange: () => this.updatePreview() + updateParam, }; - let Form = this.props.form; + let Form = operation.form; return -
+ @@ -104,25 +105,23 @@ export default class Wizard extends React.PureComponent { }; cancel = () => { - if (this.props.onCancel) { - this.props.onCancel(); - } - this.props.close(); + this.props.onCancel(); }; onOK = () => { try { - let validationErrors = this.props.validate(this.props.params); + let {type, params, resolveOperation, validator} = this.props; + if (!type) { + return null; + } + + let operation = resolveOperation(type); + let validationErrors = validator(params, operation.schema); if (validationErrors.length !== 0) { - this.setState({validationErrors}) + this.setState({validationErrors}); return; } - if (this.props.onOK) { - this.props.onOK(this.props.params); - } else { - this.context.services.craft.modify({type: this.props.type, params: this.props.params}); - } - this.props.close(); + this.props.onOK(); } catch (error) { this.handleError(error); } @@ -146,11 +145,6 @@ export default class Wizard extends React.PureComponent { throw error; } } - - - static contextTypes = { - services: PropTypes.object - }; } diff --git a/web/app/cad/craft/wizard/components/WizardManager.jsx b/web/app/cad/craft/wizard/components/WizardManager.jsx index 1bfd284f..f6aa720a 100644 --- a/web/app/cad/craft/wizard/components/WizardManager.jsx +++ b/web/app/cad/craft/wizard/components/WizardManager.jsx @@ -1,77 +1,31 @@ import React from 'react'; import Wizard from './Wizard'; -import HistoryWizard from './HistoryWizard'; -import connect from '../../../../../../modules/ui/connect'; -import decoratorChain from '../../../../../../modules/ui/decoratorChain'; -import mapContext from '../../../../../../modules/ui/mapContext'; -import {finishHistoryEditing, stepOverridingParams} from '../../craftHistoryUtils'; -import {createPreviewer} from '../../../preview/scenePreviewer'; -import errorBoundary from 'ui/errorBoundary'; -import initializeBySchema from '../../intializeBySchema'; +import connect from 'ui/connect'; +import decoratorChain from 'ui/decoratorChain'; +import mapContext from 'ui/mapContext'; +import {finishHistoryEditing, stepOverriding} from '../../craftHistoryUtils'; import validateParams from '../../validateParams'; -import {combine} from 'lstream'; -import {NOOP} from '../../../../../../modules/gems/func'; +import {NOOP} from 'gems/func'; +import {clone} from 'gems/objects'; -class WizardManager extends React.Component { - - render() { - - let {insertOperation, registry, close, history, pointer, stepHistory, cancelHistoryEdit, - previewerCreator, createValidator, initializeOperation} = this.props; - - if (insertOperation) { - let operation = registry[insertOperation]; - if (!operation) { - throw 'unknown operation ' + type; - } - - let params = initializeOperation(operation); - return - - } else { - - if (pointer === history.length - 1) { - return null; - } - - let {type, params} = history[pointer + 1]; - let operation = registry[type]; - if (!operation) { - throw 'unknown operation ' + type; - } - - return - } +function WizardManager({type, changingHistory, resolve, cancel, stepHistory, insertOperation, cancelHistoryEdit, applyWorkingRequest, validator}) { + if (!type) { + return null; } + return } export default decoratorChain( - connect(streams => combine( - streams.wizard, - streams.craft.modifications, - streams.operation.registry, - ).map(([w, {pointer, history}, registry]) => ({insertOperation: w&&w.type, pointer, registry, history}))), - mapContext(ctx => ({ - close: ctx.services.wizard.close, - initializeOperation: operation => initializeBySchema(operation.schema, ctx), - previewerCreator: operation => () => createPreviewer(operation.previewGeomProvider, ctx.services), - createValidator: operatation => params => validateParams(ctx.services, params, operatation.schema), - stepHistory: params => ctx.streams.craft.modifications.update(modifications => stepOverridingParams(modifications, params)), + connect(streams => streams.wizard.effectiveOperation), + mapContext((ctx, props) => ({ + cancel: ctx.services.wizard.cancel, + validator: (params, schema) => validateParams(ctx.services, params, schema), + resolve: type => ctx.services.operation.get(type), cancelHistoryEdit: () => ctx.streams.craft.modifications.update(modifications => finishHistoryEditing(modifications)), + applyWorkingRequest: ctx.services.wizard.applyWorkingRequest })) ) (WizardManager); - -function clone(params) { - return JSON.parse(JSON.stringify(params)); -} \ No newline at end of file diff --git a/web/app/cad/craft/wizard/components/form/Fields.jsx b/web/app/cad/craft/wizard/components/form/Fields.jsx index 7afa69cd..8f31ec55 100644 --- a/web/app/cad/craft/wizard/components/form/Fields.jsx +++ b/web/app/cad/craft/wizard/components/form/Fields.jsx @@ -4,8 +4,10 @@ import NumberControl from 'ui/components/controls/NumberControl'; import TextControl from 'ui/components/controls/TextControl'; import RadioButtons from 'ui/components/controls/RadioButtons'; import CheckboxControl from 'ui/components/controls/CheckboxControl'; +import ReadOnlyValueControl from 'ui/components/controls/ReadOnlyValueControl'; export const NumberField = attachToForm(formField(NumberControl)); export const TextField = attachToForm(formField(TextControl)); export const RadioButtonsField = attachToForm(formField(RadioButtons)); export const CheckboxField = attachToForm(formField(CheckboxControl)); +export const ReadOnlyValueField = attachToForm(formField(ReadOnlyValueControl)); diff --git a/web/app/cad/craft/wizard/components/form/Form.jsx b/web/app/cad/craft/wizard/components/form/Form.jsx index feb0960b..ddf911e6 100644 --- a/web/app/cad/craft/wizard/components/form/Form.jsx +++ b/web/app/cad/craft/wizard/components/form/Form.jsx @@ -22,17 +22,13 @@ export function formField(Control) { } export function attachToForm(Control) { - return function FormField({name, label, ...props}) { + return function FormField({name, ...props}) { return { ctx => { - const onChange = val => { - ctx.data[name] = val; - ctx.onChange(); - }; - let initValue = ctx.data[name]; + const onChange = val => ctx.updateParam(name, val); return - + ; } } diff --git a/web/app/cad/craft/wizard/components/form/MultiEntity.jsx b/web/app/cad/craft/wizard/components/form/MultiEntity.jsx index 6b93ab41..c8d64284 100644 --- a/web/app/cad/craft/wizard/components/form/MultiEntity.jsx +++ b/web/app/cad/craft/wizard/components/form/MultiEntity.jsx @@ -10,30 +10,24 @@ import initializeBySchema from '../../../intializeBySchema'; @mapContext(({streams}) => ({streams})) export default class MultiEntity extends React.Component { - constructor({initValue}) { - super(); - this.state = { - value: initValue - }; - } - selectionChanged = selection => { let {itemField, schema, context} = this.props; let value = selection.map(id => { - let item = this.state.value.find(i => i[itemField] === id); + let item = this.props.value.find(i => i[itemField] === id); if (!item) { item = initializeBySchema(schema, context); item[itemField] = id; } return item; }); - this.setState({value}); this.props.onChange(value); }; componentDidMount() { let {streams, entity} = this.props; - this.detacher = streams.selection[entity].attach(this.selectionChanged); + let selection$ = streams.selection[entity]; + this.selectionChanged(selection$.value); + this.detacher = selection$.attach(this.selectionChanged); } componentWillUnmount() { @@ -44,7 +38,7 @@ export default class MultiEntity extends React.Component { return { - ({onChange}) => this.state.value.map(data => { + ({onChange}) => this.props.value.map(data => { let subContext = { data, onChange diff --git a/web/app/cad/craft/wizard/components/form/SingleEntity.jsx b/web/app/cad/craft/wizard/components/form/SingleEntity.jsx index 59cc53dd..4ecfefb2 100644 --- a/web/app/cad/craft/wizard/components/form/SingleEntity.jsx +++ b/web/app/cad/craft/wizard/components/form/SingleEntity.jsx @@ -1,40 +1,46 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import {attachToForm, formFieldDecorator} from './Form'; import mapContext from 'ui/mapContext'; +import ls from './SingleEntity.less'; +import Label from 'ui/components/controls/Label'; +import Field from 'ui/components/controls/Field'; +import Fa from 'ui/components/Fa'; +import Button from 'ui/components/controls/Button'; +import {attachToForm} from './Form'; import {camelCaseSplitToStr} from 'gems/camelCaseSplit'; @attachToForm -@mapContext(({streams}) => ({streams})) +@mapContext(({streams, services}) => ({ + streams, + findEntity: services.cadRegistry.findEntity +})) export default class SingleEntity extends React.Component { - constructor({initValue}) { - super(); - this.state = { - selectedItem: initValue - } - } - - selectionChanged = selection => { - let selectedItem = selection[0]; - if (selectedItem) { - this.setState({selectedItem}); - this.props.onChange(selectedItem); - } - }; - componentDidMount() { - let {streams, entity} = this.props; - this.detacher = streams.selection[entity].attach(this.selectionChanged); + let {streams, entity, onChange, value, findEntity} = this.props; + let selection$ = streams.selection[entity]; + if (findEntity(entity, value)) { + selection$.next([value]); + } + this.detacher = selection$.attach(selection => onChange(selection[0])); } componentWillUnmount() { this.detacher(); } - + + deselect = () => { + let {streams, entity} = this.props; + streams.selection[entity].next([]); + }; + render() { - return
- {camelCaseSplitToStr(this.props.name)}: {this.state.selectedItem} -
; + let {name, label, streams, entity} = this.props; + let selection = streams.selection[entity].value[0]; + return + +
{selection ? + {selection} : + {''}}
+
; } } diff --git a/web/app/cad/craft/wizard/components/form/SingleEntity.less b/web/app/cad/craft/wizard/components/form/SingleEntity.less new file mode 100644 index 00000000..0a8c1207 --- /dev/null +++ b/web/app/cad/craft/wizard/components/form/SingleEntity.less @@ -0,0 +1,4 @@ +.emptySelection { + font-style: initial; + color: #BFBFBF; +} \ No newline at end of file diff --git a/web/app/cad/craft/wizard/wizardPlugin.js b/web/app/cad/craft/wizard/wizardPlugin.js index f66b5fe2..2c751303 100644 --- a/web/app/cad/craft/wizard/wizardPlugin.js +++ b/web/app/cad/craft/wizard/wizardPlugin.js @@ -1,23 +1,95 @@ -import {state} from '../../../../../modules/lstream'; +import {state} from 'lstream'; +import initializeBySchema from '../intializeBySchema'; +import {clone, EMPTY_OBJECT} from 'gems/objects'; -export function activate({streams, services}) { +export function activate(ctx) { - streams.wizard = state(null); + let {streams, services} = ctx; + + streams.wizard = {}; - services.wizard = { - - open: ({type}) => { + streams.wizard.insertOperation = state(EMPTY_OBJECT); - let wizard = { - type + streams.wizard.effectiveOperation = state(EMPTY_OBJECT); + + streams.wizard.insertOperation.attach(insertOperationReq => { + if (insertOperationReq.type) { + let type = insertOperationReq.type; + let operation = ctx.services.operation.get(type); + streams.wizard.effectiveOperation.value = { + type: operation.id, + initialOverrides: insertOperationReq.initialOverrides, + changingHistory: false }; + } + }); + + function gotoEditHistoryModeIfNeeded({pointer, history}) { + if (pointer !== history.length - 1) { + let {type, params} = history[pointer + 1]; + streams.wizard.effectiveOperation.value = { + type, + params, + changingHistory: true + }; + } else { + streams.wizard.effectiveOperation.value = EMPTY_OBJECT; + } - streams.wizard.value = wizard; + } + + streams.craft.modifications.attach(mod => { + if (streams.wizard.insertOperation.value.type) { + return; + } + gotoEditHistoryModeIfNeeded(mod); + }); + + streams.wizard.workingRequest = streams.wizard.effectiveOperation.map(opRequest => { + if (!opRequest.type) { + return EMPTY_OBJECT; + } + let operation = ctx.services.operation.get(opRequest.type); + let params; + if (opRequest.changingHistory) { + params = clone(opRequest.params) + } else { + params = initializeBySchema(operation.schema, ctx); + if (opRequest.initialOverrides) { + applyOverrides(params, opRequest.initialOverrides); + } + } + return { + type: opRequest.type, + params, + } + }).remember(EMPTY_OBJECT); + + services.wizard = { + + open: (type, initialOverrides) => { + streams.wizard.insertOperation.value = { + type, + initialOverrides + }; + }, + + cancel: () => { + streams.wizard.insertOperation.value = EMPTY_OBJECT; + gotoEditHistoryModeIfNeeded(streams.craft.modifications.value); }, - close: () => { - streams.wizard.value = null; + applyWorkingRequest: () => { + let request = clone(streams.wizard.workingRequest.value); + if (streams.wizard.insertOperation.value.type) { + ctx.services.craft.modify(request, () => streams.wizard.insertOperation.value = EMPTY_OBJECT); + } else { + ctx.services.craft.modifyInHistoryAndStep(request, () => streams.wizard.effectiveOperation.value = EMPTY_OBJECT); + } } - } -} + }; +} +function applyOverrides(params, initialOverrides) { + Object.assign(params, initialOverrides); +} diff --git a/web/app/cad/init/startApplication.js b/web/app/cad/init/startApplication.js index ac405098..b15a613d 100644 --- a/web/app/cad/init/startApplication.js +++ b/web/app/cad/init/startApplication.js @@ -2,6 +2,7 @@ import * as LifecyclePlugin from './lifecyclePlugin'; import * as AppTabsPlugin from '../dom/appTabsPlugin'; import * as DomPlugin from '../dom/domPlugin'; import * as PickControlPlugin from '../scene/controls/pickControlPlugin'; +import * as MouseEventSystemPlugin from '../scene/controls/mouseEventSystemPlugin'; import * as ScenePlugin from '../scene/scenePlugin'; import * as SelectionMarkerPlugin from '../scene/selectionMarker/selectionMarkerPlugin'; import * as ActionSystemPlugin from '../actions/actionSystemPlugin'; @@ -9,6 +10,7 @@ import * as UiEntryPointsPlugin from '../dom/uiEntryPointsPlugin'; import * as MenuPlugin from '../dom/menu/menuPlugin'; import * as KeyboardPlugin from '../keyboard/keyboardPlugin'; import * as WizardPlugin from '../craft/wizard/wizardPlugin'; +import * as PreviewPlugin from '../preview/previewPlugin'; import * as OperationPlugin from '../craft/operationPlugin'; import * as CraftEnginesPlugin from '../craft/enginesPlugin'; import * as CadRegistryPlugin from '../craft/cadRegistryPlugin'; @@ -39,10 +41,11 @@ export default function startApplication(callback) { UiEntryPointsPlugin, MenuPlugin, KeyboardPlugin, - WizardPlugin, CraftEnginesPlugin, OperationPlugin, CraftPlugin, + WizardPlugin, + PreviewPlugin, CraftUiPlugin, CadRegistryPlugin, tpiPlugin @@ -51,6 +54,7 @@ export default function startApplication(callback) { let plugins = [ DomPlugin, ScenePlugin, + MouseEventSystemPlugin, PickControlPlugin, SelectionMarkerPlugin, SketcherPlugin, diff --git a/web/app/cad/model/mdatum.js b/web/app/cad/model/mdatum.js index 821beaad..6a3d23f1 100644 --- a/web/app/cad/model/mdatum.js +++ b/web/app/cad/model/mdatum.js @@ -3,7 +3,7 @@ import {MObject} from './mobject'; export class MDatum extends MObject { static TYPE = 'datum'; - static ID_COUNTER = 0; + static ID_COUNTER = 0; // TODO: reset the counter constructor(csys) { super(); diff --git a/web/app/cad/model/mface.js b/web/app/cad/model/mface.js index 963bde71..49f52788 100644 --- a/web/app/cad/model/mface.js +++ b/web/app/cad/model/mface.js @@ -37,6 +37,14 @@ export class MFace extends MObject { return this._basis; } + get csys() { + if (!this._csys) { + let [x,y,z] = this.basis(); + this._csys = new CSys(this.normal().multiply(this.depth()), x, y, z); + } + return this._csys; + } + setSketch(sketch) { this.sketch = sketch; this.sketchObjects = []; diff --git a/web/app/cad/part/menuConfig.js b/web/app/cad/part/menuConfig.js index 359304bd..6ba4f466 100644 --- a/web/app/cad/part/menuConfig.js +++ b/web/app/cad/part/menuConfig.js @@ -8,7 +8,7 @@ export default [ id: 'craft', cssIcons: ['magic'], info: 'set of available craft operations on a solid', - actions: ['EXTRUDE', 'CUT', 'REVOLVE', 'SHELL', 'FILLET'] + actions: ['EXTRUDE', 'CUT', 'REVOLVE', 'SHELL', 'FILLET', 'DATUM_CREATE'] }, { id: 'primitives', @@ -37,5 +37,15 @@ export default [ label: 'solid-context', info: 'solid context actions', actions: ['LookAtSolid'] - } + }, + { + id: 'datum', + label: 'datum', + cssIcons: ['magic'], + info: 'operations on datum', + actions: ['DATUM_MOVE'] + // actions: ['DATUM_MOVE', 'DATUM_ROTATE', 'DATUM_REBASE', '-', 'PLANE_FROM_DATUM', 'BOX', 'SPHERE', 'TORUS', + // 'CONE', 'CYLINDER'] + }, + ]; diff --git a/web/app/cad/part/partOperationsPlugin.js b/web/app/cad/part/partOperationsPlugin.js index e5f700ea..cb6e6e4b 100644 --- a/web/app/cad/part/partOperationsPlugin.js +++ b/web/app/cad/part/partOperationsPlugin.js @@ -4,6 +4,8 @@ import cutOperation from '../craft/cutExtrude/cutOperation'; import planeOperation from '../craft/primitives/planeOperation'; import filletOperation from '../craft/fillet/filletOperation'; import revolveOperation from '../craft/revolve/revolveOperation'; +import createDatumOperation from '../craft/datum/create/createDatumOperation'; +import moveDatumOperation from '../craft/datum/move/moveDatumOperation'; export function activate({services}) { services.operation.registerOperations([ @@ -12,6 +14,8 @@ export function activate({services}) { extrudeOperation, cutOperation, revolveOperation, - filletOperation + filletOperation, + createDatumOperation, + moveDatumOperation ]) } \ No newline at end of file diff --git a/web/app/cad/preview/previewPlugin.js b/web/app/cad/preview/previewPlugin.js new file mode 100644 index 00000000..73f8a3c7 --- /dev/null +++ b/web/app/cad/preview/previewPlugin.js @@ -0,0 +1,48 @@ +import {createPreviewer} from './scenePreviewer'; + +export function activate(ctx) { + let {streams, services} = ctx; + + const updateParams = mutator => streams.wizard.workingRequest.mutate(data => mutator(data.params)); + + let previewContext = { + operation: null, + previewer: null + }; + + streams.wizard.workingRequest.attach(({type, params}) => { + if (!type) { + if (previewContext.previewer) { + previewContext.previewer.dispose(); + previewContext.previewer = null; + previewContext.operation = null; + ctx.services.viewer.requestRender(); + } + return; + } + if (type !== previewContext.operation) { + if (previewContext.previewer != null) { + previewContext.previewer.dispose(); + ctx.services.viewer.requestRender(); + previewContext.previewer = null; + } + let operation = services.operation.get(type); + + if (operation.previewGeomProvider) { + previewContext.previewer = createPreviewer(operation.previewGeomProvider, services, params); + ctx.services.viewer.requestRender(); + } else if (operation.previewer) { + previewContext.previewer = operation.previewer(ctx, params, updateParams); + ctx.services.viewer.requestRender(); + } else { + previewContext.previewer = null; + } + previewContext.operation = type; + } else { + if (previewContext.previewer) { + previewContext.previewer.update(params); + ctx.services.viewer.requestRender(); + } + } + }); +} \ No newline at end of file diff --git a/web/app/cad/preview/scenePreviewer.js b/web/app/cad/preview/scenePreviewer.js index b588a634..f6270ae6 100644 --- a/web/app/cad/preview/scenePreviewer.js +++ b/web/app/cad/preview/scenePreviewer.js @@ -3,7 +3,7 @@ import {createTransparentPhongMaterial} from 'scene/materials'; import {createMesh} from 'scene/objects/mesh'; -export function createPreviewer(sceneGeometryCreator, services) { +export function createPreviewer(sceneGeometryCreator, services, initialParams) { const previewGroup = SceneGraph.createGroup(); SceneGraph.addToGroup(services.cadScene.workGroup, previewGroup); @@ -19,17 +19,21 @@ export function createPreviewer(sceneGeometryCreator, services) { function update(params) { destroyPreviewObject(); - previewObject = createMesh(sceneGeometryCreator(params, services), IMAGINARY_SURFACE_MATERIAL); + let geometry = sceneGeometryCreator(params, services); + if (!geometry) { + services.viewer.requestRender(); + return; + } + previewObject = createMesh(geometry, IMAGINARY_SURFACE_MATERIAL); previewGroup.add(previewObject); - services.viewer.render(); } function dispose() { destroyPreviewObject(); SceneGraph.removeFromGroup(services.cadScene.workGroup, previewGroup); - services.viewer.render(); } + update(initialParams); return {update, dispose}; } diff --git a/web/app/cad/sandbox.js b/web/app/cad/sandbox.js index 1dca8b0e..023148b9 100644 --- a/web/app/cad/sandbox.js +++ b/web/app/cad/sandbox.js @@ -7,8 +7,10 @@ import NurbsCurve from "../brep/geom/curves/nurbsCurve"; import {surfaceIntersect} from '../brep/geom/intersection/surfaceSurface'; import {closestToCurveParam, findClosestToCurveParamRoughly} from '../brep/geom/curves/closestPoint'; import NurbsSurface from '../brep/geom/surfaces/nurbsSurface'; +import DatumObject3D from './craft/datum/datumObject'; +import CSys from '../math/csys'; -export function runSandbox({bus, services: { viewer, cadScene, cadRegistry, tpi, tpi: {addShellOnScene} }}) { +export function runSandbox({bus, services, services: { viewer, cadScene, cadRegistry, tpi, tpi: {addShellOnScene} }}) { function test1() { @@ -238,7 +240,18 @@ export function runSandbox({bus, services: { viewer, cadScene, cadRegistry, tpi, // cylinderAndPlaneIntersect(); // curvesIntersect(); // cylTest(); - surfaceSurfaceIntersect(); + // surfaceSurfaceIntersect(); + + + // let o1 = new DatumObject3D(CSys.origin().move(new Vector(200, 200, 200)), viewer.sceneSetup); + // o1.setMoveMode(DatumObject3D.AXIS.Y); + // cadScene.auxGroup.add(o1); + // let o2 = new DatumObject3D(CSys.origin().move(new Vector(-200, -200, -200)), viewer.sceneSetup); + // o2.setMoveMode(DatumObject3D.AXIS.Z); + // cadScene.auxGroup.add(o2); + + services.action.run('DATUM_CREATE'); + } diff --git a/web/app/cad/scene/controls/mouseEventSystemPlugin.js b/web/app/cad/scene/controls/mouseEventSystemPlugin.js new file mode 100644 index 00000000..16ff022f --- /dev/null +++ b/web/app/cad/scene/controls/mouseEventSystemPlugin.js @@ -0,0 +1,110 @@ +import {findAncestor} from 'scene/sceneGraph'; + +export function activate(context) { + const {services, streams} = context; + let domElement = services.viewer.sceneSetup.domElement(); + + domElement.addEventListener('mousedown', mousedown, false); + domElement.addEventListener('mouseup', mouseup, false); + domElement.addEventListener('mousemove', mousemove, false); + + let performRaycast = e => services.viewer.raycast(e, services.viewer.sceneSetup.scene.children); + + let toDrag = null; + let pressed = new Set(); + + function startDrag(objectToDrag, e) { + if (toDrag) { + stopDrag(e); + } + toDrag = objectToDrag; + services.viewer.sceneSetup.trackballControls.enabled = false; + } + + function stopDrag(e) { + toDrag.dragDrop(e); + toDrag = null; + services.viewer.sceneSetup.trackballControls.enabled = true; + } + + function mousedown(e) { + pressed.clear(); + let hits = performRaycast(e); + for (let hit of hits) { + let obj = hit.object; + if (obj && obj.onMouseDown) { + obj.onMouseDown(e, hits, objectToDrag => startDrag(objectToDrag, e)); + } + pressed.add(obj); + if (!hit.object.passMouseEvent || !hit.object.passMouseEvent(e, hits)) { + break; + } + } + } + + function mouseup(e) { + if (toDrag) { + stopDrag(e); + mousemove(e); + } else { + let hits = performRaycast(e); + for (let hit of hits) { + let obj = hit.object; + if (obj && obj.onMouseUp) { + obj.onMouseUp(e, hits); + } + if (pressed.has(obj) && obj.onMouseClick) { + obj.onMouseClick(e, hits); + } + if (!hit.object.passMouseEvent || !hit.object.passMouseEvent(e, hits)) { + break; + } + } + pressed.clear(); + } + } + + let entered = new Set(); + let valid = new Set(); + + function mousemove(e) { + + if (toDrag) { + toDrag.dragMove(e); + } else { + let hits = performRaycast(e); + + valid.clear(); + for (let hit of hits) { + valid.add(hit.object); + if (!hit.object.passMouseEvent || !hit.object.passMouseEvent(e, hits)) { + break; + } + } + + entered.forEach(e => { + if (!valid.has(e) && e.onMouseLeave) { + e.onMouseLeave(e, hits); + } + }); + + valid.forEach(e => { + if (!entered.has(e) && e.onMouseEnter) { + e.onMouseEnter(e, hits); + } + if (e.onMouseMove) { + e.onMouseMove(e, hits); + } + }); + + let t = valid; + valid = entered; + entered = t; + valid.clear(); + } + } +} + +export function hasObject(hits, object) { + return hits.find(hit => hit.object === object); +} \ No newline at end of file diff --git a/web/app/cad/scene/controls/pickControlPlugin.js b/web/app/cad/scene/controls/pickControlPlugin.js index 5b45ba13..33c5540e 100644 --- a/web/app/cad/scene/controls/pickControlPlugin.js +++ b/web/app/cad/scene/controls/pickControlPlugin.js @@ -1,7 +1,7 @@ import * as mask from 'gems/mask' -import {getAttribute, setAttribute} from '../../../../../modules/scene/objectData'; +import {getAttribute, setAttribute} from 'scene/objectData'; import {FACE, EDGE, SKETCH_OBJECT} from '../entites'; -import {state} from '../../../../../modules/lstream'; +import {state} from 'lstream'; export const PICK_KIND = { FACE: mask.type(1), @@ -85,7 +85,7 @@ export function activate(context) { } function raycastObjects(event, kind, visitor) { - let pickResults = services.viewer.raycast(event, services.cadScene.workGroup); + let pickResults = services.viewer.raycast(event, services.cadScene.workGroup.children); const pickers = [ (pickResult) => { if (mask.is(kind, PICK_KIND.SKETCH) && pickResult.object instanceof THREE.Line) { @@ -126,36 +126,39 @@ export function activate(context) { } } -function initStateAndServices({streams, services}) { - - services.selection = { - }; - +export function defineStreams({streams}) { streams.selection = { }; - + SELECTABLE_ENTITIES.forEach(entity => { + let selectionState = state([]); + streams.selection[entity] = state([]); + }); + +} + +function initStateAndServices({streams, services}) { + + services.selection = {}; + SELECTABLE_ENTITIES.forEach(entity => { let entitySelectApi = { objects: [], single: undefined }; services.selection[entity] = entitySelectApi; - let selectionState = state([]); - streams.selection[entity] = selectionState; + let selectionState = streams.selection[entity]; selectionState.attach(selection => { entitySelectApi.objects = selection.map(id => services.cadRegistry.findEntity(entity, id)); - entitySelectApi.single = entitySelectApi.objects[0]; + entitySelectApi.single = entitySelectApi.objects[0]; }); entitySelectApi.select = selection => selectionState.value = selection; }); - //withdraw all streams.craft.models.attach(() => { - Object.values(streams.selection).forEach(ss => ss.next([])) - }) + withdrawAll(streams.selection) + }); } - - - - +export function withdrawAll(selectionStreams) { + Object.values(selectionStreams).forEach(stream => stream.next([])) +} diff --git a/web/app/cad/scene/entites.js b/web/app/cad/scene/entites.js index 83f99209..4d98eba3 100644 --- a/web/app/cad/scene/entites.js +++ b/web/app/cad/scene/entites.js @@ -3,14 +3,16 @@ import {MFace} from '../model/mface'; import {MEdge} from '../model/medge'; import {MVertex} from '../model/mvertex'; import {MSketchObject} from '../model/msketchObject'; +import {MDatum} from '../model/mdatum'; export const SHELL = MShell.TYPE; export const FACE = MFace.TYPE; export const EDGE = MEdge.TYPE; export const VERTEX = MVertex.TYPE; export const SKETCH_OBJECT = MSketchObject.TYPE; +export const DATUM = MDatum.TYPE; -export const ENTITIES = [SHELL, FACE, EDGE, VERTEX, SKETCH_OBJECT]; +export const ENTITIES = [SHELL, DATUM, FACE, EDGE, VERTEX, SKETCH_OBJECT]; export const PART_MODELING_ENTITIES = [SHELL, FACE, EDGE, VERTEX, SKETCH_OBJECT]; export const ASSEMBLY_ENTITIES = [SHELL, FACE, EDGE, VERTEX]; diff --git a/web/app/cad/scene/selectionMarker/selectionMarkerPlugin.js b/web/app/cad/scene/selectionMarker/selectionMarkerPlugin.js index 8bc16eac..781b084f 100644 --- a/web/app/cad/scene/selectionMarker/selectionMarkerPlugin.js +++ b/web/app/cad/scene/selectionMarker/selectionMarkerPlugin.js @@ -16,7 +16,7 @@ export function activate({streams, services}) { model.ext.view.mark(); } }); - services.viewer.render(); + services.viewer.requestRender(); }; streams.selection.face.pairwise([]).attach(selectionSync(FACE)); diff --git a/web/app/cad/scene/viewSyncPlugin.js b/web/app/cad/scene/viewSyncPlugin.js index c6d53cbb..7c53cd0a 100644 --- a/web/app/cad/scene/viewSyncPlugin.js +++ b/web/app/cad/scene/viewSyncPlugin.js @@ -5,6 +5,10 @@ import {MOpenFaceShell} from '../model/mopenFace'; import {EDGE, FACE, SHELL, SKETCH_OBJECT} from './entites'; import {OpenFaceShellView} from './views/openFaceView'; import {findDiff} from '../../../../modules/gems/iterables'; +import {MShell} from '../model/mshell'; +import {MDatum} from '../model/mdatum'; +import DatumView from './views/datumView'; +import {View} from './views/view'; export function activate(context) { let {streams} = context; @@ -12,15 +16,15 @@ export function activate(context) { streams.sketcher.update.attach(mFace => mFace.ext.view.updateSketch()); } -function sceneSynchronizer({services: {cadScene, cadRegistry}}) { +function sceneSynchronizer({services: {cadScene, cadRegistry, viewer, wizard, action}}) { return function() { let wgChildren = cadScene.workGroup.children; let existent = new Set(); for (let i = wgChildren.length - 1; i >= 0; --i) { let obj = wgChildren[i]; - let shellView = getAttribute(obj, SHELL); + let shellView = getAttribute(obj, View.MARKER); if (shellView) { - let exists = cadRegistry.shellIndex.has(shellView.model.id); + let exists = cadRegistry.modelIndex.has(shellView.model.id); if (!exists) { SceneGraph.removeFromGroup(cadScene.workGroup, obj); shellView.dispose(); @@ -30,17 +34,19 @@ function sceneSynchronizer({services: {cadScene, cadRegistry}}) { } } - let allShells = cadRegistry.getAllShells(); - - for (let shell of allShells) { - if (!existent.has(shell.id)) { - let shellView; - if (shell instanceof MOpenFaceShell) { - shellView = new OpenFaceShellView(shell); + for (let model of cadRegistry.models) { + if (!existent.has(model.id)) { + let modelView; + if (model instanceof MOpenFaceShell) { + modelView = new OpenFaceShellView(model); + } else if (model instanceof MShell) { + modelView = new ShellView(model); + } else if (model instanceof MDatum) { + modelView = new DatumView(model, viewer, wizard.open, (e) => action.run('menu.datum', e)); } else { - shellView = new ShellView(shell); + console.warn('unsupported model ' + model); } - SceneGraph.addToGroup(cadScene.workGroup, shellView.rootGroup); + SceneGraph.addToGroup(cadScene.workGroup, modelView.rootGroup); } } } diff --git a/web/app/cad/scene/viewer.js b/web/app/cad/scene/viewer.js index 25a5a63c..cc4cbd57 100644 --- a/web/app/cad/scene/viewer.js +++ b/web/app/cad/scene/viewer.js @@ -11,7 +11,7 @@ export default class Viewer { this.sceneSetup.render(); } - requestRender() { + requestRender = () => { if (this.renderRequested) { return; } @@ -19,14 +19,21 @@ export default class Viewer { this.renderRequested = false; this.render(); }); - } + }; + + setVisualProp = (obj, prop, value) => { + if (obj[prop] !== value) { + obj[prop] = value; + this.requestRender(); + } + }; lookAt(obj) { this.sceneSetup.lookAt(obj); } - raycast(event, group) { - return this.sceneSetup.raycast(event, group); + raycast(event, objects) { + return this.sceneSetup.raycast(event, objects); } setCameraMode(mode) { diff --git a/web/app/cad/scene/views/datumView.js b/web/app/cad/scene/views/datumView.js index bc585596..ec1aeaaa 100644 --- a/web/app/cad/scene/views/datumView.js +++ b/web/app/cad/scene/views/datumView.js @@ -1,31 +1,164 @@ import {View} from './view'; -import * as SceneGraph from '../../../../../modules/scene/sceneGraph'; -import {createArrow} from '../../../../../modules/scene/objects/auxiliary'; -import {moveObject3D} from '../../../../../modules/scene/objects/transform'; -import {AXIS} from '../../../math/l3space'; +import DatumObject3D from '../../craft/datum/datumObject'; +import {DATUM} from '../entites'; +import {setAttribute} from 'scene/objectData'; +import {Mesh, MeshBasicMaterial, PolyhedronGeometry, SphereGeometry} from 'three'; +import {CSYS_SIZE_MODEL} from '../../craft/datum/csysObject'; export default class DatumView extends View { - constructor(edge) { - super(edge); - this.rootGroup = SceneGraph.createGroup(); - } - - setUpAxises() { - let arrowLength = 100; - let createAxisArrow = createArrow.bind(null, arrowLength, 5, 2); - let addAxis = (axis, color) => { - let arrow = createAxisArrow(axis, color, 0.2); - moveObject3D(arrow, axis.scale(-arrowLength * 0.5)); - SceneGraph.addToGroup(this.auxGroup, arrow); - }; - addAxis(AXIS.X, 0xFF0000); - addAxis(AXIS.Y, 0x00FF00); - addAxis(AXIS.Z, 0x0000FF); - } + constructor(datum, viewer, beginOperation, showDatumMenu) { + super(datum); + class MenuButton extends Mesh { + + mouseInside; + + constructor() { + super(new SphereGeometry( 1 ), new MeshBasicMaterial({ + transparent: true, + opacity: 0.5, + color: 0xFFFFFF, + visible: false + })); + this.scale.multiplyScalar(CSYS_SIZE_MODEL * 0.2); + } + + dispose() { + this.geometry.dispose(); + this.material.dispose(); + } + + onMouseEnter() { + this.mouseInside = true; + this.updateVisibility(); + this.material.color.setHex(0xFBB4FF); + viewer.requestRender(); + } + + onMouseLeave(e, hits, behindHits) { + this.mouseInside = false; + this.updateVisibility(); + this.material.color.setHex(0xFFFFFF); + viewer.requestRender(); + } + + onMouseDown() { + this.material.color.setHex(0xB500FF); + viewer.requestRender(); + } + + onMouseUp() { + this.material.color.setHex(0xFBB4FF); + viewer.requestRender(); + } + + onMouseClick(e) { + showDatumMenu({ + x: e.offsetX, + y: e.offsetY + }); + } + + updateVisibility() { + let datum3D = this.parent.parent; + viewer.setVisualProp(this.material, 'visible', !datum3D.operationStarted && + (this.mouseInside || datum3D.affordanceArea.mouseInside)); + } + } + + class ActiveAffordanceBox extends AffordanceBox { + + mouseInside; + + onMouseEnter(e, hits) { + this.mouseInside = true; + this.parent.parent.menuButton.updateVisibility(); + + } + + onMouseLeave(e, hits) { + this.mouseInside = false; + this.parent.parent.menuButton.updateVisibility(); + } + + passMouseEvent(e, hits) { + return true; + } + } + + class StartingOperationDatumObject3D extends DatumObject3D { + + operationStarted = false; + + constructor(csys, viewer) { + super(csys, viewer); + this.affordanceArea = new ActiveAffordanceBox(); + this.menuButton = new MenuButton(); + this.csysObj.add(this.affordanceArea); + this.csysObj.add(this.menuButton); + } + + dragStart(e, axis) { + if (!this.operationStarted) { + beginOperation('DATUM_MOVE', { + datum: datum.id + }); + this.beginOperation(); + } + super.dragStart(e, axis); + } + + beginOperation() { + this.operationStarted = true; + this.menuButton.updateVisibility(); + } + + finishOperation() { + this.operationStarted = false; + this.menuButton.updateVisibility(); + this.exitEditMode(); + } + + dispose() { + super.dispose(); + this.affordanceArea.dispose(); + this.menuButton.dispose(); + } + } + this.rootGroup = new StartingOperationDatumObject3D(datum.csys, viewer); + + setAttribute(this.rootGroup, DATUM, this); + setAttribute(this.rootGroup, View.MARKER, this); + } dispose() { + super.dispose(); + this.rootGroup.dispose(); + } +} + +class AffordanceBox extends Mesh { + + constructor() { + super(new PolyhedronGeometry( + [0,0,0, 1,0,0, 0,1,0, 0,0,1], + [0,2,1, 0,1,3, 0,3,2, 1,2,3] + ), new MeshBasicMaterial({ + transparent: true, + opacity: 0.5, + color: 0xAA8439, + visible: false + })); + let size = CSYS_SIZE_MODEL * 1.5; + let shift = -(size - CSYS_SIZE_MODEL) * 0.3; + this.scale.set(size, size, size); + this.position.set(shift, shift, shift); + } + + dispose() { + this.geometry.dispose(); + this.material.dispose(); } } \ No newline at end of file diff --git a/web/app/cad/scene/views/openFaceView.js b/web/app/cad/scene/views/openFaceView.js index f0fb9764..fa14284c 100644 --- a/web/app/cad/scene/views/openFaceView.js +++ b/web/app/cad/scene/views/openFaceView.js @@ -8,7 +8,8 @@ export class OpenFaceShellView extends View { constructor(shell) { super(shell); this.openFace = new OpenFaceView(shell.face); - setAttribute(this.rootGroup, SHELL, this) + setAttribute(this.rootGroup, SHELL, this); + setAttribute(this.rootGroup, View.MARKER, this); } get rootGroup() { diff --git a/web/app/cad/scene/views/shellView.js b/web/app/cad/scene/views/shellView.js index 63b869f2..032ec2cd 100644 --- a/web/app/cad/scene/views/shellView.js +++ b/web/app/cad/scene/views/shellView.js @@ -1,11 +1,10 @@ import {View} from './view'; import * as SceneGraph from '../../../../../modules/scene/sceneGraph'; -import {genSolidId} from '../../craft/cadRegistryPlugin'; import {setAttribute} from '../../../../../modules/scene/objectData'; import {createSolidMaterial} from '../wrappers/sceneObject'; import {FaceView} from './faceView'; -import {SHELL} from '../entites'; import {EdgeView} from './edgeView'; +import {SHELL} from '../entites'; export class ShellView extends View { @@ -24,6 +23,7 @@ export class ShellView extends View { SceneGraph.addToGroup(this.rootGroup, this.vertexGroup); setAttribute(this.rootGroup, SHELL, this); + setAttribute(this.rootGroup, View.MARKER, this); const geometry = new THREE.Geometry(); geometry.dynamic = true; diff --git a/web/app/cad/scene/views/view.js b/web/app/cad/scene/views/view.js index a3214e86..b73b9058 100644 --- a/web/app/cad/scene/views/view.js +++ b/web/app/cad/scene/views/view.js @@ -1,5 +1,7 @@ export class View { + static MARKER = 'ModelView'; + constructor(model) { this.model = model; model.ext.view = this; diff --git a/web/app/math/csys.js b/web/app/math/csys.js index 57e58962..d3df8a95 100644 --- a/web/app/math/csys.js +++ b/web/app/math/csys.js @@ -6,6 +6,10 @@ export default class CSys { return new CSys(origin, dir, normal.cross(dir), normal) } + static origin() { + return new CSys(ORIGIN.copy(), AXIS.X.copy(), AXIS.Y.copy(), AXIS.Z.copy()); + } + constructor(origin, x, y, z) { this.origin = origin; this.x = x; @@ -32,9 +36,22 @@ export default class CSys { return this._outTr; } - copy() { - return CSys(this.origin, this.x, this.y, this.z); + copy(csys) { + this.origin.setV(csys.origin); + this.x.setV(csys.x); + this.y.setV(csys.y); + this.z.setV(csys.z); + return this; } + + clone() { + return new CSys(this.origin.copy(), this.x.copy(), this.y.copy(), this.z.copy()); + } + + move(x, y, z) { + this.origin.set(x, y, z); + return this; + } + } -CSys.ORIGIN = new CSys(ORIGIN, AXIS.X, AXIS.Y, AXIS.Z);