mirror of
https://github.com/xibyte/jsketcher
synced 2025-12-14 20:33:30 +01:00
create/move datum operation
This commit is contained in:
parent
325f08c9a0
commit
df742b81d6
58 changed files with 1357 additions and 323 deletions
|
|
@ -1 +1,6 @@
|
|||
export const EMPTY_OBJECT = Object.freeze({});
|
||||
|
||||
export function clone(object) {
|
||||
return JSON.parse(JSON.stringify(object));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
12
modules/lstream/disableable.js
Normal file
12
modules/lstream/disableable.js
Normal 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
16
modules/renders/index.js
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
18
modules/scene/objects/primitiveObjects.js
Normal file
18
modules/scene/objects/primitiveObjects.js
Normal 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 );
|
||||
30
modules/scene/objects/raycastabeArea.js
Normal file
30
modules/scene/objects/raycastabeArea.js
Normal 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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -19,4 +19,8 @@
|
|||
|
||||
.danger {
|
||||
.button-behavior(@color-danger)
|
||||
}
|
||||
}
|
||||
|
||||
.minor {
|
||||
.button-behavior(@color-neutral)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)} />
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
|||
9
modules/ui/components/controls/ReadOnlyValueControl.jsx
Normal file
9
modules/ui/components/controls/ReadOnlyValueControl.jsx
Normal 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>;
|
||||
}
|
||||
}
|
||||
|
|
@ -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':
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
13
web/app/cad/craft/datum/create/CreateDatumWizard.jsx
Normal file
13
web/app/cad/craft/datum/create/CreateDatumWizard.jsx
Normal 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>;
|
||||
}
|
||||
19
web/app/cad/craft/datum/create/createDatumOpSchema.js
Normal file
19
web/app/cad/craft/datum/create/createDatumOpSchema.js
Normal 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
|
||||
},
|
||||
}
|
||||
84
web/app/cad/craft/datum/create/createDatumOperation.js
Normal file
84
web/app/cad/craft/datum/create/createDatumOperation.js
Normal 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
|
||||
};
|
||||
|
||||
|
||||
|
||||
84
web/app/cad/craft/datum/csysObject.js
Normal file
84
web/app/cad/craft/datum/csysObject.js
Normal 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;
|
||||
148
web/app/cad/craft/datum/datumObject.js
Normal file
148
web/app/cad/craft/datum/datumObject.js
Normal 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);
|
||||
};
|
||||
}
|
||||
14
web/app/cad/craft/datum/move/MoveDatumWizard.jsx
Normal file
14
web/app/cad/craft/datum/move/MoveDatumWizard.jsx
Normal 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>;
|
||||
}
|
||||
21
web/app/cad/craft/datum/move/moveDatumOpSchema.js
Normal file
21
web/app/cad/craft/datum/move/moveDatumOpSchema.js
Normal 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
|
||||
}
|
||||
}
|
||||
80
web/app/cad/craft/datum/move/moveDatumOperation.js
Normal file
80
web/app/cad/craft/datum/move/moveDatumOperation.js
Normal 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
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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}) => ({
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
.emptySelection {
|
||||
font-style: initial;
|
||||
color: #BFBFBF;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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 = [];
|
||||
|
|
|
|||
|
|
@ -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']
|
||||
},
|
||||
|
||||
];
|
||||
|
|
|
|||
|
|
@ -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
|
||||
])
|
||||
}
|
||||
48
web/app/cad/preview/previewPlugin.js
Normal file
48
web/app/cad/preview/previewPlugin.js
Normal 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();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -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};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
110
web/app/cad/scene/controls/mouseEventSystemPlugin.js
Normal file
110
web/app/cad/scene/controls/mouseEventSystemPlugin.js
Normal 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);
|
||||
}
|
||||
|
|
@ -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([]))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
export class View {
|
||||
|
||||
static MARKER = 'ModelView';
|
||||
|
||||
constructor(model) {
|
||||
this.model = model;
|
||||
model.ext.view = this;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue