diff --git a/modules/lstream/base.js b/modules/lstream/base.js index 88d55074..841d8340 100644 --- a/modules/lstream/base.js +++ b/modules/lstream/base.js @@ -30,6 +30,10 @@ export class StreamBase { distinct() { return new DistinctStream(this); } + + throttle(delay, accumulator) { + return new ThrottleStream(this, delay, accumulator); + } } const {MapStream} = require('./map'); @@ -38,3 +42,4 @@ const {StateStream} = require('./state'); const {PairwiseStream} = require('./pairwise'); const {ScanStream} = require('./scan'); const {DistinctStream} = require('./distinct'); +const {ThrottleStream} = require('./throttle'); diff --git a/modules/lstream/throttle.js b/modules/lstream/throttle.js new file mode 100644 index 00000000..c5e1368d --- /dev/null +++ b/modules/lstream/throttle.js @@ -0,0 +1,26 @@ +import {StreamBase} from './base'; +import {Emitter} from './emitter'; + +export class ThrottleStream extends StreamBase { + + constructor(stream, delay = 0, accumulator = v => v) { + super(); + this.stream = stream; + this.delay = delay; + this.accumulator = accumulator; + } + + attach(observer) { + let scheduled = false; + let value = undefined; + this.stream.attach(val => { + value = this.accumulator(val); + if (!scheduled) { + setTimeout(() => { + scheduled = false; + observer(value); + }); + } + }, this.delay) + } +} diff --git a/modules/ui/components/Toolbar.less b/modules/ui/components/Toolbar.less index 4e61ac9f..96d217db 100644 --- a/modules/ui/components/Toolbar.less +++ b/modules/ui/components/Toolbar.less @@ -22,7 +22,8 @@ white-space: nowrap; font-size: 10px; padding: 3px 7px; - color: #555; + color: #555; + pointer-events: auto; &:hover { cursor: pointer; background-color: #333; diff --git a/web/app/cad/dom/components/SketcherMode.jsx b/web/app/cad/dom/components/SketcherMode.jsx new file mode 100644 index 00000000..8988cf26 --- /dev/null +++ b/web/app/cad/dom/components/SketcherMode.jsx @@ -0,0 +1,13 @@ +import React from 'react'; +import connect from 'ui/connect'; + +@connect(streams => streams.sketcher.sketchingMode.map(sketchingMode => ({visible: sketchingMode}))) +export default class SketcherMode extends React.Component { + + render() { + if (!this.props.visible) { + return null; + } + return this.props.children; + } +} \ No newline at end of file diff --git a/web/app/cad/dom/components/SketcherToolbars.jsx b/web/app/cad/dom/components/SketcherToolbars.jsx index e6f00e52..f3da1a40 100644 --- a/web/app/cad/dom/components/SketcherToolbars.jsx +++ b/web/app/cad/dom/components/SketcherToolbars.jsx @@ -1,20 +1,14 @@ import React from 'react'; import ls from './SketcherToolbars.less'; -import Abs from 'ui/components/Abs'; import {createPlugableToolbar} from './PlugableToolbar'; -import connect from 'ui/connect'; -export default connect(streams => streams.ui.toolbars.sketcherToolbarsVisible.map(visible => ({visible})))( -function SketcherToolbars({visible}) { - if (!visible) { - return null; - } - return +export default function SketcherToolbars({visible}) { + return
- ; -}) +
; +} const SketcherToolbarGeneral = createPlugableToolbar(streams => streams.ui.toolbars.sketcherGeneral); const SketcherToolbarConstraints = createPlugableToolbar(streams => streams.ui.toolbars.sketcherConstraints); diff --git a/web/app/cad/dom/components/SketcherToolbars.less b/web/app/cad/dom/components/SketcherToolbars.less index a25ffbb2..2a4c8574 100644 --- a/web/app/cad/dom/components/SketcherToolbars.less +++ b/web/app/cad/dom/components/SketcherToolbars.less @@ -1,9 +1,11 @@ .sketcherToolbars { + height: 100%; display: flex; align-items: flex-start; - margin-top: 5px; } .sketcherToolbars > * { margin-right: 5px; + max-height: 100%; + pointer-events: auto; } diff --git a/web/app/cad/dom/components/View3d.jsx b/web/app/cad/dom/components/View3d.jsx index 74482918..f8ceef8b 100644 --- a/web/app/cad/dom/components/View3d.jsx +++ b/web/app/cad/dom/components/View3d.jsx @@ -12,6 +12,8 @@ import SketcherToolbars from './SketcherToolbars'; import CameraControl from './CameraControl'; import HeadsUpHelper from './HeadsUpHelper'; import {HeadsUpToolbar} from './HeadsUpToolbar'; +import {SketchObjectExplorer} from '../../../sketcher/components/SketchObjectExplorer'; +import SketcherMode from './SketcherMode'; export default class View3d extends React.Component { @@ -26,17 +28,37 @@ export default class View3d extends React.Component {
- - - - - - - - - - - + +
+
+ + +
+ +
+ +
+ +
+
+
+ +
+ +
+
+ +
+ +
+ +
+ + + +
+ +
; diff --git a/web/app/cad/dom/components/View3d.less b/web/app/cad/dom/components/View3d.less index 63b3eec5..6fecde8e 100644 --- a/web/app/cad/dom/components/View3d.less +++ b/web/app/cad/dom/components/View3d.less @@ -22,3 +22,52 @@ overflow: hidden; position: absolute; } + +.mainLayout { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + + display: flex; + flex-direction: column; + + pointer-events: none; +} + +.headsUp { + display: flex; + flex-direction: column; +} + +.middleSection { + display: flex; + align-items: flex-start; + flex-grow: 1; +} + +.overlayingPanel { + align-self: stretch; +} + +.wizardArea { + pointer-events: auto; + position: relative; +} + +.spring { + flex-grow: 1; +} + +.middleRight { + height: 100%; + margin: 5px 0; +} + +.bottomStack { + display: flex; + flex-direction: column; + position: relative; + pointer-events: auto; +} \ No newline at end of file diff --git a/web/app/cad/dom/uiPlugin.js b/web/app/cad/dom/uiPlugin.js index 8471ed45..199e97c4 100644 --- a/web/app/cad/dom/uiPlugin.js +++ b/web/app/cad/dom/uiPlugin.js @@ -12,8 +12,7 @@ export function defineStreams({streams}) { headsUpQuickActions: state([]), sketcherGeneral: state([]), sketcherConstraints: state([]), - sketcherControl: state([]), - sketcherToolbarsVisible: state(false) + sketcherControl: state([]) }, floatViews: state([]), sockets: {} diff --git a/web/app/cad/sketch/inPlaceSketcher.js b/web/app/cad/sketch/inPlaceSketcher.js index ac5fbc2e..1152b5e9 100644 --- a/web/app/cad/sketch/inPlaceSketcher.js +++ b/web/app/cad/sketch/inPlaceSketcher.js @@ -5,12 +5,14 @@ import {Matrix4} from 'three/src/math/Matrix4'; import {ORIGIN} from '../../math/l3space'; import {CAMERA_MODE} from '../scene/viewer'; import DPR from 'dpr'; +import sketcherStreams from '../../sketcher/sketcherStreams'; export class InPlaceSketcher { constructor(ctx) { this.face = null; // should be only one in the state this.ctx = ctx; + this.viewer = null; } get inEditMode() { @@ -32,6 +34,8 @@ export class InPlaceSketcher { container.appendChild(canvas); this.viewer = new Viewer(canvas, IO); + this.ctx.streams.sketcherApp = this.viewer.streams; + this.syncWithCamera(); this.viewer.toolManager.setDefaultTool(new DelegatingPanTool(this.viewer, viewer3d.sceneSetup.renderer.domElement)); viewer3d.sceneSetup.trackballControls.addEventListener( 'change', this.onCameraChange); @@ -55,7 +59,9 @@ export class InPlaceSketcher { this.face = null; this.viewer.canvas.parentNode.removeChild(this.viewer.canvas); this.viewer.dispose(); + this.viewer = null; this.ctx.streams.sketcher.sketchingFace.value = null; + this.ctx.streams.sketcherApp = null; viewer3d.requestRender(); } diff --git a/web/app/cad/sketch/sketchReader.js b/web/app/cad/sketch/sketchReader.js index d7ddaa50..1e3a0517 100644 --- a/web/app/cad/sketch/sketchReader.js +++ b/web/app/cad/sketch/sketchReader.js @@ -74,15 +74,18 @@ export function ReadSketch(sketch, sketchId, readConstructionSegments) { if (sketch.layers !== undefined) { for (let layer of sketch.layers) { const isConstructionLayer = layer.name === "_construction_"; - if (isConstructionLayer && !readConstructionSegments) continue; + for (let obj of layer.data) { - if (isConstructionLayer && obj._class !== 'TCAD.TWO.Segment') continue; + let isConstructionObject = isConstructionLayer || obj.role === 'construction'; + if (isConstructionObject && !readConstructionSegments) continue; + // if (isConstructionObject && obj._class !== 'TCAD.TWO.Segment') continue; + if (obj.edge !== undefined) continue; if (!!obj.aux) continue; if (obj._class === 'TCAD.TWO.Segment') { const segA = ReadSketchPoint(obj.points[0]); const segB = ReadSketchPoint(obj.points[1]); - const pushOn = isConstructionLayer ? out.constructionSegments : out.connections; + const pushOn = isConstructionObject ? out.constructionSegments : out.connections; pushOn.push(new sm.Segment(getID(obj), segA, segB)); } else if (obj._class === 'TCAD.TWO.Arc') { const arcA = ReadSketchPoint(obj.points[0]); diff --git a/web/app/cad/sketch/sketcherPlugin.js b/web/app/cad/sketch/sketcherPlugin.js index c9092adb..bfda96ad 100644 --- a/web/app/cad/sketch/sketcherPlugin.js +++ b/web/app/cad/sketch/sketcherPlugin.js @@ -4,6 +4,15 @@ import {state, stream} from 'lstream'; import {InPlaceSketcher} from './inPlaceSketcher'; import sketcherUIContrib from './sketcherUIContrib'; import initReassignSketchMode from './reassignSketchMode'; +import sketcherStreams from '../../sketcher/sketcherStreams'; + +export function defineStreams(ctx) { + ctx.streams.sketcher = { + update: stream(), + sketchingFace: state(null) + }; + ctx.streams.sketcher.sketchingMode = ctx.streams.sketcher.sketchingFace.map(face => !!face); +} export function activate(ctx) { @@ -11,13 +20,6 @@ export function activate(ctx) { sketcherUIContrib(ctx); - streams.sketcher = { - update: stream(), - sketchingFace: state(null) - }; - - streams.sketcher.sketchingFace.attach(face => streams.ui.toolbars.sketcherToolbarsVisible.value = !!face); - const onSketchUpdate = evt => { let prefix = services.project.sketchStorageNamespace; if (evt.key.indexOf(prefix) < 0) return; diff --git a/web/app/sketcher.js b/web/app/sketcher.js index 2c34f545..c1f5a7b6 100644 --- a/web/app/sketcher.js +++ b/web/app/sketcher.js @@ -145,6 +145,14 @@ function initializeSketcherApplication() { app.viewer.parametricManager.listeners.push(function() {constrList.refresh()}); constrList.refresh(); + + var addingModeRadio = new toolkit.InlineRadio(['sketch', 'construction'], ['sketch', 'construction'], 0); + app.dock.views['Properties'].node.append('
Adding Mode
').append(addingModeRadio.root); + + addingModeRadio.root.find('input:radio').change(() => { + app.viewer.addingRoleMode = addingModeRadio.getValue(); + }); + var layerSelection = new toolkit.Combo('layerSelection', 'Layer'); app.dock.views['Properties'].node.append(layerSelection.root); diff --git a/web/app/sketcher/components/Commands.jsx b/web/app/sketcher/components/Commands.jsx new file mode 100644 index 00000000..e69de29b diff --git a/web/app/sketcher/components/ContraintExplorer.jsx b/web/app/sketcher/components/ContraintExplorer.jsx new file mode 100644 index 00000000..e69de29b diff --git a/web/app/sketcher/components/SketchObjectExplorer.jsx b/web/app/sketcher/components/SketchObjectExplorer.jsx new file mode 100644 index 00000000..45c2c9a7 --- /dev/null +++ b/web/app/sketcher/components/SketchObjectExplorer.jsx @@ -0,0 +1,39 @@ +import React from 'react'; +import cx from 'classnames'; +import ls from './SketchObjectExplorer.less' + +import connect from 'ui/connect'; + +@connect(streams => streams.sketcherApp.objects.map(objects => ({objects}))) +export class SketchObjectExplorer extends React.Component { + + render() { + const {objects} = this.props; + return
+ {objects.map(o =>
+ + {getObjectRole(o)} + {o.simpleClassName} + {o.id} + +
)} +
+ } +} + +function getObjectRole(o) { + if (o.aux) { + return B + } else if (o.role) { + return B + } +} + +function ObjectIcon({object}) { + + return null; +} + +function getClassName() { + return null; +} \ No newline at end of file diff --git a/web/app/sketcher/components/SketchObjectExplorer.less b/web/app/sketcher/components/SketchObjectExplorer.less new file mode 100644 index 00000000..650c499f --- /dev/null +++ b/web/app/sketcher/components/SketchObjectExplorer.less @@ -0,0 +1,25 @@ +.objectItem { + background-color: rgba(0,0,0, 0.6); + border: 1px solid #000; + border-radius: 5px; + padding: 3px 5px; + margin: 2px 5px; + pointer-events: auto; + display: flex; + align-items: center; + + @alt-color: #9c9c9c; + .objectIcon { + background-color: @alt-color; + } + + .menuButton { + background-color: @alt-color; + } + + .objectTag { + display: inline-block; + padding: 0 2px; + } +} + diff --git a/web/app/sketcher/components/SketchSettings.jsx b/web/app/sketcher/components/SketchSettings.jsx new file mode 100644 index 00000000..e69de29b diff --git a/web/app/sketcher/io.js b/web/app/sketcher/io.js index a18cc5e4..e23e6599 100644 --- a/web/app/sketcher/io.js +++ b/web/app/sketcher/io.js @@ -80,7 +80,7 @@ IO.prototype._loadSketch = function(sketch) { } } } - var layer = new Layer(name, Styles.DEFAULT); + var layer = viewer.createLayer(name, Styles.DEFAULT); viewer.layers.push(layer); return layer; } @@ -103,6 +103,12 @@ IO.prototype._loadSketch = function(sketch) { var skobj = null; var _class = obj['_class']; var aux = !!obj['aux']; + var role = obj['role']; + + //support legacy format + if (!role && layerName === '_construction_') { + role = 'construction'; + } if (boundaryProcessing) { if (_class === T.SEGMENT && boundary.lines.length == 0) continue; @@ -158,6 +164,7 @@ IO.prototype._loadSketch = function(sketch) { skobj = new DiameterDimension(obj['obj']); } if (skobj != null) { + skobj.role = role; if (!aux) skobj.stabilize(this.viewer); if (aux) skobj.accept(function(o){o.aux = true; return true;}); if (obj['edge'] !== undefined) { @@ -265,7 +272,7 @@ IO.prototype.addNewBoundaryObjects = function(boundary, maxEdge) { var boundaryLayer = this.viewer.findLayerByName(IO.BOUNDARY_LAYER_NAME); if (boundaryLayer === null) { - boundaryLayer = new Layer(IO.BOUNDARY_LAYER_NAME, Styles.BOUNDS); + boundaryLayer = this.viewer.createLayer(IO.BOUNDARY_LAYER_NAME, Styles.BOUNDS); this.viewer.layers.splice(0, 0, boundaryLayer); } @@ -343,7 +350,7 @@ IO.prototype._serializeSketch = function() { sketch['layers'].push(toLayer); for (var i = 0; i < layer.objects.length; ++i) { var obj = layer.objects[i]; - var to = {'id': obj.id, '_class': obj._class}; + var to = {'id': obj.id, '_class': obj._class, role: obj.role}; if (obj.aux) to.aux = obj.aux; if (obj.edge !== undefined) to.edge = obj.edge; toLayer['data'].push(to); diff --git a/web/app/sketcher/shapes/bezier-curve.js b/web/app/sketcher/shapes/bezier-curve.js index 55ee1950..0bf9378b 100644 --- a/web/app/sketcher/shapes/bezier-curve.js +++ b/web/app/sketcher/shapes/bezier-curve.js @@ -20,7 +20,7 @@ export class BezierCurve extends SketchObject { this.addChild(new Segment(a, cp1)); this.addChild(new Segment(b, cp2)); for (let c of this.children) { - c.role = 'construction'; + c.role = 'objectConstruction'; } } diff --git a/web/app/sketcher/shapes/sketch-object.js b/web/app/sketcher/shapes/sketch-object.js index 1a2c3f09..4aacabff 100644 --- a/web/app/sketcher/shapes/sketch-object.js +++ b/web/app/sketcher/shapes/sketch-object.js @@ -113,6 +113,10 @@ export class SketchObject extends Shape { this.visitParams(p => params.push(p)); } + get simpleClassName() { + return this._class.replace('TCAD.TWO.', ''); + } + get effectiveLayer() { let shape = this; while (shape) { diff --git a/web/app/sketcher/sketcherStreams.js b/web/app/sketcher/sketcherStreams.js new file mode 100644 index 00000000..dca57ecd --- /dev/null +++ b/web/app/sketcher/sketcherStreams.js @@ -0,0 +1,17 @@ +import {state, stream} from 'lstream'; + +export default function(viewer) { + + const streams = {}; + + streams.objectsUpdate = stream(); + streams.objects = streams.objectsUpdate.throttle().map(() => { + let objects = []; + viewer.layers.forEach(l => l.objects.forEach(o => objects.push(o))); + return objects; + }).remember([]); + + streams.addingRoleMode = state(null); + + return streams; +}; \ No newline at end of file diff --git a/web/app/sketcher/viewer2d.js b/web/app/sketcher/viewer2d.js index 37fffd1b..51e64d34 100644 --- a/web/app/sketcher/viewer2d.js +++ b/web/app/sketcher/viewer2d.js @@ -13,497 +13,532 @@ import Vector from 'math/vector'; import * as draw_utils from './shapes/draw-utils'; import {Matrix3} from '../math/l3space'; +import sketcherStreams from './sketcherStreams'; -/** @constructor */ -function Viewer(canvas, IO) { - // 1/1000'' aka 1 mil is a standard precision for the imperial system(for engeneering) - // this precision also covers the metric system which is supposed to be ~0.01 - // this field is used only for displaying purposes now, although in future it could be - // used to keep all internal data with such precision transforming the input from user - this.presicion = 3; - this.canvas = canvas; - this.params = new Parameters(); - this.io = new IO(this); - var viewer = this; - this.retinaPxielRatio = window.devicePixelRatio > 1 ? window.devicePixelRatio : 1; - function updateCanvasSize() { - var canvasWidth = canvas.parentNode.offsetWidth; - var canvasHeight = canvas.parentNode.offsetHeight; +class Viewer { - canvas.width = canvasWidth * viewer.retinaPxielRatio; - canvas.height = canvasHeight * viewer.retinaPxielRatio; + constructor(canvas, IO) { - canvas.style.width = canvasWidth + "px"; - canvas.style.height = canvasHeight + "px"; - } + // 1/1000'' aka 1 mil is a standard precision for the imperial system(for engeneering) + // this precision also covers the metric system which is supposed to be ~0.01 + // this field is used only for displaying purposes now, although in future it could be + // used to keep all internal data with such precision transforming the input from user + this.presicion = 3; + this.canvas = canvas; + this.params = new Parameters(); + this.io = new IO(this); + this.streams = sketcherStreams(this); + var viewer = this; + this.retinaPxielRatio = window.devicePixelRatio > 1 ? window.devicePixelRatio : 1; - this.onWindowResize = function() { + function updateCanvasSize() { + var canvasWidth = canvas.parentNode.offsetWidth; + var canvasHeight = canvas.parentNode.offsetHeight; + + canvas.width = canvasWidth * viewer.retinaPxielRatio; + canvas.height = canvasHeight * viewer.retinaPxielRatio; + + canvas.style.width = canvasWidth + "px"; + canvas.style.height = canvasHeight + "px"; + } + + this.onWindowResize = function () { + updateCanvasSize(); + viewer.refresh(); + }; updateCanvasSize(); - viewer.refresh(); - }; - updateCanvasSize(); - window.addEventListener( 'resize', this.onWindowResize, false ); + window.addEventListener('resize', this.onWindowResize, false); - Object.defineProperty(this, "activeLayer", { - get: viewer.getActiveLayer , - set: viewer.setActiveLayer - }); + Object.defineProperty(this, "activeLayer", { + get: viewer.getActiveLayer, + set: viewer.setActiveLayer + }); - this.bus = new Bus(); - this.ctx = this.canvas.getContext("2d"); - this._activeLayer = null; - this.layers = [ - new Layer("sketch", Styles.DEFAULT), - new Layer("_construction_", Styles.CONSTRUCTION) - ]; - this.dimLayer = new Layer("_dim", Styles.DIM); - this.dimLayers = [this.dimLayer]; - this.bus.defineObservable(this, 'dimScale', 1); - this.bus.subscribe('dimScale', function(){ viewer.refresh(); }); - - this._workspace = [this.layers, this.dimLayers]; + this.bus = new Bus(); + this.ctx = this.canvas.getContext("2d"); + this._activeLayer = null; + this.layers = [ + this.createLayer("sketch", Styles.DEFAULT) + // this.createLayer("_construction_", Styles.CONSTRUCTION) + ]; + this.dimLayer = this.createLayer("_dim", Styles.DIM); + this.dimLayers = [this.dimLayer]; + this.bus.defineObservable(this, 'dimScale', 1); + this.bus.subscribe('dimScale', function () { + viewer.refresh(); + }); - this.referencePoint = new ReferencePoint(); - this._serviceWorkspace = [this._createServiceLayers()]; - - this.toolManager = new ToolManager(this, new PanTool(this)); - this.parametricManager = new ParametricManager(this); + this._workspace = [this.layers, this.dimLayers]; - this.translate = {x : 0.0, y : 0.0}; - this.scale = 1.0; + this.referencePoint = new ReferencePoint(); + this._serviceWorkspace = [this._createServiceLayers()]; - this.selected = []; - this.snapped = null; - - this.historyManager = new HistoryManager(this); - this.transformation = null; - this.screenToModelMatrix = null; - this.validators = []; - this.refresh(); -} + this.toolManager = new ToolManager(this, new PanTool(this)); + this.parametricManager = new ParametricManager(this); -Viewer.prototype.dispose = function() { - window.removeEventListener( 'resize', this.onWindowResize, false ); - this.canvas = null; - this.toolManager.dispose(); -}; + this.translate = {x: 0.0, y: 0.0}; + this.scale = 1.0; -Viewer.prototype.isDisposed = function() { - return this.canvas === null; -}; - -Viewer.prototype.setTransformation = function(a, b, c, d, e, f, zoom) { - this.transformation = [a, b, c, d, e, f]; - this.scale = zoom; - if (this.screenToModelMatrix === null) { - this.screenToModelMatrix = new Matrix3(); - } - this.screenToModelMatrix.set34( - a, c, 0, e, - b, d, 0, f, - 0, 0, 1, 0 - )._invert(); -}; - -Viewer.prototype.roundToPrecision = function(value) { - return value.toFixed(this.presicion); -}; - -Viewer.prototype.addSegment = function(x1, y1, x2, y2, layer) { - var a = new EndPoint(x1, y1); - var b = new EndPoint(x2, y2); - var line = new Segment(a, b); - layer.add(line); - return line; -}; - -Viewer.prototype.remove = function(obj) { - this.removeAll([obj]); -}; - -Viewer.prototype.removeAll = function(objects) { - this.parametricManager.removeObjects(objects); -}; - -Viewer.prototype.add = function(obj, layer) { - layer.add(obj); -}; - -function isEndPoint(o) { - return o._class === 'TCAD.TWO.EndPoint' -} - -Viewer.prototype.search = function(x, y, buffer, deep, onlyPoints, filter) { - - buffer *= 0.5; - - var pickResult = []; - var aim = new Vector(x, y); - - var heroIdx = 0; - var unreachable = buffer * 2; - var heroLength = unreachable; // unreachable - - function isFiltered(o) { - for (var i = 0; i < filter.length; ++i) { - if (filter[i] === o) return true; - } - return false; - } - - for (var i = 0; i < this.layers.length; i++) { - var objs = this.layers[i].objects; - for (var j = 0; j < objs.length; j++) { - var l = unreachable + 1; - var before = pickResult.length; - objs[j].accept((o) => { - if (!o.visible) return true; - if (onlyPoints && !isEndPoint(o)) { - return true; - } - l = o.normalDistance(aim, this.scale); - if (l >= 0 && l <= buffer && !isFiltered(o)) { - pickResult.push(o); - return false; - } - return true; - }); - var hit = before - pickResult.length != 0; - if (hit) { - if (!deep && pickResult.length != 0) return pickResult; - if (l >= 0 && l < heroLength) { - heroLength = l; - heroIdx = pickResult.length - 1; - } - } - } - } - if (pickResult.length > 0) { - var _f = pickResult[0]; - pickResult[0] = pickResult[heroIdx]; - pickResult[heroIdx] = _f; - } - return pickResult; -}; - -Viewer.prototype._createServiceLayers = function() { - let layer = new Layer("_service", Styles.SERVICE); -// layer.objects.push(new CrossHair(0, 0, 20)); - layer.objects.push(new Point(0, 0, 2)); - layer.objects.push(this.referencePoint); - layer.objects.push(new BasisOrigin(null, this)); - return [layer]; - -}; - -Viewer.prototype.refresh = function() { - const viewer = this; - window.requestAnimationFrame(function() { - if (!viewer.isDisposed()) { - viewer.repaint(); - } - }); -}; - -Viewer.prototype.repaint = function() { - - const ctx = this.ctx; - ctx.setTransform(1, 0, 0, 1, 0, 0); - - ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); - - ctx.transform(1, 0, 0, -1, 0, this.canvas.height ); - - if (this.transformation) { - let [a, b, c, d, e, f] = this.transformation; - ctx.transform(a, b, c, d, e, f); - } else { - ctx.transform(1, 0, 0, 1, this.translate.x , this.translate.y ); - ctx.transform(this.scale, 0, 0, this.scale, 0, 0); - } - - this.__prevStyle = null; - - this.__drawWorkspace(ctx, this._workspace, Viewer.__SKETCH_DRAW_PIPELINE); - this.__drawWorkspace(ctx, this._serviceWorkspace, Viewer.__SIMPLE_DRAW_PIPELINE); -}; - -Viewer.__SKETCH_DRAW_PIPELINE = [ - (obj) => !isEndPoint(obj) && obj.marked === null, - (obj) => !isEndPoint(obj) && obj.marked !== null, - (obj) => isEndPoint(obj) && obj.marked === null, - (obj) => isEndPoint(obj) && obj.marked !== null -]; - -Viewer.__SIMPLE_DRAW_PIPELINE = [ - (obj) => true -]; - -Viewer.prototype.__drawWorkspace = function(ctx, workspace, pipeline) { - for (let drawPredicate of pipeline) { - for (let layers of workspace) { - for (let layer of layers) { - for (let obj of layer.objects) { - obj.accept((obj) => { - if (!obj.visible) return true; - if (drawPredicate(obj)) { - this.__draw(ctx, layer, obj); - } - return true; - }); - } - } - } - } -}; - -Viewer.prototype.__draw = function(ctx, layer, obj) { - const style = this.getStyleForObject(layer, obj); - if (style !== this.__prevStyle) { - this.setStyle(style, ctx); - } - this.__prevStyle = style; - obj.draw(ctx, this.scale / this.retinaPxielRatio, this); -}; - -Viewer.prototype.getStyleForObject = function(layer, obj) { - if (obj.style != null) { - return obj.style; - } else if (obj.role != null) { - const style = layer.stylesByRoles[obj.role]; - if (style) { - return style; - } - } - return layer.style; -}; - -Viewer.prototype.setStyle = function(style, ctx) { - draw_utils.SetStyle(style, ctx, this.scale / this.retinaPxielRatio); -}; - -Viewer.prototype.snap = function(x, y, excl) { - this.cleanSnap(); - var snapTo = this.search(x, y, 20 / this.scale, true, true, excl); - if (snapTo.length > 0) { - this.snapped = snapTo[0]; - this.mark(this.snapped, Styles.SNAP); - } - return this.snapped; -}; - -Viewer.prototype.cleanSnap = function() { - if (this.snapped != null) { - this.deselect(this.snapped); + this.selected = []; this.snapped = null; + + this.historyManager = new HistoryManager(this); + this.transformation = null; + this.screenToModelMatrix = null; + this.validators = []; + this.refresh(); } -}; -Viewer.prototype.showBounds = function(x1, y1, x2, y2, offset) { - var dx = Math.max(x2 - x1, 1); - var dy = Math.max(y2 - y1, 1); - if (this.canvas.width > this.canvas.height) { - this.scale = this.canvas.height / dy; - } else { - this.scale = this.canvas.width / dx; - } - this.translate.x = -x1 * this.scale; - this.translate.y = -y1 * this.scale; -}; + dispose() { + window.removeEventListener('resize', this.onWindowResize, false); + this.canvas = null; + this.toolManager.dispose(); + }; -Viewer.prototype.screenToModel2 = function(x, y, out) { - out.x = x * this.retinaPxielRatio; - out.y = this.canvas.height - y * this.retinaPxielRatio; + isDisposed() { + return this.canvas === null; + }; - if (this.transformation) { - out.z = 0; - this.screenToModelMatrix._apply(out); - } else { - out.x -= this.translate.x; - out.y -= this.translate.y; + setTransformation(a, b, c, d, e, f, zoom) { + this.transformation = [a, b, c, d, e, f]; + this.scale = zoom; + if (this.screenToModelMatrix === null) { + this.screenToModelMatrix = new Matrix3(); + } + this.screenToModelMatrix.set34( + a, c, 0, e, + b, d, 0, f, + 0, 0, 1, 0 + )._invert(); + }; - out.x /= this.scale; - out.y /= this.scale; - } -}; + roundToPrecision(value) { + return value.toFixed(this.presicion); + }; -Viewer.prototype.screenToModel = function(e) { - return this._screenToModel(e.offsetX, e.offsetY); -}; + addSegment(x1, y1, x2, y2, layer) { + var a = new EndPoint(x1, y1); + var b = new EndPoint(x2, y2); + var line = new Segment(a, b); + layer.add(line); + return line; + }; -Viewer.prototype._screenToModel = function(x, y) { - var out = {x: 0, y: 0}; - this.screenToModel2(x, y, out); - return out; -}; + remove(obj) { + this.removeAll([obj]); + }; -Viewer.prototype.accept = function(visitor) { - for (let layer of this.layers) { - for (let object of layer.objects) { - if (!object.accept(visitor)) { - return false; + removeAll(objects) { + this.parametricManager.removeObjects(objects); + }; + + add(obj, layer) { + layer.add(obj); + }; + + search(x, y, buffer, deep, onlyPoints, filter) { + + buffer *= 0.5; + + var pickResult = []; + var aim = new Vector(x, y); + + var heroIdx = 0; + var unreachable = buffer * 2; + var heroLength = unreachable; // unreachable + + function isFiltered(o) { + for (var i = 0; i < filter.length; ++i) { + if (filter[i] === o) return true; } - } - } -}; - -Viewer.prototype.findLayerByName = function(name) { - for (var i = 0; i < this.layers.length; i++) { - if (this.layers[i].name == name) { - return this.layers[i]; - } - } - return null; -}; - -Viewer.prototype.findById = function(id) { - var result = null; - this.accept(function(o) { - if (o.id === id) { - result = o; return false; } - return true; - }); - return result; -}; -Viewer.prototype.select = function(objs, exclusive) { - if (exclusive) this.deselectAll(); - for (var i = 0; i < objs.length; i++) { - this.mark(objs[i]); - } -}; - -Viewer.prototype.pick = function(e) { - var m = this.screenToModel(e); - return this.search(m.x, m.y, 20 / this.scale, true, false, []); -}; - -Viewer.prototype.mark = function(obj, style) { - if (style === undefined) { - style = Styles.MARK; - } - obj.marked = style; - - if (this.selected.indexOf(obj) == -1) { - this.selected.push(obj); - } -}; - -Viewer.prototype.getActiveLayer = function() { - var layer = this._activeLayer; - if (layer == null || layer.readOnly) { - layer = null; for (var i = 0; i < this.layers.length; i++) { - var l = this.layers[i]; - if (!l.readOnly) { - layer = l; + var objs = this.layers[i].objects; + for (var j = 0; j < objs.length; j++) { + var l = unreachable + 1; + var before = pickResult.length; + objs[j].accept((o) => { + if (!o.visible) return true; + if (onlyPoints && !isEndPoint(o)) { + return true; + } + l = o.normalDistance(aim, this.scale); + if (l >= 0 && l <= buffer && !isFiltered(o)) { + pickResult.push(o); + return false; + } + return true; + }); + var hit = before - pickResult.length != 0; + if (hit) { + if (!deep && pickResult.length != 0) return pickResult; + if (l >= 0 && l < heroLength) { + heroLength = l; + heroIdx = pickResult.length - 1; + } + } + } + } + if (pickResult.length > 0) { + var _f = pickResult[0]; + pickResult[0] = pickResult[heroIdx]; + pickResult[heroIdx] = _f; + } + return pickResult; + }; + + _createServiceLayers() { + let layer = this.createLayer("_service", Styles.SERVICE); +// layer.objects.push(new CrossHair(0, 0, 20)); + layer.objects.push(new Point(0, 0, 2)); + layer.objects.push(this.referencePoint); + layer.objects.push(new BasisOrigin(null, this)); + return [layer]; + + }; + + refresh() { + const viewer = this; + window.requestAnimationFrame(function () { + if (!viewer.isDisposed()) { + viewer.repaint(); + } + }); + }; + + repaint() { + + const ctx = this.ctx; + ctx.setTransform(1, 0, 0, 1, 0, 0); + + ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); + + ctx.transform(1, 0, 0, -1, 0, this.canvas.height); + + if (this.transformation) { + let [a, b, c, d, e, f] = this.transformation; + ctx.transform(a, b, c, d, e, f); + } else { + ctx.transform(1, 0, 0, 1, this.translate.x, this.translate.y); + ctx.transform(this.scale, 0, 0, this.scale, 0, 0); + } + + this.__prevStyle = null; + + this.__drawWorkspace(ctx, this._workspace, Viewer.__SKETCH_DRAW_PIPELINE); + this.__drawWorkspace(ctx, this._serviceWorkspace, Viewer.__SIMPLE_DRAW_PIPELINE); + }; + + __drawWorkspace(ctx, workspace, pipeline) { + for (let drawPredicate of pipeline) { + for (let layers of workspace) { + for (let layer of layers) { + for (let obj of layer.objects) { + obj.accept((obj) => { + if (!obj.visible) return true; + if (drawPredicate(obj)) { + this.__draw(ctx, layer, obj); + } + return true; + }); + } + } + } + } + }; + + __draw(ctx, layer, obj) { + const style = this.getStyleForObject(layer, obj); + if (style !== this.__prevStyle) { + this.setStyle(style, ctx); + } + this.__prevStyle = style; + obj.draw(ctx, this.scale / this.retinaPxielRatio, this); + }; + + getStyleForObject(layer, obj) { + if (obj.style != null) { + return obj.style; + } else if (obj.role != null) { + const style = layer.stylesByRoles[obj.role]; + if (style) { + return style; + } + } + return layer.style; + }; + + setStyle(style, ctx) { + draw_utils.SetStyle(style, ctx, this.scale / this.retinaPxielRatio); + }; + + snap(x, y, excl) { + this.cleanSnap(); + var snapTo = this.search(x, y, 20 / this.scale, true, true, excl); + if (snapTo.length > 0) { + this.snapped = snapTo[0]; + this.mark(this.snapped, Styles.SNAP); + } + return this.snapped; + }; + + cleanSnap() { + if (this.snapped != null) { + this.deselect(this.snapped); + this.snapped = null; + } + }; + + showBounds(x1, y1, x2, y2, offset) { + var dx = Math.max(x2 - x1, 1); + var dy = Math.max(y2 - y1, 1); + if (this.canvas.width > this.canvas.height) { + this.scale = this.canvas.height / dy; + } else { + this.scale = this.canvas.width / dx; + } + this.translate.x = -x1 * this.scale; + this.translate.y = -y1 * this.scale; + }; + + screenToModel2(x, y, out) { + out.x = x * this.retinaPxielRatio; + out.y = this.canvas.height - y * this.retinaPxielRatio; + + if (this.transformation) { + out.z = 0; + this.screenToModelMatrix._apply(out); + } else { + out.x -= this.translate.x; + out.y -= this.translate.y; + + out.x /= this.scale; + out.y /= this.scale; + } + }; + + screenToModel(e) { + return this._screenToModel(e.offsetX, e.offsetY); + }; + + _screenToModel(x, y) { + var out = {x: 0, y: 0}; + this.screenToModel2(x, y, out); + return out; + }; + + accept(visitor) { + for (let layer of this.layers) { + for (let object of layer.objects) { + if (!object.accept(visitor)) { + return false; + } + } + } + }; + + findLayerByName(name) { + for (var i = 0; i < this.layers.length; i++) { + if (this.layers[i].name == name) { + return this.layers[i]; + } + } + return null; + }; + + findById(id) { + var result = null; + this.accept(function (o) { + if (o.id === id) { + result = o; + return false; + } + return true; + }); + return result; + }; + + select(objs, exclusive) { + if (exclusive) this.deselectAll(); + for (var i = 0; i < objs.length; i++) { + this.mark(objs[i]); + } + }; + + pick(e) { + var m = this.screenToModel(e); + return this.search(m.x, m.y, 20 / this.scale, true, false, []); + }; + + mark(obj, style) { + if (style === undefined) { + style = Styles.MARK; + } + obj.marked = style; + + if (this.selected.indexOf(obj) == -1) { + this.selected.push(obj); + } + }; + + getActiveLayer() { + var layer = this._activeLayer; + if (layer == null || layer.readOnly) { + layer = null; + for (var i = 0; i < this.layers.length; i++) { + var l = this.layers[i]; + if (!l.readOnly) { + layer = l; + break; + } + } + } + if (layer == null) { + layer = this.createLayer("sketch", Styles.DEFAULT); + this.layers.push(layer); + } + return layer; + }; + + setActiveLayerName(layerName) { + let layer = this.findLayerByName(layerName); + if (layer) { + this.activeLayer = layer; + } else { + console.warn("layer doesn't exist: " + layerName); + } + }; + + setActiveLayer(layer) { + if (!layer.readOnly) { + this._activeLayer = layer; + this.bus.dispatch("activeLayer"); + } + }; + + deselect(obj) { + for (var i = 0; i < this.selected.length; i++) { + if (obj === this.selected[i]) { + this.selected.splice(i, 1)[0].marked = null; break; } } - } - if (layer == null) { - layer = new Layer("sketch", Styles.DEFAULT); - this.layers.push(layer); - } - return layer; -}; + }; -Viewer.prototype.setActiveLayerName = function(layerName) { - let layer = this.findLayerByName(layerName); - if (layer) { - this.activeLayer = layer; - } else { - console.warn("layer doesn't exist: " + layerName); - } -}; - -Viewer.prototype.setActiveLayer = function(layer) { - if (!layer.readOnly) { - this._activeLayer = layer; - this.bus.dispatch("activeLayer"); - } -}; - -Viewer.prototype.deselect = function(obj) { - for (var i = 0; i < this.selected.length; i++) { - if (obj === this.selected[i]) { - this.selected.splice(i, 1)[0].marked = null; - break; + deselectAll() { + for (var i = 0; i < this.selected.length; i++) { + this.selected[i].marked = null; } - } -}; + while (this.selected.length > 0) this.selected.pop(); + }; -Viewer.prototype.deselectAll = function() { - for (var i = 0; i < this.selected.length; i++) { - this.selected[i].marked = null; - } - while(this.selected.length > 0) this.selected.pop(); -}; + equalizeLinkedEndpoints() { + const visited = new Set(); -Viewer.prototype.equalizeLinkedEndpoints = function() { - const visited = new Set(); - - function equalize(obj) { - if (visited.has(obj.id)) return; - visited.add(obj.id); - for (let link of obj.linked) { - if (isEndPoint(link)) { - equalize(obj, link); - link.setFromPoint(obj); - equalize(link); + function equalize(obj) { + if (visited.has(obj.id)) return; + visited.add(obj.id); + for (let link of obj.linked) { + if (isEndPoint(link)) { + equalize(obj, link); + link.setFromPoint(obj); + equalize(link); + } } } - } - this.accept((obj) => { - if (isEndPoint(obj)) { - equalize(obj); - } - return true; - }); -}; - -/** @constructor */ -function Layer(name, style) { - this.name = name; - this.style = style; - this.stylesByRoles = { - 'construction': Styles.CONSTRUCTION_OF_OBJECT, - 'virtual': Styles.VIRTUAL + this.accept((obj) => { + if (isEndPoint(obj)) { + equalize(obj); + } + return true; + }); }; - this.objects = []; - this.readOnly = false; // This is actually a mark for boundary layers coming from 3D + + fullHeavyUIRefresh() { + this.refresh(); + this.parametricManager.notify(); + }; + + createLayer(name, style, onUpdate) { + return new Layer(name, style, this) + }; + + objectsUpdate = () => this.streams.objectsUpdate.next(); + + get addingRoleMode() { + return this.streams.addingRoleMode.value; + } + + set addingRoleMode(value) { + this.streams.addingRoleMode.next(value); + } + + static __SKETCH_DRAW_PIPELINE = [ + (obj) => !isEndPoint(obj) && obj.marked === null && isConstruction(obj), + (obj) => !isEndPoint(obj) && obj.marked === null && !isConstruction(obj), + (obj) => !isEndPoint(obj) && obj.marked !== null, + (obj) => isEndPoint(obj) && obj.marked === null, + (obj) => isEndPoint(obj) && obj.marked !== null + ]; + + static __SIMPLE_DRAW_PIPELINE = [ + (obj) => true + ]; } -Layer.prototype.remove = function(object) { - const idx = this.objects.indexOf(object); - if (idx != -1) { - this.objects.splice(idx, 1); - return true; - } - return false; -}; +const isEndPoint = o => o._class === 'TCAD.TWO.EndPoint'; +const isConstruction = o => o.role === 'construction'; -Layer.prototype.add = function(object) { - if (object.layer !== undefined) { - if (object.layer != null) { - object.layer.remove(object); +class Layer { + + constructor(name, style, viewer) { + this.name = name; + this.style = style; + this.stylesByRoles = { + 'objectConstruction': Styles.CONSTRUCTION_OF_OBJECT, + 'construction': Styles.CONSTRUCTION, + 'virtual': Styles.VIRTUAL + }; + this.objects = []; + this.readOnly = false; // This is actually a mark for boundary layers coming from 3D + this.viewer = viewer; + } + + remove(object) { + const idx = this.objects.indexOf(object); + if (idx !== -1) { + this.objects.splice(idx, 1); + this.viewer.objectsUpdate(); + return true; } - if (object.layer !== this) { - this.objects.push(object); - object.layer = this; + return false; + }; + + add(object) { + if (object.layer !== undefined) { + if (object.layer != null) { + object.layer.remove(object); + } + if (object.layer !== this) { + object.layer = this; + this._addAndNotify(object); + } + } else { + this._addAndNotify(object); + } + }; + + _addAndNotify(object) { + if (this.viewer.addingRoleMode) { + object.role = this.viewer.addingRoleMode; } - } else { this.objects.push(object); + this.viewer.objectsUpdate(); } -}; +} -Viewer.prototype.fullHeavyUIRefresh = function() { - this.refresh(); - this.parametricManager.notify(); -}; - -export {Viewer, Layer, Styles} \ No newline at end of file +export {Viewer, Styles} \ No newline at end of file diff --git a/web/index.html b/web/index.html index 21595262..40a35a91 100644 --- a/web/index.html +++ b/web/index.html @@ -2,7 +2,7 @@ Web CAD / Part Designer - + diff --git a/web/test/utils/subjects/modeller/sketcherUISubject.js b/web/test/utils/subjects/modeller/sketcherUISubject.js index 2bdd79b8..3828e7aa 100644 --- a/web/test/utils/subjects/modeller/sketcherUISubject.js +++ b/web/test/utils/subjects/modeller/sketcherUISubject.js @@ -73,11 +73,11 @@ export function createSketcherSubject(sketcherApp) { } function changeToConstructionLayer() { - changeLayer('_construction_'); + viewer.addingRoleMode = 'construction'; } function changeToDefaultLayer() { - changeLayer('sketch'); + viewer.addingRoleMode = null; } function click(modelX, modelY, attrs) {