create/move datum operation

This commit is contained in:
Val Erastov 2018-10-19 23:19:33 -07:00
parent 325f08c9a0
commit df742b81d6
58 changed files with 1357 additions and 323 deletions

View file

@ -1 +1,6 @@
export const EMPTY_OBJECT = Object.freeze({});
export function clone(object) {
return JSON.parse(JSON.stringify(object));
}

View file

@ -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;
}

View file

@ -0,0 +1,12 @@
import {StateStream} from './state';
export class DisableableState extends StateStream {
disabled = false;
next(value) {
if (!this._disabled) {
super.next(value);
}
}
}

16
modules/renders/index.js Normal file
View file

@ -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);
}
}

View file

@ -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);

View file

@ -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();
}
}
}

View file

@ -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 );

View file

@ -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
});
}
}
}

View file

@ -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;
}

View file

@ -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);

View file

@ -19,4 +19,8 @@
.danger {
.button-behavior(@color-danger)
}
}
.minor {
.button-behavior(@color-neutral)
}

View file

@ -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 <input type='checkbox'
defaultValue={initValue}
defaultValue={value}
onChange={e => onChange(e.target.value)} />
}
}

View file

@ -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 <InputControl type='number'
onWheel={this.onWheel}
defaultValue={initValue}
value={ Math.round(value * 1000) / 1000 }
onChange={this.onChange}
inputRef={input => 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
};

View file

@ -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 <label className={ls.radioButton}>
<input type='radio' name={radioButtonsGroupName} defaultChecked={radioButtonsInitValue === value}
<input type='radio' name={radioButtonsGroupName} checked={radioButtonsValue === value}
value={value} onChange={onChange}/> {label}
</label>
@ -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
};

View file

@ -0,0 +1,9 @@
import React from 'react';
export default class ReadOnlyValueControl extends React.Component {
render() {
let {value} = this.props;
return <span>{value}</span>;
}
}

View file

@ -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':

View file

@ -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;
}
}
}

View file

@ -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

View file

@ -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));

View file

@ -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 <Group>
<NumberField name='x' label='X' />
<NumberField name='y' label='Y' />
<NumberField name='z' label='Z' />
<SingleEntity name='face' label='off of' entity='face' />
</Group>;
}

View file

@ -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
},
}

View file

@ -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
};

View file

@ -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;

View file

@ -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);
};
}

View file

@ -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 <Group>
<ReadOnlyValueField name='datum'/>
<NumberField name='x' label='X' />
<NumberField name='y' label='Y' />
<NumberField name='z' label='Z' />
<CheckboxField name='copy' />
</Group>;
}

View file

@ -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
}
}

View file

@ -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
};

View file

@ -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);

View file

@ -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}) => ({

View file

@ -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 => <ModelSection type='shell' model={m} defaultOpen={true}>
return models.map(m => (m instanceof MShell) ? <ModelSection type='shell' model={m} defaultOpen={true}>
<Section label='faces' defaultOpen={true}>
{
m.faces.map(f => <ModelSection type='face' model={f}>
@ -23,7 +25,7 @@ export default connect(streams => streams.craft.models.map(models => ({models}))
{m.edges.map(e => <ModelSection type='edge' model={e} />)}
</Section>
</ModelSection>);
</ModelSection> : (m instanceof MDatum) ? <ModelSection type='datum' model={m} defaultOpen={true}/> : null);
});

View file

@ -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);

View file

@ -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 <span>operation error</span>;
}
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 <Window initWidth={250}
initLeft={left}
title={type}
initLeft={left || 25}
title={title}
onClose={this.cancel}
onKeyDown={this.onKeyDown}
setFocus={this.focusFirstInput}>
<FormContext.Provider value={formContext}>
<Form />
<Form/>
</FormContext.Provider>
<Stack>
<ButtonGroup>
@ -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
};
}

View file

@ -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 <Wizard type={insertOperation}
createPreviewer={previewerCreator(operation)}
form={operation.form}
params={params}
validate={createValidator(operation)}
close={close}/>
} 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 <Wizard type={type}
validate={createValidator(operation)}
createPreviewer={previewerCreator(operation)}
params={clone(params)}
form={operation.form}
onCancel={cancelHistoryEdit} onOK={stepHistory} close={NOOP} />
}
function WizardManager({type, changingHistory, resolve, cancel, stepHistory, insertOperation, cancelHistoryEdit, applyWorkingRequest, validator}) {
if (!type) {
return null;
}
return <Wizard resolveOperation={resolve}
validator={validator}
onCancel={changingHistory ? cancelHistoryEdit : cancel}
onOK={applyWorkingRequest} />
}
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));
}

View file

@ -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));

View file

@ -22,17 +22,13 @@ export function formField(Control) {
}
export function attachToForm(Control) {
return function FormField({name, label, ...props}) {
return function FormField({name, ...props}) {
return <FormContext.Consumer>
{
ctx => {
const onChange = val => {
ctx.data[name] = val;
ctx.onChange();
};
let initValue = ctx.data[name];
const onChange = val => ctx.updateParam(name, val);
return <React.Fragment>
<Control initValue={initValue} onChange={onChange} name={name} {...props} />
<Control value={ctx.data[name]} onChange={onChange} name={name} {...props} />
</React.Fragment>;
}
}

View file

@ -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 <FormContext.Consumer>
{
({onChange}) => this.state.value.map(data => {
({onChange}) => this.props.value.map(data => {
let subContext = {
data,
onChange

View file

@ -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 <div>
{camelCaseSplitToStr(this.props.name)}: {this.state.selectedItem}
</div>;
let {name, label, streams, entity} = this.props;
let selection = streams.selection[entity].value[0];
return <Field>
<Label>{label||camelCaseSplitToStr(name)}:</Label>
<div>{selection ?
<span>{selection} <Button type='minor' onClick={this.deselect}> <Fa icon='times'/></Button></span> :
<span className={ls.emptySelection}>{'<not selected>'}</span>}</div>
</Field>;
}
}

View file

@ -0,0 +1,4 @@
.emptySelection {
font-style: initial;
color: #BFBFBF;
}

View file

@ -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);
}

View file

@ -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,

View file

@ -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();

View file

@ -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 = [];

View file

@ -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']
},
];

View file

@ -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
])
}

View file

@ -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();
}
}
});
}

View file

@ -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};
}

View file

@ -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');
}

View file

@ -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);
}

View file

@ -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([]))
}

View file

@ -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];

View file

@ -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));

View file

@ -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);
}
}
}

View file

@ -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) {

View file

@ -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();
}
}

View file

@ -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() {

View file

@ -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;

View file

@ -1,5 +1,7 @@
export class View {
static MARKER = 'ModelView';
constructor(model) {
this.model = model;
model.ext.view = this;

View file

@ -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);