introducing model object and decoupling from 3d

This commit is contained in:
Val Erastov 2018-06-22 21:46:51 -07:00
parent e226d416ee
commit 60878ad77c
67 changed files with 11525 additions and 373 deletions

1
modules/gems/func.js Normal file
View file

@ -0,0 +1 @@
export const NOOP = () => {};

View file

@ -26,4 +26,6 @@ export function findDiff(arr1, arr2) {
}
return [both, firstOnly, secondOnly]
}
}
export const EMPTY_ARRAY = Object.freeze([]);

View file

@ -1,10 +1,7 @@
export class StreamBase {
attach() {}
next(value) {}
attach(observer) {}
map(fn) {
return new MapStream(this, fn);
}
@ -12,39 +9,20 @@ export class StreamBase {
filter(predicate) {
return new FilterStream(this, predicate);
}
}
export class MapStream extends StreamBase {
constructor(stream, fn) {
super();
this.stream = stream;
this.fn = fn;
}
attach(observer) {
return this.stream.attach(val => observer(this.fn(val)));
}
static create = (stream, fn) => new MapStream(stream, fn);
}
export class FilterStream extends StreamBase {
constructor(stream, predicate) {
super();
this.stream = stream;
this.predicate = predicate;
}
attach(observer) {
return this.stream.attach(val => {
if (this.predicate(val)) {
observer(val);
}
});
pairwise(first) {
return new PairwiseStream(this, first);
}
static create = (stream, predicate) => new FilterStream(stream, predicate);
keep() {
let stateStream = new StateStream(undefined);
this.attach(v => stateStream.next(v));
return stateStream;
}
}
const {MapStream} = require('./map');
const {FilterStream} = require('./filter');
const {StateStream} = require('./state');
const {PairwiseStream} = require('./pairwise');

View file

@ -1,4 +1,5 @@
import {StreamBase} from './base';
import {NOT_INITIALIZED} from './utils';
export class CombineStream extends StreamBase {
@ -15,8 +16,8 @@ export class CombineStream extends StreamBase {
detachers[i] = s.attach(value => {
this.values[i] = value;
if (!this.ready) {
this.ready = this.isReady();
}
this.ready = this.isReady();
}
if (this.ready) {
observer(this.values);
}
@ -29,10 +30,8 @@ export class CombineStream extends StreamBase {
for (let val of this.values) {
if (val === NOT_INITIALIZED) {
return false;
}
}
}
}
return true;
}
}
const NOT_INITIALIZED = {};

View file

@ -0,0 +1,39 @@
import {Emitter} from './emitter';
export class ExternalStateStream extends Emitter {
constructor(get, set) {
super();
this.get = get;
this.set = set;
}
get value() {
return this.get();
}
set value(v) {
this.next(v);
}
next(v) {
this.set(v);
super.next(v);
}
update(updater) {
this.value = updater(this.value);
}
mutate(mutator) {
mutator(this.value);
this.next(this.value);
}
attach(observer) {
observer(this.value);
return super.attach(observer);
}
}

18
modules/lstream/filter.js Normal file
View file

@ -0,0 +1,18 @@
import {StreamBase} from './base';
export class FilterStream extends StreamBase {
constructor(stream, predicate) {
super();
this.stream = stream;
this.predicate = predicate;
}
attach(observer) {
return this.stream.attach(val => {
if (this.predicate(val)) {
observer(val);
}
});
}
}

View file

@ -2,21 +2,31 @@ import {CombineStream} from './combine';
import {StateStream} from './state';
import {Emitter} from './emitter';
import {FilterStream, MapStream} from './base';
import {ExternalStateStream} from './external';
import {MergeStream} from './merge';
export function stream() {
return new Emitter();
}
export function combine(...streams) {
return new CombineStream(streams);
}
export function stream() {
return new Emitter();
export function merge(...streams) {
return new MergeStream(streams);
}
export function state(initialValue) {
return new StateStream(initialValue);
}
export const map = MapStream.create;
export function externalState(get, set) {
return new ExternalStateStream(get, set);
}
export const filter = FilterStream.create;
export const map = (stream, fn) => new MapStream(stream, fn);
export const filter = (stream, predicate) => new FilterStream(stream, predicate);
export const merger = states => states.reduce((acc, v) => Object.assign(acc, v), {});

14
modules/lstream/map.js Normal file
View file

@ -0,0 +1,14 @@
import {StreamBase} from './base';
export class MapStream extends StreamBase {
constructor(stream, fn) {
super();
this.stream = stream;
this.fn = fn;
}
attach(observer) {
return this.stream.attach(v => observer(this.fn(v)));
}
}

View file

@ -0,0 +1,13 @@
import {NOT_INITIALIZED} from './utils';
export function memoize(fn) {
let value;
let lastArg = NOT_INITIALIZED;
return arg => {
if (arg !== lastArg) {
lastArg = arg;
value = fn(arg);
}
return value;
}
}

18
modules/lstream/merge.js Normal file
View file

@ -0,0 +1,18 @@
import {StreamBase} from './base';
export class MergeStream extends StreamBase {
constructor(streams) {
super();
this.streams = streams;
}
attach(observer) {
let detachers = new Array(this.streams.length);
this.streams.forEach((s, i) => {
detachers[i] = s.attach(observer);
});
return () => detachers.forEach(d => d());
}
}

View file

@ -0,0 +1,17 @@
import {StreamBase} from './base';
export class PairwiseStream extends StreamBase {
constructor(stream, first) {
super();
this.stream = stream;
this.latest = first;
}
attach(observer) {
return this.stream.attach(v => {
observer([this.latest, v]);
this.latest = v;
});
}
}

View file

@ -35,4 +35,3 @@ export class StateStream extends Emitter {
}
}

2
modules/lstream/utils.js Normal file
View file

@ -0,0 +1,2 @@
export const NOT_INITIALIZED = Object.freeze({});

View file

@ -0,0 +1,11 @@
const STATIC_RESOURCES = [];
export default function staticResource(resource) {
STATIC_RESOURCES.push(resource);
return resource;
}
window.addEventListener("beforeunload", function() {
STATIC_RESOURCES.forEach(r => r.dispose());
}, false);

10279
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,10 @@
interface IMObject {
}
IMFace
IMVertex

View file

@ -47,7 +47,7 @@ export class Line {
return new Line(this.p0.plus(vector), this.v);
}
approximate(resolution, from, to, path) {
tessellate(resolution, from, to, path) {
}
offset() {};

View file

@ -3,7 +3,7 @@ import * as stream from 'lstream';
export function activate(context) {
let {bus, streams} = context;
let {streams} = context;
streams.action = {
appearance: {},

View file

@ -1,8 +1,8 @@
import {subtract, union, intersect} from '../../brep/operations/boolean'
import {BREPSceneSolid} from '../scene/wrappers/brepSceneObject'
import {update as updateStitching} from '../../brep/stitching'
import {BREPValidator} from '../../brep/brep-validator'
import {Shell} from '../../brep/topo/shell'
import {intersect, subtract, union} from '../../brep/operations/boolean';
import {update as updateStitching} from '../../brep/stitching';
import {BREPValidator} from '../../brep/brep-validator';
import {Shell} from '../../brep/topo/shell';
import {MBrepShell} from '../model/mshell';
const BoolOpMap = {
'subtract': subtract,
@ -12,7 +12,7 @@ const BoolOpMap = {
export function BooleanOperation(face, solid, operand, operationType) {
let result;
if (solid instanceof BREPSceneSolid) {
if (solid instanceof MBrepShell) {
const op = BoolOpMap[operationType];
result = op(solid.shell, operand);
for (let newFace of result.faces) {
@ -25,7 +25,7 @@ export function BooleanOperation(face, solid, operand, operationType) {
result = operand;
}
updateStitching(result);
const newSolid = new BREPSceneSolid(result);
const newSolid = new MBrepShell(result);
return {
outdated: [solid],
created: [newSolid]

View file

@ -1,43 +1,22 @@
import {createToken} from "bus";
import * as SceneGraph from 'scene/sceneGraph';
import {EDGE, FACE, SKETCH_OBJECT} from '../scene/entites';
export function activate({bus, services}) {
export function activate({streams, services}) {
let registry = new Map();
streams.cadRegistry = {
shellIndex: streams.craft.models.map(models => models.reduce((i, v)=> i.set(v.id, v), new Map())).keep()
};
streams.cadRegistry.update = streams.cadRegistry.shellIndex;
function getAllShells() {
return Array.from(registry.values());
}
function update(toRemove, toAdd) {
if (toRemove) {
toRemove.forEach(shell => {
registry.delete(shell.tCadId);
SceneGraph.removeFromGroup(services.cadScene.workGroup, shell.cadGroup);
shell.dispose();
});
}
if (toAdd) {
toAdd.forEach(shell => {
registry.set(shell.tCadId, shell);
SceneGraph.addToGroup(services.cadScene.workGroup, shell.cadGroup);
});
}
services.viewer.render();
bus.dispatch(TOKENS.SHELLS, registry);
return streams.craft.models.value;
}
function reset() {
SOLIDS_COUNTER = 0;
update(getAllShells());
}
function findFace(faceId) {
let shells = getAllShells();
for (let shell of shells) {
for (let face of shell.sceneFaces) {
for (let face of shell.faces) {
if (face.id === faceId) {
return face;
}
@ -49,7 +28,7 @@ export function activate({bus, services}) {
function findEdge(edgeId) {
let shells = getAllShells();
for (let shell of shells) {
for (let edge of shell.sceneEdges) {
for (let edge of shell.edges) {
if (edge.id === edgeId) {
return edge;
}
@ -59,10 +38,10 @@ export function activate({bus, services}) {
}
function findSketchObject(sketchObjectGlobalId) {
let [faceId, sketchObjectId] = sketchObjectGlobalId.split('/');
let face = findFace(faceId);
let [shellId, faceId, sketchObjectId] = sketchObjectGlobalId.split('/');
let face = findFace(shellId+'/'+faceId);
if (face) {
return face.findById(sketchObjectGlobalId);
return face.findSketchObjectById(sketchObjectGlobalId);
}
return null;
}
@ -77,16 +56,11 @@ export function activate({bus, services}) {
}
services.cadRegistry = {
getAllShells, update, reset, findFace, findEdge, findSketchObject, findEntity
getAllShells, findFace, findEdge, findSketchObject, findEntity,
get shellIndex() {
return streams.cadRegistry.shellIndex.value;
}
}
}
export const TOKENS = {
SHELLS: createToken('cadRegistry', 'shells'),
};
let SOLIDS_COUNTER = 0;
export function genSolidId() {
return SOLIDS_COUNTER ++
}

View file

@ -1,68 +1,78 @@
import {createToken} from "bus";
import {addModification} from './craftHistoryUtils';
import {state, stream} from 'lstream';
import {MShell} from '../model/mshell';
export function activate({bus, services}) {
export function activate({streams, services}) {
bus.enableState(TOKENS.MODIFICATIONS, {
let initialState = {
history: [],
pointer: -1
});
};
function isAdditiveChange({history, pointer}, {history:oldHistory, pointer:oldPointer}) {
if (pointer < oldPointer) {
return false;
}
for (let i = 0; i <= oldPointer; i++) {
let modCurr = history[i];
let modPrev = oldHistory[i];
if (modCurr !== modPrev) {
return false;
}
}
return true;
}
bus.subscribe(TOKENS.MODIFICATIONS, (curr, prev) => {
let beginIndex;
if (isAdditiveChange(curr, prev)) {
beginIndex = prev.pointer + 1;
} else {
services.cadRegistry.reset();
beginIndex = 0;
}
let {history, pointer} = curr;
for (let i = beginIndex; i <= pointer; i++) {
modifyInternal(history[i]);
}
});
function modifyInternal(request) {
let op = services.operation.registry[request.type];
if (!op) return `unknown operation ${request.type}`;
let result = op.run(request.params, services);
services.cadRegistry.update(result.outdated, result.created);
}
streams.craft = {
modifications: state(initialState),
models: state([]),
update: stream()
};
function modify(request) {
bus.updateState(TOKENS.MODIFICATIONS, modifications => addModification(modifications, request));
streams.craft.modifications.update(modifications => addModification(modifications, request));
}
function reset(modifications) {
bus.dispatch(TOKENS.MODIFICATIONS, {
streams.craft.modifications.next({
history: modifications,
pointer: modifications.length - 1
});
}
services.craft = {
modify, reset, TOKENS
}
modify, reset
};
streams.craft.modifications.pairwise(initialState).attach(([prev, curr]) => {
let models;
let beginIndex;
if (isAdditiveChange(prev, curr)) {
beginIndex = prev.pointer + 1;
models = new Set(streams.craft.models.value);
} else {
MShell.ID_COUNTER = 0;
beginIndex = 0;
models = new Set()
}
if (prev === curr) {
return Array.from(models);
}
let {history, pointer} = curr;
for (let i = beginIndex; i <= pointer; i++) {
let request = history[i];
let op = services.operation.registry[request.type];
if (!op) {
console.log(`unknown operation ${request.type}`);
}
let {outdated, created} = op.run(request.params, services);
outdated.forEach(m => models.delete(m));
created.forEach(m => models.add(m));
streams.craft.models.next(Array.from(models).sort(m => m.id));
}
})
}
export const TOKENS = {
MODIFICATIONS: createToken('craft', 'modifications')
};
function isAdditiveChange({history:oldHistory, pointer:oldPointer}, {history, pointer}) {
if (pointer < oldPointer) {
return false;
}
for (let i = 0; i <= oldPointer; i++) {
let modCurr = history[i];
let modPrev = oldHistory[i];
if (modCurr !== modPrev) {
return false;
}
}
return true;
}

View file

@ -19,7 +19,7 @@ export function doOperation(params, {cadRegistry, sketcher}, cut) {
let sketch = sketcher.readSketch(face.id);
if (!sketch) throw 'illegal state';
let plane = face.surface().tangentPlane(0, 0);
let plane = face.surface.tangentPlane(0, 0);
const details = getEncloseDetails(params, sketch.fetchContours(), plane, !cut, false);
const operand = combineShells(details.map(d => enclose(d.basePath, d.lidPath, d.baseSurface, d.lidSurface)));
return BooleanOperation(face, solid, operand, cut ? 'subtract' : 'union');

View file

@ -14,7 +14,7 @@ export function createPreviewGeomProvider(inversed) {
if (!face) return null;
let sketch = face.sketch.fetchContours();
const encloseDetails = getEncloseDetails(params, sketch, face.surface().tangentPlane(0, 0), !inversed);
const encloseDetails = getEncloseDetails(params, sketch, face.surface.tangentPlane(0, 0), !inversed);
const triangles = [];
for (let {basePath, lidPath, baseSurface, lidSurface} of encloseDetails) {

View file

@ -1,11 +1,9 @@
import {createToken} from 'bus';
import {TOKENS as WIZARD_TOKENS} from './wizard/wizardPlugin'
export function activate(context) {
let {bus, services} = context;
let {services} = context;
let registry = {};
let registry = {};
function addOperation(descriptor, actions) {
let {id, label, info, icon, actionParams} = descriptor;
@ -17,7 +15,7 @@ export function activate(context) {
icon32: icon + '32.png',
icon96: icon + '96.png',
},
invoke: () => bus.dispatch(WIZARD_TOKENS.OPEN, {type: id}),
invoke: () => services.wizard.open({type: id}),
...actionParams
};
actions.push(opAction);
@ -34,7 +32,7 @@ export function activate(context) {
}
services.action.registerActions(actions);
}
function get(id) {
let op = registry[id];
if (!op) {
@ -42,12 +40,12 @@ export function activate(context) {
}
return op;
}
services.operation = {
registerOperations,
registry,
get
}
};
}
function runOperation(request, descriptor, services) {
@ -57,5 +55,5 @@ function runOperation(request, descriptor, services) {
return result;
}
}
return descriptor.run(request, services)
return descriptor.run(request, services);
}

View file

@ -1,15 +1,9 @@
// import {MESH_OPERATIONS} from './mesh/workbench'
// import {Extrude, Cut} from './brep/cut-extrude'
// import {Revolve} from './brep/revolve'
import {BREPSceneSolid} from '../scene/wrappers/brepSceneObject'
// import {PlaneSceneObject} from '../scene/wrappers/planeSceneObject'
import {box} from '../../brep/brep-primitives'
export const REVOLVE = {
icon: 'img/cad/revolve',
label: 'Revolve',
info: (p) => '(' + p.angle + ')',
action: (app, params) => Revolve(app, params)
action: (app, params) => console.log(app, params)
};
export const SHELL = {

View file

@ -1,12 +1,12 @@
import {box} from '../../../brep/brep-primitives'
import {BREPSceneSolid} from '../../scene/wrappers/brepSceneObject';
import {createBoxGeometry} from "scene/geoms";
import {box} from '../../../brep/brep-primitives';
import {createBoxGeometry} from 'scene/geoms';
import BoxWizard from './BoxWizard';
import {MBrepShell} from '../../model/mshell';
function createBox({width, height, depth}) {
return {
outdated: [],
created: [new BREPSceneSolid(box(width, height, depth))]
created: [new MBrepShell(box(width, height, depth))]
}
}

View file

@ -1,9 +1,10 @@
import {createMeshGeometry} from 'scene/geoms';
import {STANDARD_BASES} from '../../../math/l3space';
import {Plane} from '../../../brep/geom/impl/plane';
import {PlaneSceneObject} from '../../scene/wrappers/planeSceneObject';
import Vector from 'math/vector';
import PlaneWizard from './PlaneWizard';
import {MOpenFaceShell} from '../../model/mopenFace';
import {createBoundingSurfaceFrom2DPoints} from '../../../brep/brep-builder';
function paramsToPlane({orientation, parallelTo, depth}, cadRegistry) {
let face = null;
@ -15,15 +16,18 @@ function paramsToPlane({orientation, parallelTo, depth}, cadRegistry) {
const normal = STANDARD_BASES[orientation][2];
plane = new Plane(normal, depth);
} else {
plane = new Plane(face.surface().normalInMiddle(), depth);
plane = new Plane(face.surface.normalInMiddle(), depth);
}
return plane;
}
function createPlane(params, {cadRegistry}) {
let surface = createBoundingSurfaceFrom2DPoints([
new Vector(0,0,0), new Vector(0,100,0), new Vector(100,100,0), new Vector(100,0,0)
], paramsToPlane(params, cadRegistry));
return {
outdated: [],
created: [new PlaneSceneObject(paramsToPlane(params, cadRegistry))]
created: [new MOpenFaceShell(surface)]
}
}

View file

@ -1,8 +1,10 @@
import React from 'react';
import connect from '../../../../../../modules/ui/connectLegacy';
import {TOKENS as CRAFT_TOKENS} from '../../craftPlugin';
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}) {
if (pointer === history.length - 1) {
@ -16,11 +18,10 @@ function HistoryWizard({history, pointer, step, cancel, offset}) {
}
export default connect(HistoryWizard, CRAFT_TOKENS.MODIFICATIONS, {
mapActions: ({updateState}) => ({
step: (params) => updateState(CRAFT_TOKENS.MODIFICATIONS, modifications => stepOverridingParams(modifications, params)),
cancel: () => updateState(CRAFT_TOKENS.MODIFICATIONS, modifications => finishHistoryEditing(modifications)),
})
});
const NOOP = () => {};
export default decoratorChain(
connect(streams => streams.craft.modifications),
mapContext(({streams}) => ({
step: params => streams.craft.modifications.update(modifications => stepOverridingParams(modifications, params)),
cancel: () => streams.craft.modifications.update(modifications => finishHistoryEditing(modifications)),
}))
)(HistoryWizard);

View file

@ -4,7 +4,6 @@ import Window from 'ui/components/Window';
import Stack from 'ui/components/Stack';
import Button from 'ui/components/controls/Button';
import ButtonGroup from 'ui/components/controls/ButtonGroup';
import {CURRENT_SELECTION} from '../wizardPlugin';
import ls from './Wizard.less';
import CadError from '../../../../utils/errors';
@ -42,7 +41,15 @@ export default class Wizard extends React.Component {
this.formContext.onChange = noop;
}
componentDidCatch() {
this.setState({hasInternalError: true});
}
render() {
if (this.state.hasInternalError) {
return <span>operation error</span>;
}
let {type, left} = this.props;
let {wizard: WizardImpl} = this.context.services.operation.get(type);
@ -62,7 +69,7 @@ export default class Wizard extends React.Component {
</ButtonGroup>
{this.state.hasError && <div className={ls.errorMessage}>
performing operation with current parameters leads to an invalid object
(manifold / self-intersecting / zero-thickness / complete degeneration or unsupported cases)
(self-intersecting / zero-thickness / complete degeneration or unsupported cases)
{this.state.code && <div className={ls.errorCode}>{this.state.code}</div>}
{this.state.userMessage && <div className={ls.userErrorMessage}>{this.state.userMessage}</div>}
</div>}
@ -131,8 +138,7 @@ export default class Wizard extends React.Component {
static contextTypes = {
services: PropTypes.object,
bus: PropTypes.object
services: PropTypes.object
};
}

View file

@ -1,32 +1,51 @@
import React, {Fragment} from 'react';
import {TOKENS as WIZARD_TOKENS} from '../wizardPlugin';
import connect from 'ui/connectLegacy';
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} from '../../craftHistoryUtils';
function WizardManager({wizards, close}) {
return <Fragment>
{wizards.map((wizardRef, wizardIndex) => {
let {type, initialState} = wizardRef;
class WizardManager extends React.Component {
render() {
if (this.hasError) {
return null;
}
let {wizards, close} = this.props;
return <Fragment>
{wizards.map((wizardRef, wizardIndex) => {
let {type, initialState} = wizardRef;
const closeInstance = () => close(wizardRef);
return <Wizard key={wizardIndex}
type={type}
close={closeInstance}
initialState={initialState} left={offset(wizardIndex)} />
})}
<HistoryWizard offset={offset(wizards.length)}/>
</Fragment>
const closeInstance = () => close(wizardRef);
return <Wizard key={wizardIndex}
type={type}
close={closeInstance}
initialState={initialState} left={offset(wizardIndex)} />
})}
<HistoryWizard offset={offset(wizards.length)}/>
</Fragment>;
}
componentDidCatch() {
this.hasError = true;
this.props.reset();
this.hasError = false;
}
}
function offset(wizardIndex) {
return 70 + (wizardIndex * (250 + 20));
}
export default connect(WizardManager, WIZARD_TOKENS.WIZARDS, {
mapProps: ([wizards]) => ({wizards}),
mapActions: ({dispatch}) => ({
close: wizard => dispatch(WIZARD_TOKENS.CLOSE, wizard)
})
});
export default decoratorChain(
connect(streams => streams.wizards.map(wizards => ({wizards}))),
mapContext(({services, streams}) => ({
close: wizard => services.wizard.close(wizard),
reset: () => {
streams.wizards.value = [];
streams.craft.modifications.update(modifications => finishHistoryEditing(modifications));
}
}))
)
(WizardManager);

View file

@ -1,6 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import {entitySelectionToken} from '../../../../scene/controls/pickControlPlugin';
import {attachToForm} from './Form';
import Stack from 'ui/components/Stack';
import {FormContext} from '../form/Form';

View file

@ -1,6 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import {entitySelectionToken} from '../../../../scene/controls/pickControlPlugin';
import {attachToForm} from './Form';
import mapContext from 'ui/mapContext';
@ -17,8 +16,10 @@ class SingleEntityImpl extends React.Component {
selectionChanged = selection => {
let selectedItem = selection[0];
this.setState({selectedItem});
this.props.onChange(selectedItem);
if (selectedItem) {
this.setState({selectedItem});
this.props.onChange(selectedItem);
}
};
componentDidMount() {

View file

@ -1,27 +1,25 @@
import {createToken} from 'bus';
import {state} from '../../../../../modules/lstream';
export function activate({bus, services}) {
export function activate({streams, services}) {
bus.enableState(TOKENS.WIZARDS, []);
bus.subscribe(TOKENS.OPEN, ({type, initialState, overridingHistory}) => {
let wizard = {
type,
initialState,
overridingHistory,
};
bus.updateState(TOKENS.WIZARDS, opened => [...opened, wizard])
});
streams.wizards = state([]);
bus.subscribe(TOKENS.CLOSE, wizard => {
bus.updateState(TOKENS.WIZARDS, opened => opened.filter(w => w !== wizard));
});
services.wizard = {
open: ({type, initialState, overridingHistory}) => {
let wizard = {
type,
initialState,
overridingHistory,
};
streams.wizards.update(opened => [...opened, wizard]);
},
close: wizard => {
streams.wizards.update(opened => opened.filter(w => w !== wizard));
}
}
}
export const TOKENS = {
WIZARDS: createToken('wizards'),
OPEN: createToken('wizards', 'open'),
CLOSE: createToken('wizards', 'close'),
PARAMS: createToken('wizardParams')
};

View file

@ -1,24 +1,23 @@
import React from 'react';
import PropTypes from 'prop-types';
import Stack from 'ui/components/Stack';
import connect from 'ui/connectLegacy';
import connect from 'ui/connect';
import Fa from 'ui/components/Fa';
import ImgIcon from 'ui/components/ImgIcon';
import ls from './OperationHistory.less';
import cx from 'classnames';
import ButtonGroup from 'ui/components/controls/ButtonGroup';
import Button from 'ui/components/controls/Button';
import {finishHistoryEditing, removeAndDropDependants} from '../../craft/craftHistoryUtils';
import mapContext from 'ui/mapContext';
import decoratorChain from 'ui/decoratorChain';
import {TOKENS as CRAFT_TOKENS} from '../../craft/craftPlugin';
import ButtonGroup from '../../../../../modules/ui/components/controls/ButtonGroup';
import Button from '../../../../../modules/ui/components/controls/Button';
import {removeAndDropDependants} from '../../craft/craftHistoryUtils';
function OperationHistory({history, pointer, setHistoryPointer, remove}, {services: {operation: operationService}}) {
function OperationHistory({history, pointer, setHistoryPointer, remove, operationRegistry}) {
let lastMod = history.length - 1;
return <Stack>
{history.map(({type, params}, index) => {
let {appearance, paramsInfo} = getDescriptor(type, operationService.registry);
let {appearance, paramsInfo} = getDescriptor(type, operationRegistry);
return <div key={index} onClick={() => setHistoryPointer(index - 1)}
className={cx(ls.item, pointer + 1 === index && ls.selected)}>
{appearance && <ImgIcon url={appearance.icon32} size={16}/>}
@ -45,13 +44,12 @@ function getDescriptor(type, registry) {
return descriptor;
}
OperationHistory.contextTypes = {
services: PropTypes.object
};
export default decoratorChain(
connect(streams => streams.craft.modifications),
mapContext(({streams, services}) => ({
remove: atIndex => streams.craft.modifications.update(modifications => removeAndDropDependants(modifications, atIndex)),
cancel: () => streams.craft.modifications.update(modifications => finishHistoryEditing(modifications)),
operationRegistry: services.operation.registry
}))
)(OperationHistory);
export default connect(OperationHistory, CRAFT_TOKENS.MODIFICATIONS, {
mapActions: ({setState, updateState}) => ({
setHistoryPointer: pointer => setState(CRAFT_TOKENS.MODIFICATIONS, {pointer}),
remove: atIndex => updateState(CRAFT_TOKENS.MODIFICATIONS, modifications => removeAndDropDependants(modifications, atIndex))
})
});

View file

@ -1,6 +1,6 @@
import {state} from '../../../../../modules/lstream';
export function activate({bus, services, streams}) {
export function activate({services, streams}) {
streams.ui.menu = {
all: state([]),

View file

@ -1,6 +1,6 @@
import {state} from 'lstream';
export function activate({bus, streams}) {
export function activate({streams}) {
streams.ui = {
controlBars: {

View file

@ -1,20 +1,23 @@
import {createToken} from '../../../../modules/bus';
import {state} from '../../../../modules/lstream';
export function activate({bus, services}) {
export function activate({streams, services}) {
const startTime = performance.now();
bus.enableState(APP_READY_TOKEN, false);
bus.enableState(APP_PROJECT_LOADED, false);
streams.lifecycle = {
appReady: state(false),
projectLoaded: state(false)
};
services.lifecycle = {
loadProjectRequest: () => {
if (bus.state[APP_READY_TOKEN] && !bus.state[APP_PROJECT_LOADED] && services.craftEngines.allEnginesReady()) {
if (streams.lifecycle.appReady.value &&
!streams.lifecycle.projectLoaded.value &&
services.craftEngines.allEnginesReady()) {
services.project.load();
bus.dispatch(APP_PROJECT_LOADED, true);
streams.lifecycle.projectLoaded.value = true;
const onLoadTime = performance.now();
console.log("project loaded, took: " + ((onLoadTime - startTime) / 1000).toFixed(2) + ' sec');
}
}
},
declareAppReady: () => streams.lifecycle.appReady.value = true
}
}
export const APP_READY_TOKEN = createToken('app', 'ready');
export const APP_PROJECT_LOADED = createToken('app', 'projectLoaded');

View file

@ -19,11 +19,11 @@ import * as SketcherPlugin from '../sketch/sketcherPlugin';
import * as tpiPlugin from '../tpi/tpiPlugin';
import * as PartModellerPlugin from '../part/partModellerPlugin';
import * as ViewSyncPlugin from '../scene/viewSyncPlugin';
import context from 'context';
import startReact from "../dom/startReact";
import {APP_READY_TOKEN} from './lifecyclePlugin';
export default function startApplication(callback) {
@ -41,9 +41,8 @@ export default function startApplication(callback) {
WizardPlugin,
CraftEnginesPlugin,
OperationPlugin,
CadRegistryPlugin,
CraftPlugin,
SketcherPlugin,
CadRegistryPlugin,
tpiPlugin
];
@ -52,14 +51,16 @@ export default function startApplication(callback) {
ScenePlugin,
PickControlPlugin,
SelectionMarkerPlugin,
SketcherPlugin,
...applicationPlugins,
ViewSyncPlugin
];
activatePlugins(preUIPlugins, context);
startReact(context, () => {
activatePlugins(plugins, context);
context.bus.dispatch(APP_READY_TOKEN, true);
context.services.lifecycle.declareAppReady();
context.services.viewer.render();
callback(context);
});

View file

@ -6,7 +6,7 @@ export function Revolve(app, params) {
const face = app.findFace(params.face);
const solid = face.solid;
const surface = face.surface();
const surface = face.surface;
const sketch = ReadSketchFromFace(app, face);
const pivot = evalPivot(params.pivot, sketch, surface);

View file

@ -0,0 +1,14 @@
import {MObject} from './mobject';
export class MEdge extends MObject {
static TYPE = 'edge';
constructor(id, shell, brepEdge) {
super();
this.id = id;
this.shell = shell;
this.brepEdge = brepEdge;
}
}

View file

@ -0,0 +1,97 @@
import {MObject} from './mobject';
import Vector from 'math/vector';
import {BasisForPlane} from '../../math/l3space';
import {MSketchObject} from './msketchObject';
import {EMPTY_ARRAY} from 'gems/iterables';
export class MFace extends MObject {
static TYPE = 'face';
constructor(id, shell, surface) {
super(id);
this.id = id;
this.shell = shell;
this.surface = surface;
this.sketchObjects = [];
}
normal() {
return this.surface.normalInMiddle();
}
depth() {
return this.surface.tangentPlaneInMiddle().w;
}
calcBasis() {
return BasisForPlane(this.normal());
};
basis() {
if (!this._basis) {
this._basis = this.calcBasis();
}
return this._basis;
}
setSketch(sketch) {
this.sketch = sketch;
this.sketchObjects = [];
const addSketchObjects = sketchObjects => {
let isConstruction = sketchObjects === sketch.constructionSegments;
for (let sketchObject of sketchObjects) {
let mSketchObject = new MSketchObject(this, sketchObject);
mSketchObject.construction = isConstruction;
this.sketchObjects.push(mSketchObject);
}
};
addSketchObjects(sketch.constructionSegments);
addSketchObjects(sketch.connections);
addSketchObjects(sketch.loops);
}
findSketchObjectById(sketchObjectId) {
for (let o of this.sketchObjects) {
if (o.id === sketchObjectId) {
return o;
}
}
}
getBounds() {
return EMPTY_ARRAY;
}
get sketchToWorldTransformation() {
if (!this._sketchToWorldTransformation) {
this._sketchToWorldTransformation = this.surface.tangentPlaneInMiddle().get3DTransformation();
}
return this._sketchToWorldTransformation;
}
get worldToSketchTransformation() {
if (!this._worldToSketchTransformation) {
this._worldToSketchTransformation = this.sketchToWorldTransformation.invert();
}
return this._worldToSketchTransformation;
}
}
export class MBrepFace extends MFace {
constructor(id, shell, brepFace) {
super(id, shell, brepFace.surface);
this.id = id;
this.brepFace = brepFace;
}
getBounds() {
const bounds = [];
for (let loop of this.brepFace.loops) {
bounds.push(loop.asPolygon().map(p => new Vector().setV(p)));
}
return bounds;
}
}

View file

@ -0,0 +1,8 @@
export class MObject {
id;
ext = {}
}

View file

@ -0,0 +1,15 @@
import {MShell} from './mshell';
import {MFace} from './mface';
export class MOpenFaceShell extends MShell {
constructor(surface) {
super();
this.faces.push(new MFace(this.id + '/SURFACE', this, surface))
}
get face() {
return this.faces[0];
}
}

View file

@ -0,0 +1,44 @@
import {MObject} from './mobject';
import {MBrepFace, MFace} from './mface';
import {MEdge} from './medge';
import {MVertex} from './mvertex';
export class MShell extends MObject {
static TYPE = 'shell';
static ID_COUNTER = 0;
id = 'S:' + (MShell.ID_COUNTER++);
shell;
faces = [];
edges = [];
vertices = [];
}
export class MBrepShell extends MShell {
constructor(shell) {
super();
this.brepShell = shell;
let faceCounter = 0;
let edgeCounter = 0;
let vertexCounter = 0;
for (let brepFace of this.brepShell.faces) {
const mFace = new MBrepFace(this.id + '/F:' + faceCounter++, this, brepFace);
this.faces.push(mFace);
}
for (let brepEdge of this.brepShell.edges) {
const mEdge = new MEdge(this.id + '/E:' + edgeCounter++, this, brepEdge);
this.edges.push(mEdge);
}
for (let brepVertex of this.brepShell.vertices) {
const mVertex = new MVertex(this.id + '/V:' + vertexCounter++, this, brepVertex);
this.vertices.push(mVertex);
}
}
}

View file

@ -0,0 +1,15 @@
import {MObject} from './mobject';
export class MSketchObject extends MObject {
static TYPE = 'sketchObject';
constructor(face, sketchPrimitive) {
super();
this.id = sketchPrimitive.id;
this.face = face;
this.sketchPrimitive = sketchPrimitive;
this.construction = false;
}
}

View file

@ -0,0 +1,14 @@
import {MObject} from './mobject';
export class MVertex extends MObject {
static TYPE = 'vertex';
constructor(id, shell, brepVertex) {
super();
this.id = id;
this.shell = shell;
this.brepVertex = brepVertex;
}
}

View file

@ -3,7 +3,7 @@ import OperationActions from '../actions/operationActions';
import HistoryActions from '../actions/historyActions';
import menuConfig from './menuConfig';
export function activate({bus, services, streams}) {
export function activate({services, streams}) {
streams.ui.controlBars.left.value = ['menu.file', 'menu.craft', 'menu.boolean', 'menu.primitives', 'Donate', 'GitHub'];
streams.ui.controlBars.right.value = [
['Info', {label: null}],

View file

@ -7,7 +7,7 @@ const STORAGE_PREFIX = `${STORAGE_GLOBAL_PREFIX}.projects.`;
export function activate(context) {
const {services, bus} = context;
const {streams, services} = context;
const [id, params] = parseHintsFromLocation();
@ -30,7 +30,7 @@ export function activate(context) {
function save() {
let data = {};
data.history = bus.state[services.craft.TOKENS.MODIFICATIONS].history;
data.history = streams.craft.modifications.value.history;
services.storage.set(projectStorageKey(), JSON.stringify(data));
}

View file

@ -210,8 +210,8 @@ export function runSandbox({bus, services: { viewer, cadScene, cadRegistry, tpi,
addShellOnScene(cylinder);
addShellOnScene(box);
let surfaceA = cadRegistry.findFace('0:0').surface();
let surfaceB = cadRegistry.findFace('1:4').surface();
let surfaceA = cadRegistry.findFace('0:0').surface;
let surfaceB = cadRegistry.findFace('1:4').surface;
let curves = surfaceIntersect(surfaceA.data, surfaceB.data);

View file

@ -47,20 +47,19 @@ export function activate(context) {
}
function handlePick(event) {
raycastObjects(event, PICK_KIND.FACE | PICK_KIND.SKETCH | PICK_KIND.EDGE, (object, kind) => {
raycastObjects(event, PICK_KIND.FACE | PICK_KIND.SKETCH | PICK_KIND.EDGE, (view, kind) => {
let modelId = view.model.id;
if (kind === PICK_KIND.FACE) {
if (!selected(streams.selection.face, object.id)) {
services.cadScene.showBasis(object.basis(), object.depth());
streams.selection.face.next([object.id]);
if (dispatchSelection(streams.selection.face, modelId, event)) {
services.cadScene.showBasis(view.model.basis(), view.model.depth());
return false;
}
} else if (kind === PICK_KIND.SKETCH) {
if (!selected(streams.selection.sketchObject, object.id)) {
streams.selection.sketchObject.next([object.id]);
if (dispatchSelection(streams.selection.sketchObject, modelId, event)) {
return false;
}
} else if (kind === PICK_KIND.EDGE) {
if (dispatchSelection(streams.selection.edge, object.id, event)) {
if (dispatchSelection(streams.selection.edge, modelId, event)) {
return false;
}
}
@ -90,27 +89,27 @@ export function activate(context) {
const pickers = [
(pickResult) => {
if (mask.is(kind, PICK_KIND.SKETCH) && pickResult.object instanceof THREE.Line) {
let sketchObject = getAttribute(pickResult.object, 'sketchObject');
if (sketchObject) {
return !visitor(sketchObject, PICK_KIND.SKETCH);
let sketchObjectV = getAttribute(pickResult.object, SKETCH_OBJECT);
if (sketchObjectV) {
return !visitor(sketchObjectV, PICK_KIND.SKETCH);
}
}
return false;
},
(pickResult) => {
if (mask.is(kind, PICK_KIND.EDGE)) {
let cadEdge = getAttribute(pickResult.object, 'edge');
if (cadEdge) {
return !visitor(cadEdge, PICK_KIND.EDGE);
let edgeV = getAttribute(pickResult.object, EDGE);
if (edgeV) {
return !visitor(edgeV, PICK_KIND.EDGE);
}
}
return false;
},
(pickResult) => {
if (mask.is(kind, PICK_KIND.FACE) && !!pickResult.face) {
let sketchFace = getAttribute(pickResult.face, 'face');
if (sketchFace) {
return !visitor(sketchFace, PICK_KIND.FACE);
let faceV = getAttribute(pickResult.face, FACE);
if (faceV) {
return !visitor(faceV, PICK_KIND.FACE);
}
}
return false;
@ -149,6 +148,11 @@ function initStateAndServices({streams, services}) {
});
entitySelectApi.select = selection => selectionState.value = selection;
});
//withdraw all
streams.craft.models.attach(() => {
Object.values(streams.selection).forEach(ss => ss.next([]))
})
}

View file

@ -1,9 +1,14 @@
export const SOLID = 'solid';
export const FACE = 'face';
export const EDGE = 'edge';
export const VERTEX = 'vertex';
export const SKETCH_OBJECT = 'sketchObject';
import {MShell} from '../model/mshell';
import {MFace} from '../model/mface';
import {MEdge} from '../model/medge';
import {MVertex} from '../model/mvertex';
import {MSketchObject} from '../model/msketchObject';
const ENTITIES = [SOLID, FACE, EDGE, VERTEX, SKETCH_OBJECT];
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 default ENTITIES;
export const PART_MODELING_ENTITIES = [SHELL, FACE, EDGE, VERTEX, SKETCH_OBJECT];
export const ASSEMBLY_ENTITIES = [SHELL, FACE, EDGE, VERTEX];

View file

@ -1,13 +1,16 @@
import Viewer from './viewer';
import CadScene from "./cadScene";
import CadScene from './cadScene';
import {externalState} from '../../../../modules/lstream';
export function activate(context) {
let {dom} = context.services;
export function activate({streams, services}) {
let {dom} = services;
let viewer = new Viewer(context.bus, dom.viewerContainer);
let viewer = new Viewer(dom.viewerContainer);
context.services.viewer = viewer;
context.services.cadScene = new CadScene(viewer.sceneSetup.rootGroup);
services.viewer = viewer;
services.cadScene = new CadScene(viewer.sceneSetup.rootGroup);
context.bus.subscribe('scene:update', () => viewer.render());
streams.cadScene = {
cameraMode: externalState(() => viewer.getCameraMode(), mode => viewer.setCameraMode(mode))
};
}

View file

@ -1,12 +1,29 @@
import DPR from 'dpr';
import {SelectionMarker} from './selectionMarker';
import {SketchSelectionMarker} from './sketchSelectionMarker';
import {EdgeSelectionMarker} from './edgeSelectionMarker';
import {createLineMaterial} from 'scene/materials';
import {EDGE, FACE, SKETCH_OBJECT} from '../entites';
import {findDiff} from '../../../../../modules/gems/iterables';
export function activate(context) {
new SelectionMarker(context, 0xFAFAD2, 0xFF0000, null);
new SketchSelectionMarker(context, createLineMaterial(0xFF0000, 6 / DPR));
new EdgeSelectionMarker(context, 0xFA8072);
export function activate({streams, services}) {
let selectionSync = entity => ([old, curr]) => {
let [, toWithdraw, toMark] = findDiff(old, curr);
toWithdraw.forEach(id => {
let model = services.cadRegistry.findEntity(entity, id);
if (model) {
model.ext.view.withdraw();
}
});
toMark.forEach(id => {
let model = services.cadRegistry.findEntity(entity, id);
if (model) {
model.ext.view.mark();
}
});
services.viewer.render();
};
streams.selection.face.pairwise([]).attach(selectionSync(FACE));
streams.selection.edge.pairwise([]).attach(selectionSync(EDGE));
streams.selection.sketchObject.pairwise([]).attach(selectionSync(SKETCH_OBJECT));
// new SelectionMarker(context, 0xFAFAD2, 0xFF0000, null);
// new SketchSelectionMarker(context, createLineMaterial(0xFF0000, 6 / DPR));
// new EdgeSelectionMarker(context, 0xFA8072);
}

View file

@ -0,0 +1,47 @@
import * as SceneGraph from '../../../../modules/scene/sceneGraph';
import {ShellView} from './views/shellView';
import {getAttribute} from '../../../../modules/scene/objectData';
import {MOpenFaceShell} from '../model/mopenFace';
import {EDGE, FACE, SHELL, SKETCH_OBJECT} from './entites';
import {OpenFaceShellView} from './views/openFaceView';
import {findDiff} from '../../../../modules/gems/iterables';
export function activate(context) {
let {streams} = context;
streams.cadRegistry.update.attach(sceneSynchronizer(context));
streams.sketcher.update.attach(mFace => mFace.ext.view.updateSketch());
}
function sceneSynchronizer({services: {cadScene, cadRegistry}}) {
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);
if (shellView) {
let exists = cadRegistry.shellIndex.has(shellView.model.id);
if (!exists) {
SceneGraph.removeFromGroup(cadScene.workGroup, obj);
shellView.dispose();
} else {
existent.add(shellView.shell.id);
}
}
}
let allShells = cadRegistry.getAllShells();
for (let shell of allShells) {
if (!existent.has(shell.id)) {
let shellView;
if (shell instanceof MOpenFaceShell) {
shellView = new OpenFaceShellView(shell);
} else {
shellView = new ShellView(shell);
}
SceneGraph.addToGroup(cadScene.workGroup, shellView.rootGroup);
}
}
}
}

View file

@ -2,8 +2,7 @@ import SceneSetup from 'scene/sceneSetup';
export default class Viewer {
constructor(bus, container) {
this.bus = bus;
constructor(container) {
this.sceneSetup = new SceneSetup(container);
this.renderRequested = false;
}
@ -31,13 +30,14 @@ export default class Viewer {
}
setCameraMode(mode) {
if (this.getCameraMode() === mode) {
return;
}
if (mode === CAMERA_MODE.PERSPECTIVE) {
this.sceneSetup.setCamera(this.sceneSetup.pCamera);
} else {
this.sceneSetup.setCamera(this.sceneSetup.oCamera);
}
this.bus.dispatch('scene_cameraMode', this.getCameraMode());
}
getCameraMode() {

View file

@ -0,0 +1,109 @@
import {View} from './view';
import * as vec from '../../../math/vec';
import {setAttribute} from '../../../../../modules/scene/objectData';
import {perpendicularVector} from '../../../math/math';
import * as SceneGraph from '../../../../../modules/scene/sceneGraph';
import {EDGE} from '../entites';
export class EdgeView extends View {
constructor(edge) {
super(edge);
this.rootGroup = SceneGraph.createGroup();
const doEdge = (edge, aux, width, color, opacity) => {
const geometry = new THREE.Geometry();
const scaleTargets = [];
geometry.dynamic = true;
let materialParams = {
color,
vertexColors: THREE.FaceColors,
shininess: 0,
visible: !aux,
morphTargets: true
};
if (opacity !== undefined) {
materialParams.transparent = true;
materialParams.opacity = opacity;
}
let tess = edge.data.tesselation ? edge.data.tesselation : edge.curve.tessellateToData();
let base = null;
for (let i = 1; i < tess.length; i++) {
let a = tess[i - 1];
let b = tess[i];
let ab = vec._normalize(vec.sub(b, a));
let dirs = [];
dirs[0] = perpendicularVector(ab);
dirs[1] = vec.cross(ab, dirs[0]);
dirs[2] = vec.negate(dirs[0]);
dirs[3] = vec.negate(dirs[1]);
dirs.forEach(d => vec._mul(d, width));
if (base === null) {
base = dirs.map(d => vec.add(a, d));
}
let lid = dirs.map(d => vec.add(b, d));
let off = geometry.vertices.length;
base.forEach(p => geometry.vertices.push(vThree(p)));
lid.forEach(p => geometry.vertices.push(vThree(p)));
function addScaleTargets(points, origin) {
points.forEach(p => scaleTargets.push(vThree(vec._add(vec._mul(vec.sub(p, origin), 10), origin))));
}
addScaleTargets(base, a);
addScaleTargets(lid, b);
base = lid;
[
[0, 4, 3],
[3, 4, 7],
[2, 3, 7],
[7, 6, 2],
[0, 1, 5],
[5, 4, 0],
[1, 2, 6],
[6, 5, 1],
].forEach(([a, b, c]) => geometry.faces.push(new THREE.Face3(a + off, b + off, c + off)));
}
geometry.morphTargets.push( { name: "scaleTargets", vertices: scaleTargets } );
geometry.computeFaceNormals();
let mesh = new THREE.Mesh(geometry, new THREE.MeshPhongMaterial(materialParams));
this.rootGroup.add(mesh);
// mesh.morphTargetInfluences[ 0 ] = 0.2;
return mesh;
};
this.representation = doEdge(edge.brepEdge, false, 1, 0x2B3856);
this.marker = doEdge(edge.brepEdge, true, 3, 0xFA8072, 0.8);
setAttribute(this.representation, EDGE, this);
setAttribute(this.marker, EDGE, this);
}
mark(color) {
this.marker.material.visible = true;
}
withdraw(color) {
this.marker.material.visible = false;
}
dispose() {
this.representation.geometry.dispose();
this.representation.material.dispose();
this.marker.geometry.dispose();
this.marker.material.dispose();
super.dispose();
}
}
const vThree = arr => new THREE.Vector3().fromArray(arr);

View file

@ -0,0 +1,87 @@
import {setAttribute} from '../../../../../modules/scene/objectData';
import {brepFaceToGeom, tessDataToGeom} from '../wrappers/brepSceneObject';
import {FACE} from '../entites';
import * as SceneGraph from '../../../../../modules/scene/sceneGraph';
import {SketchObjectView} from './sketchObjectView';
import {View} from './view';
export class SketchingView extends View {
constructor(face) {
super(face);
this.sketchGroup = SceneGraph.createGroup();
this.sketchObjectViews = [];
this.rootGroup = SceneGraph.createGroup();
SceneGraph.addToGroup(this.rootGroup, this.sketchGroup);
}
updateSketch() {
SceneGraph.clearGroup(this.sketchGroup);
this.disposeSketch();
this.sketchObjectViews = [];
const sketchTr = this.model.sketchToWorldTransformation;
for (let sketchObject of this.model.sketchObjects) {
let sov = new SketchObjectView(sketchObject, sketchTr);
SceneGraph.addToGroup(this.sketchGroup, sov.rootGroup);
}
}
disposeSketch() {
for (let sov of this.sketchObjectViews) {
sov.dispose();
}
}
dispose() {
this.disposeSketch();
super.dispose();
}
}
export class FaceView extends SketchingView {
constructor(face, geometry) {
super(face);
this.geometry = geometry;
this.meshFaces = [];
let off = geometry.faces.length;
if (face.brepFace.data.tesselation) {
tessDataToGeom(face.brepFace.data.tesselation.data, geometry)
} else {
brepFaceToGeom(face, geometry);
}
for (let i = off; i < geometry.faces.length; i++) {
const meshFace = geometry.faces[i];
this.meshFaces.push(meshFace);
setAttribute(meshFace, FACE, this);
}
}
mark(color) {
this.setColor(color || SELECTION_COLOR);
}
withdraw(color) {
this.setColor(null);
}
setColor(color) {
setFacesColor(this.meshFaces, color);
this.geometry.colorsNeedUpdate = true;
}
}
export function setFacesColor(faces, color) {
for (let face of faces) {
if (color === null) {
face.color.set(NULL_COLOR);
} else {
face.color.set( color );
}
}
}
export const NULL_COLOR = new THREE.Color();
export const SELECTION_COLOR = 0xFAFAD2;

View file

@ -0,0 +1,106 @@
import Vector from '../../../../../modules/math/vector';
import {setAttribute} from '../../../../../modules/scene/objectData';
import {FACE, SHELL} from '../entites';
import {SELECTION_COLOR, setFacesColor, SketchingView} from './faceView';
import {View} from './view';
const INIT_WIDTH_H = 750 * 0.5;
const INIT_HEIGHT_H = 750 * 0.5;
export const INIT_BOUNDS = [
new Vector(-INIT_WIDTH_H, -INIT_HEIGHT_H, 0),
new Vector( INIT_WIDTH_H, -INIT_HEIGHT_H, 0),
new Vector( INIT_WIDTH_H, INIT_HEIGHT_H, 0),
new Vector(-INIT_WIDTH_H, INIT_HEIGHT_H, 0)
];
export class OpenFaceShellView extends View {
constructor(shell) {
super(shell);
this.openFace = new OpenFaceView(shell.face);
setAttribute(this.rootGroup, SHELL, this)
}
get rootGroup() {
return this.openFace.rootGroup
}
dispose() {
this.openFace.dispose();
}
}
export class OpenFaceView extends SketchingView {
constructor(mFace) {
super(mFace);
this.material = new THREE.MeshPhongMaterial({
vertexColors: THREE.FaceColors,
color: 0xB0C4DE,
shininess: 0,
polygonOffset : true,
polygonOffsetFactor : 1,
polygonOffsetUnits : 2,
side : THREE.DoubleSide,
transparent: true,
opacity: 0.5
});
this.updateBounds(INIT_BOUNDS);
}
dropGeometry() {
if (this.mesh) {
this.rootGroup.remove( this.mesh );
this.mesh.geometry.dispose();
}
}
createGeometry() {
const geometry = new THREE.Geometry();
geometry.dynamic = true;
this.bounds.forEach(v => geometry.vertices.push(v.three()));
geometry.faces.push(new THREE.Face3(0, 1, 2));
geometry.faces.push(new THREE.Face3(0, 2, 3));
geometry.faces.forEach(f => setAttribute(f, FACE, this));
geometry.computeFaceNormals();
this.mesh = new THREE.Mesh(geometry, this.material);
this.rootGroup.add(this.mesh);
}
updateBounds(bounds2d) {
this.dropGeometry();
const tr = this.model.sketchToWorldTransformation;
this.bounds = bounds2d.map(v => tr.apply(v));
this.createGeometry();
}
updateSketch() {
super.updateSketch();
// let bounds2d = ...
// for (let mSketchObject of this.model.sketchObjects) {
// mSketchObject.sketchPrimitive.tessellate(...to bounds2d)
// }
// this.updateBounds(bounds2d)
}
mark(color) {
this.setColor(color || SELECTION_COLOR);
}
withdraw(color) {
this.setColor(null);
}
setColor(color) {
setFacesColor(this.mesh.geometry.faces, color);
this.mesh.geometry.colorsNeedUpdate = true;
}
dispose() {
this.dropGeometry();
this.material.dispose();
super.dispose();
}
}

View file

@ -0,0 +1,61 @@
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';
export class ShellView extends View {
constructor(shell, skin) {
super(shell);
this.material = createSolidMaterial(skin);
this.rootGroup = SceneGraph.createGroup();
this.edgeGroup = SceneGraph.createGroup();
this.vertexGroup = SceneGraph.createGroup();
this.faceViews = [];
this.edgeViews = [];
this.vertexViews = [];
SceneGraph.addToGroup(this.rootGroup, this.edgeGroup);
SceneGraph.addToGroup(this.rootGroup, this.vertexGroup);
setAttribute(this.rootGroup, SHELL, this);
const geometry = new THREE.Geometry();
geometry.dynamic = true;
this.mesh = new THREE.Mesh(geometry, this.material);
this.rootGroup.add(this.mesh);
const geom = this.mesh.geometry;
for (let face of shell.faces) {
const faceView = new FaceView(face, geom);
this.faceViews.push(faceView);
this.rootGroup.add(faceView.rootGroup);
}
geom.mergeVertices();
for (let edge of shell.edges) {
const edgeView = new EdgeView(edge);
SceneGraph.addToGroup(this.edgeGroup, edgeView.rootGroup);
this.edgeViews.push(edgeView);
}
}
dispose() {
for (let faceView of this.faceViews) {
faceView.dispose();
}
for (let edgeView of this.edgeViews) {
edgeView.dispose();
}
for (let vertexView of this.vertexViews) {
vertexView.dispose();
}
super.dispose();
}
}

View file

@ -0,0 +1,53 @@
import {View} from './view';
import {getAttribute, setAttribute} from 'scene/objectData';
import staticResource from 'scene/staticResource';
import {SKETCH_OBJECT} from '../entites';
import Vector from 'math/vector';
import {createLineMaterial} from 'scene/materials';
export class SketchObjectView extends View {
constructor(mSketchObject, _3dTransformation) {
super(mSketchObject);
let material = mSketchObject.construction ? SKETCH_CONSTRUCTION_MATERIAL : SKETCH_MATERIAL;
let line = new THREE.Line(new THREE.Geometry(), material);
setAttribute(line, SKETCH_OBJECT, this);
const chunks = mSketchObject.sketchPrimitive.tessellate(10);
function addLine(p, q) {
const lg = line.geometry;
const a = _3dTransformation.apply(chunks[p]);
const b = _3dTransformation.apply(chunks[q]);
lg.vertices.push(a._plus(OFF_LINES_VECTOR).three());
lg.vertices.push(b._plus(OFF_LINES_VECTOR).three());
}
for (let q = 1; q < chunks.length; q ++) {
addLine(q - 1, q);
}
this.rootGroup = line;
}
mark(color) {
let line = this.rootGroup;
setAttribute(line, 'selection.defaultMaterial', line.material);
line.material = SKETCH_SELECTION_MATERIAL;
}
withdraw(color) {
let line = this.rootGroup;
line.material = getAttribute(line, 'selection.defaultMaterial');
}
dispose() {
this.rootGroup.geometry.dispose();
super.dispose();
}
}
const OFF_LINES_VECTOR = new Vector();//normal.multiply(0); // disable it. use polygon offset feature of material
const SKETCH_MATERIAL = staticResource(createLineMaterial(0xFFFFFF, 3));
const SKETCH_CONSTRUCTION_MATERIAL = staticResource(createLineMaterial(0x777777, 2));
const SKETCH_SELECTION_MATERIAL = staticResource(createLineMaterial(0xFF0000, 6));

View file

@ -0,0 +1,10 @@
import {View} from './view';
export class VertexView extends View {
constructor() {
super();
}
}

View file

@ -0,0 +1,23 @@
export class View {
constructor(model) {
this.model = model;
model.ext.view = this;
}
setVisible(value) {
}
mark(color) {
}
withdraw() {
}
dispose() {
this.model.ext.view = null;
this.model = null;
};
}

View file

@ -1,6 +1,6 @@
import {readBrep} from '../../../brep/io/brepIO';
import {BREPSceneSolid} from './brepSceneObject';
import {MBrepShell} from '../../model/mshell';
export function readShellEntityFromJson(data) {
return new BREPSceneSolid(readBrep(data));
return new MBrepShell(readBrep(data));
}

View file

@ -9,7 +9,7 @@ import {getAttribute} from '../../../../../modules/scene/objectData';
export class SceneSolid {
constructor(type, id, skin) {
this.tCadType = type || 'SOLID';
this.tCadType = type || 'SHELL';
this.cadGroup = new THREE.Object3D();
setAttribute(this.cadGroup, 'shell', this);
@ -132,7 +132,7 @@ export class SceneFace {
let line = new THREE.Line(new THREE.Geometry(), material);
let sceneSketchObject = new SceneSketchObject(sketchObject, line);
setAttribute(line, 'sketchObject', sceneSketchObject);
const chunks = sketchObject.approximate(10);
const chunks = sketchObject.tessellate(10);
function addLine(p, q) {
const lg = line.geometry;
const a = _3dTransformation.apply(chunks[p]);

View file

@ -18,12 +18,12 @@ class SketchPrimitive {
this.inverted = !this.inverted;
}
approximate(resolution) {
const approximation = this.approximateImpl(resolution);
tessellate(resolution) {
const tessellation = this.tessellateImpl(resolution);
if (this.inverted) {
approximation.reverse();
tessellation.reverse();
}
return approximation;
return tessellation;
}
isCurve() {
@ -45,6 +45,10 @@ class SketchPrimitive {
toVerbNurbs(plane, _3dtr) {
throw 'not implemented'
}
tessellateImpl() {
throw 'not implemented'
}
}
export class Segment extends SketchPrimitive {
@ -54,7 +58,7 @@ export class Segment extends SketchPrimitive {
this.b = b;
}
approximateImpl(resolution) {
tessellateImpl(resolution) {
return [this.a, this.b];
}
@ -71,11 +75,11 @@ export class Arc extends SketchPrimitive {
this.c = c;
}
approximateImpl(resolution) {
return Arc.approximateArc(this.a, this.b, this.c, resolution);
tessellateImpl(resolution) {
return Arc.tessellateArc(this.a, this.b, this.c, resolution);
}
static approximateArc(ao, bo, c, resolution) {
static tessellateArc(ao, bo, c, resolution) {
var a = ao.minus(c);
var b = bo.minus(c);
var points = [ao];
@ -129,7 +133,7 @@ export class BezierCurve extends SketchPrimitive {
this.cp2 = cp2;
}
approximateImpl(resolution) {
tessellateImpl(resolution) {
return LUT(this.a, this.b, this.cp1, this.cp2, 10);
}
}
@ -144,11 +148,11 @@ export class EllipticalArc extends SketchPrimitive {
this.r = r;
}
approximateImpl(resolution) {
return EllipticalArc.approxEllipticalArc(this.ep1, this.ep2, this.a, this.b, this.r, resolution);
tessellateImpl(resolution) {
return EllipticalArc.tessEllipticalArc(this.ep1, this.ep2, this.a, this.b, this.r, resolution);
}
static approxEllipticalArc(ep1, ep2, ao, bo, radiusY, resolution) {
static tessEllipticalArc(ep1, ep2, ao, bo, radiusY, resolution) {
const axisX = ep2.minus(ep1);
const radiusX = axisX.length() * 0.5;
axisX._normalize();
@ -187,11 +191,11 @@ export class Circle extends SketchPrimitive {
this.r = r;
}
approximateImpl(resolution) {
return Circle.approxCircle(this.c, this.r, resolution);
tessellateImpl(resolution) {
return Circle.tessCircle(this.c, this.r, resolution);
}
static approxCircle(c, r, resolution) {
static tessCircle(c, r, resolution) {
var points = [];
resolution = 1;
//var step = Math.acos(1 - ((resolution * resolution) / (2 * r * r)));
@ -220,8 +224,8 @@ export class Ellipse extends SketchPrimitive {
this.r = r;
}
approximateImpl(resolution) {
return EllipticalArc.approxEllipticalArc(this.ep1, this.ep2, this.ep1, this.ep1, this.r, resolution);
tessellateImpl(resolution) {
return EllipticalArc.tessEllipticalArc(this.ep1, this.ep2, this.ep1, this.ep1, this.r, resolution);
}
}
@ -235,7 +239,7 @@ export class Contour {
this.segments.push(obj);
}
approximateOnSurface(surface) {
tessellateOnSurface(surface) {
const cc = new CompositeCurve();
const tr = to3DTrFunc(surface);
@ -243,25 +247,25 @@ export class Contour {
let firstPoint = null;
for (let segIdx = 0; segIdx < this.segments.length; ++segIdx) {
let segment = this.segments[segIdx];
let approximation = segment.approximate(RESOLUTION);
let tessellation = segment.tessellate(RESOLUTION);
approximation = approximation.map(p => tr(p));
tessellation = tessellation.map(p => tr(p));
const n = approximation.length;
prev = prev == null ? approximation[0] : prev;
approximation[0] = prev; // this magic is to keep identity of same vectors
if (firstPoint == null) firstPoint = approximation[0];
const n = tessellation.length;
prev = prev == null ? tessellation[0] : prev;
tessellation[0] = prev; // this magic is to keep identity of same vectors
if (firstPoint == null) firstPoint = tessellation[0];
if (segIdx == this.segments.length - 1) {
approximation[n - 1] = firstPoint;
tessellation[n - 1] = firstPoint;
}
cc.add(segment.toNurbs(surface), prev, segment);
prev = approximation[n - 1];
prev = tessellation[n - 1];
//It might be an optimization for segments
// for (let i = 1; i < n; ++i) {
// const curr = approximation[i];
// const curr = tessellation[i];
// cc.add(new Line.fromSegment(prev, curr), prev, segment);
// prev = curr;
// }
@ -281,20 +285,20 @@ export class Contour {
return cc;
}
approximate(resolution) {
const approximation = [];
tessellate(resolution) {
const tessellation = [];
for (let segment of this.segments) {
const segmentApproximation = segment.approximate(resolution);
const segmentTessellation = segment.tessellate(resolution);
//skip last one cuz it's guaranteed to be closed
for (let i = 0; i < segmentApproximation.length - 1; ++i) {
approximation.push(segmentApproximation[i]);
for (let i = 0; i < segmentTessellation.length - 1; ++i) {
tessellation.push(segmentTessellation[i]);
}
}
return approximation;
return tessellation;
}
isCCW() {
return isCCW(this.approximate(10));
return isCCW(this.tessellate(10));
}
reverse() {

View file

@ -1,11 +1,15 @@
import {createToken} from 'bus';
import {ReadSketch} from './sketchReader';
import {getSketchBoundaries} from './sketchBoundaries';
import {TOKENS as CRAFT_TOKENS} from '../craft/craftPlugin';
import {stream} from 'lstream';
export function activate({bus, services}) {
export function activate({streams, services}) {
streams.sketcher = {
update: stream()
};
services.storage.addListener(evt => {
let prefix = services.project.sketchStorageNamespace;
if (evt.key.indexOf(prefix) < 0) return;
@ -33,17 +37,17 @@ export function activate({bus, services}) {
return ReadSketch(JSON.parse(savedSketch), sketchId, true);
}
function updateSketchForFace(sketchFace) {
let sketch = readSketch(sketchFace.id);
function updateSketchForFace(mFace) {
let sketch = readSketch(mFace.id);
if (sketch !== null) {
sketchFace.updateSketch(sketch);
bus.dispatch(TOKENS.SKETCH_UPDATE, sketchFace.id);
mFace.setSketch(sketch);
streams.sketcher.update.next(mFace);
}
}
function updateAllSketches() {
let allShells = services.cadRegistry.getAllShells();
allShells.forEach(sceneShell => sceneShell.sceneFaces.forEach(sceneFace => updateSketchForFace(sceneFace)));
allShells.forEach(mShell => mShell.faces.forEach(mFace => updateSketchForFace(mFace)));
services.viewer.requestRender();
}
@ -66,13 +70,9 @@ export function activate({bus, services}) {
services.appTabs.show(sceneFace.id, 'Sketch ' + sceneFace.id, 'sketcher.html#' + sketchURL);
}
bus.subscribe(CRAFT_TOKENS.MODIFICATIONS, updateAllSketches);
streams.craft.modifications.attach(updateAllSketches);
services.sketcher = {
sketchFace, updateAllSketches, getAllSketches, readSketch
}
}
export const TOKENS = {
SKETCH_UPDATE: createToken('sketcher', 'sketchUpdate')
};

View file

@ -1,5 +1,5 @@
import TPI from './tpi';
import {BREPSceneSolid} from '../scene/wrappers/brepSceneObject';
import {MBrepShell} from '../model/mshell';
/*
* TPI stands for the Test Program Interface
@ -7,7 +7,7 @@ import {BREPSceneSolid} from '../scene/wrappers/brepSceneObject';
export function activate({bus, services}) {
function addShellOnScene(shell, skin) {
const sceneSolid = new BREPSceneSolid(shell, undefined, skin);
const sceneSolid = new MBrepShell(shell);
addOnScene(sceneSolid, skin);
return sceneSolid;
}