From 3dea46139232100bb53752c730468f5b1fff8bfa Mon Sep 17 00:00:00 2001 From: Val Erastov Date: Wed, 16 Mar 2022 01:54:51 -0700 Subject: [PATCH] objects highlight --- modules/gems/indexed.ts | 8 ++ modules/scene/objects/scalableLine.js | 13 +++- modules/scene/sceneSetup.ts | 26 ++++++- modules/ui/components/GenericExplorer.tsx | 4 +- modules/ui/components/GenericWizard.tsx | 6 +- modules/ui/components/Menu.jsx | 4 +- .../ui/components/{Stack.jsx => Stack.tsx} | 4 +- modules/ui/components/controls/Button.tsx | 5 +- .../components/controls/CheckboxControl.jsx | 19 ++--- .../ui/components/controls/ColorControl.tsx | 7 +- modules/ui/components/controls/Field.jsx | 13 +++- modules/ui/components/controls/Field.less | 3 +- modules/ui/components/controls/Label.jsx | 6 +- package.json | 1 - web/app/cad/actions/ActionButtonBehavior.jsx | 5 +- web/app/cad/actions/actionHelpers.js | 10 +++ web/app/cad/actions/actionSystemPlugin.ts | 12 +-- web/app/cad/attributes/attributesPlugin.ts | 36 ++++++++- web/app/cad/attributes/attributesService.ts | 33 ++++++-- .../attributes/ui/DisplayOptionsDialog.tsx | 76 +++++++++++++++++++ .../craft/ui/SceneInlineObjectExplorer.tsx | 4 +- web/app/cad/craft/ui/VisibleSwitch.tsx | 34 --------- .../wizard/components/form/EntityList.jsx | 50 ++++++------ .../dom/components/ContributedComponents.jsx | 2 +- web/app/cad/dom/menu/MenuHolder.jsx | 41 +++++----- web/app/cad/init/startApplication.js | 6 +- .../scene/controls/mouseEventSystemPlugin.js | 54 +++++-------- .../cad/scene/controls/pickControlPlugin.ts | 9 ++- web/app/cad/scene/entityContextPlugin.ts | 14 ++-- web/app/cad/scene/highlightPlugin.ts | 56 ++++++++++++++ .../cad/scene/selectionMarker/markerPlugin.ts | 6 +- web/app/cad/scene/viewSyncPlugin.js | 48 +++++++++--- web/app/cad/scene/views/curveBasedView.js | 31 +++++--- web/app/cad/scene/views/datumView.js | 16 ++-- web/app/cad/scene/views/edgeView.js | 17 ++++- web/app/cad/scene/views/faceView.js | 67 ++++++++++------ web/app/cad/scene/views/openFaceView.js | 42 ++++------ web/app/cad/scene/views/shellView.js | 63 ++++----------- web/app/cad/scene/views/sketchLoopView.js | 70 ++++++++++------- web/app/cad/scene/views/sketchObjectView.js | 4 +- web/app/cad/scene/views/vertexView.js | 6 +- web/app/cad/scene/views/view.js | 72 +++++++++++++----- web/app/cad/workbench/menuConfig.js | 8 +- 43 files changed, 657 insertions(+), 354 deletions(-) create mode 100644 modules/gems/indexed.ts rename modules/ui/components/{Stack.jsx => Stack.tsx} (55%) create mode 100644 web/app/cad/attributes/ui/DisplayOptionsDialog.tsx delete mode 100644 web/app/cad/craft/ui/VisibleSwitch.tsx create mode 100644 web/app/cad/scene/highlightPlugin.ts diff --git a/modules/gems/indexed.ts b/modules/gems/indexed.ts new file mode 100644 index 00000000..acd6f062 --- /dev/null +++ b/modules/gems/indexed.ts @@ -0,0 +1,8 @@ +import {Index} from "gems/indexType"; + +export function createIndex(arr: T[], indexProp: (item) => string): Index { + return arr.reduce((index, item) => { + index[indexProp(item)] = item; + return index; + }, {}) +} \ No newline at end of file diff --git a/modules/scene/objects/scalableLine.js b/modules/scene/objects/scalableLine.js index cffef86a..8e728018 100644 --- a/modules/scene/objects/scalableLine.js +++ b/modules/scene/objects/scalableLine.js @@ -10,8 +10,8 @@ import {ORIGIN} from "math/vector"; export default class ScalableLine extends Mesh { - constructor(tessellation, width, color, opacity, smooth, ambient) { - super(createGeometry(tessellation, smooth), createMaterial(color, opacity, ambient)); + constructor(tessellation, width, color, opacity, smooth, ambient, offset) { + super(createGeometry(tessellation, smooth), createMaterial(color, opacity, ambient, offset)); this.width = width; this.morphTargetInfluences = [0]; } @@ -35,12 +35,19 @@ export default class ScalableLine extends Mesh { } } -function createMaterial(color, opacity, ambient) { +function createMaterial(color, opacity, ambient, offset) { let materialParams = { vertexColors: FaceColors, morphTargets: true, color, }; + if (offset) { + Object.assign(materialParams, { + polygonOffset: true, + polygonOffsetFactor: -2.0, + polygonOffsetUnits: -1.0, + }); + } if (!ambient) { materialParams.shininess = 0; } diff --git a/modules/scene/sceneSetup.ts b/modules/scene/sceneSetup.ts index 6219aed4..5653169b 100644 --- a/modules/scene/sceneSetup.ts +++ b/modules/scene/sceneSetup.ts @@ -217,7 +217,31 @@ export default class SceneSetUp { if (logInfoOut !== null) { logInfoOut.ray = raycaster.ray } - return raycaster.intersectObjects( objects, true ); + + const intersects = []; + + function intersectObject(object) { + + object.raycast( raycaster, intersects ); + + const children = object.children; + + if (object.visible) { + for ( let i = 0, l = children.length; i < l; i ++ ) { + intersectObject(children[ i ]); + } + } + } + + objects.forEach(intersectObject); + + intersects.sort((a, b) => { + if (Math.abs(a.distance - b.distance) < 0.01 && (a.object.raycastPriority || b.object.raycastPriority)) { + return b.object.raycastPriority||0 - a.object.raycastPriority||0; + } + return a.distance - b.distance; + }) + return intersects; } customRaycast(from3, to3, objects) { diff --git a/modules/ui/components/GenericExplorer.tsx b/modules/ui/components/GenericExplorer.tsx index 58438e3e..ef6f1e73 100644 --- a/modules/ui/components/GenericExplorer.tsx +++ b/modules/ui/components/GenericExplorer.tsx @@ -26,6 +26,8 @@ interface GenericExplorerNodeProps { defaultExpanded?: boolean; expandable: boolean; select: any; + onMouseEnter?: any; + onMouseLeave?: any; } export function GenericExplorerNode(props: GenericExplorerNodeProps) { @@ -36,7 +38,7 @@ export function GenericExplorerNode(props: GenericExplorerNodeProps) { const toggle = expandable ? (() => setExpanded(expanded => !expanded)) : undefined; return <> -
+
{expandable ? (expanded ? : ) : } diff --git a/modules/ui/components/GenericWizard.tsx b/modules/ui/components/GenericWizard.tsx index 8fa067da..21d3f9f1 100644 --- a/modules/ui/components/GenericWizard.tsx +++ b/modules/ui/components/GenericWizard.tsx @@ -7,18 +7,20 @@ import Stack from "ui/components/Stack"; import ButtonGroup from "ui/components/controls/ButtonGroup"; import Button from "ui/components/controls/Button"; -export function GenericWizard({topicId, title, left, className, children, onCancel, onOK, infoText, ...props}: { +export function GenericWizard({topicId, title, left, top, className, children, onCancel, onOK, infoText, ...props}: { topicId: string, title: string, left?: number, + top?: number, onCancel: () => any, onOK: () => any, onKeyDown?: (e) => any, - infoText: any + infoText?: any } & WindowProps ) { return diff --git a/modules/ui/components/Menu.jsx b/modules/ui/components/Menu.jsx index a0adb5cd..20921e5b 100644 --- a/modules/ui/components/Menu.jsx +++ b/modules/ui/components/Menu.jsx @@ -37,9 +37,9 @@ export function MenuItem({icon, label, hotKey, style, disabled, onClick, childre hotKey = null; } } - let clickHandler = disabled ? undefined : () => { + let clickHandler = disabled ? undefined : (e) => { closeAllUpPopups(); - onClick(); + onClick(e); }; return
e.stopPropagation()} style={style} onClick={clickHandler} {...props}> diff --git a/modules/ui/components/Stack.jsx b/modules/ui/components/Stack.tsx similarity index 55% rename from modules/ui/components/Stack.jsx rename to modules/ui/components/Stack.tsx index c50eb36e..6ca0a4a7 100644 --- a/modules/ui/components/Stack.jsx +++ b/modules/ui/components/Stack.tsx @@ -3,7 +3,9 @@ import React from 'react'; import ls from './Stack.less' import cx from "classnames"; -export default function Stack({className, ...props}) { +type StackProps = React.HTMLAttributes; + +export default function Stack({className, ...props}: StackProps) { return
} diff --git a/modules/ui/components/controls/Button.tsx b/modules/ui/components/controls/Button.tsx index 65df9ab5..bfa79053 100644 --- a/modules/ui/components/controls/Button.tsx +++ b/modules/ui/components/controls/Button.tsx @@ -2,16 +2,17 @@ import React from 'react'; import cx from 'classnames'; -export default function Button({type, onClick, className, ...props}: { +export default function Button({type, onClick, className, compact, ...props}: { type?: string, onClick: () => void, className? : string, children?: any, + compact?: boolean } & JSX.IntrinsicAttributes) { type = type || 'neutral'; - return - + + {open && setOpen(false)} title={props.dialogTitle}/>} diff --git a/modules/ui/components/controls/Field.jsx b/modules/ui/components/controls/Field.jsx index 5fe76d76..0bfb781d 100644 --- a/modules/ui/components/controls/Field.jsx +++ b/modules/ui/components/controls/Field.jsx @@ -1,8 +1,17 @@ -import React from 'react'; +import React, {useMemo} from 'react'; import ls from './Field.less' import cx from 'classnames'; +export const FieldId = React.createContext(-1); + +let ID_GENERATOR = 0; + export default function Field({active, name, ...props}) { - return
+ + const fieldId = useMemo(() => 'Field_' + (ID_GENERATOR++), []); + + return +
+ ; } diff --git a/modules/ui/components/controls/Field.less b/modules/ui/components/controls/Field.less index 49dd08b4..1835302b 100644 --- a/modules/ui/components/controls/Field.less +++ b/modules/ui/components/controls/Field.less @@ -3,7 +3,8 @@ .root { display: flex; justify-content: space-between; - align-items: baseline; + align-items: center; + } .active { diff --git a/modules/ui/components/controls/Label.jsx b/modules/ui/components/controls/Label.jsx index d44abc23..f9ad9cfb 100644 --- a/modules/ui/components/controls/Label.jsx +++ b/modules/ui/components/controls/Label.jsx @@ -1,5 +1,7 @@ -import React from 'react'; +import React, {useContext} from 'react'; +import {FieldId} from "ui/components/controls/Field"; export default function Label({children}) { - return {children} + const fieldId = useContext(FieldId); + return } diff --git a/package.json b/package.json index e1e36572..32262bd3 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,6 @@ "marked": "^1.0.0", "mousetrap": "1.6.1", "numeric": "1.2.6", - "opencascade.js": "^1.1.1", "prop-types": "15.6.0", "react": "^16.13.1", "react-color": "^2.19.3", diff --git a/web/app/cad/actions/ActionButtonBehavior.jsx b/web/app/cad/actions/ActionButtonBehavior.jsx index 63570292..e35143cf 100644 --- a/web/app/cad/actions/ActionButtonBehavior.jsx +++ b/web/app/cad/actions/ActionButtonBehavior.jsx @@ -19,7 +19,10 @@ export function ActionButtonBehavior({children, actionId}) { return children({ 'data-action-id': actionId, - onClick: e => actionService.run(actionId, e), + onClick: e => { + canceled = true; + actionService.run(actionId, e); + }, onMouseEnter: e => { updateCoords(e); canceled = false; diff --git a/web/app/cad/actions/actionHelpers.js b/web/app/cad/actions/actionHelpers.js index 1c14a643..50813d02 100644 --- a/web/app/cad/actions/actionHelpers.js +++ b/web/app/cad/actions/actionHelpers.js @@ -29,3 +29,13 @@ export function requiresSolidSelection(amount) { update: checkForSelectedSolids(amount) } } + +export const RequiresAnyModelSelection = { + listens: ctx => ctx.streams.selection.all, + update: (state, selection) => { + state.enabled = selection.length > 0; + if (!state.enabled) { + state.hint = 'requires at least one model selected'; + } + } +} \ No newline at end of file diff --git a/web/app/cad/actions/actionSystemPlugin.ts b/web/app/cad/actions/actionSystemPlugin.ts index aa67df29..d4d5de19 100644 --- a/web/app/cad/actions/actionSystemPlugin.ts +++ b/web/app/cad/actions/actionSystemPlugin.ts @@ -155,10 +155,12 @@ export interface ActionService { hint$: StateStream; } -declare module 'context' { - interface CoreContext { - - actionService: ActionService; - } +export interface ActionSystemPlugin { + actionService: ActionService; +} + + +declare module 'context' { + interface CoreContext extends ActionSystemPlugin {} } diff --git a/web/app/cad/attributes/attributesPlugin.ts b/web/app/cad/attributes/attributesPlugin.ts index c72ccf29..1bb13685 100644 --- a/web/app/cad/attributes/attributesPlugin.ts +++ b/web/app/cad/attributes/attributesPlugin.ts @@ -1,8 +1,14 @@ import {Plugin} from "plugable/pluginSystem"; import {AttributesService} from "cad/attributes/attributesService"; +import {contributeComponent} from "cad/dom/components/ContributedComponents"; +import {DisplayOptionsDialogManager} from "cad/attributes/ui/DisplayOptionsDialog"; +import {ActionSystemPlugin} from "cad/actions/actionSystemPlugin"; +import {RequiresAnyModelSelection} from "cad/actions/actionHelpers"; +import {IoColorPalette} from "react-icons/io5"; +import {FaTable} from "react-icons/fa"; +import {ApplicationContext} from "context"; -interface AttributesPluginInputContext { -} +type AttributesPluginInputContext = ActionSystemPlugin; export interface AttributesPluginContext { attributesService: AttributesService; @@ -17,6 +23,7 @@ declare module 'context' { export const AttributesPlugin: Plugin = { inputContextSpec: { + actionService: 'required', }, outputContextSpec: { @@ -25,6 +32,31 @@ export const AttributesPlugin: Plugin ctx.attributesService + .openDisplayOptionsEditor(ctx.entityContextService.selectedIds, e), + ...RequiresAnyModelSelection, + }, + { + id: 'ModelAttributesEditor', + appearance: { + icon: FaTable, + label: 'edit attributes...', + info: 'open a dialog with attributes editor for the model', + }, + invoke: ({services}) => services.sketcher.sketchFace(services.selection.face.single), + ...RequiresAnyModelSelection, + }, + ]) }, } diff --git a/web/app/cad/attributes/attributesService.ts b/web/app/cad/attributes/attributesService.ts index 56d2888e..6bc2d881 100644 --- a/web/app/cad/attributes/attributesService.ts +++ b/web/app/cad/attributes/attributesService.ts @@ -1,4 +1,11 @@ import {LazyStreams} from "lstream/lazyStreams"; +import {state} from "lstream"; + +export interface ModelAttributes { + hidden: boolean; + label: string; + color: string; +} export class AttributesService { @@ -8,12 +15,28 @@ export class AttributesService { color: null })); + displayOptionsEditors$ = state({}); + + attributesEditors$ = state({}); + + openDisplayOptionsEditor(modelIds: string[], e: any) { + this.displayOptionsEditors$.mutate(editors => { + const copy = [...modelIds].sort(); + editors[copy.join(':')] = { + x: e.pageX, + y: e.pageY, + models: copy + }; + }); + } + } -export interface ModelAttributes { - hidden: boolean; - label: string; - color: string; +export type EditorSet = { + [key: string]: { + x: number, + y: number, + models: string[] + } } - diff --git a/web/app/cad/attributes/ui/DisplayOptionsDialog.tsx b/web/app/cad/attributes/ui/DisplayOptionsDialog.tsx new file mode 100644 index 00000000..5e810bb3 --- /dev/null +++ b/web/app/cad/attributes/ui/DisplayOptionsDialog.tsx @@ -0,0 +1,76 @@ +import React, {useContext} from "react"; +import {useStreamWithPatcher, useStreamWithUpdater} from "ui/effects"; +import Stack from "ui/components/Stack"; +import Field from "ui/components/controls/Field"; +import Label from "ui/components/controls/Label"; +import {ColorControl} from "ui/components/controls/ColorControl"; +import CheckboxControl from "ui/components/controls/CheckboxControl"; +import {AppContext} from "cad/dom/components/AppContext"; +import {ModelAttributes} from "cad/attributes/attributesService"; +import {GenericWizard} from "ui/components/GenericWizard"; + +export function DisplayOptionsDialogManager() { + + const [editors, update] = useStreamWithUpdater(ctx => ctx.attributesService.displayOptionsEditors$); + + function close(key: string) { + update(editor => { + delete editor[key]; + return editor; + }); + } + + return + {Object.keys(editors).map(key => { + const request = editors[key]; + return close(key)} + onOK={() => close(key)} + onClose={() => close(key)} + topicId='entity-display-options' + left={request.x} + top={request.y}> + + + })} + ; +} + + +export interface DisplayOptionsViewProps { + modelIds: string[]; +} + +export function DisplayOptionsView(props: DisplayOptionsViewProps) { + + const ctx = useContext(AppContext); + const streamsAndPatchers: [ModelAttributes, any][] = []; + + for (let modelId of props.modelIds) { + const streamAndPatcher = useStreamWithPatcher(ctx => ctx.attributesService.streams.get(modelId)); + streamsAndPatchers.push(streamAndPatcher); + } + + function patchAttrs(mutator) { + for (let [model, patch] of streamsAndPatchers) { + patch(mutator); + } + } + + const [[proto]] = streamsAndPatchers; + const attrs = proto; + + const DO_NOTHING = ()=>{}; + return + + + patchAttrs(attrs => attrs.hidden = !val)}/> + + + + patchAttrs(attrs => attrs.color = val)}/> + + ; +} diff --git a/web/app/cad/craft/ui/SceneInlineObjectExplorer.tsx b/web/app/cad/craft/ui/SceneInlineObjectExplorer.tsx index 360b3905..5879367a 100644 --- a/web/app/cad/craft/ui/SceneInlineObjectExplorer.tsx +++ b/web/app/cad/craft/ui/SceneInlineObjectExplorer.tsx @@ -23,7 +23,7 @@ export function SceneInlineObjectExplorer() { return {models.map(m => { if (m instanceof MOpenFaceShell) { - return + return } else if (m instanceof MShell) { return
@@ -101,6 +101,8 @@ function ModelSection({model, type, typeLabel, expandable = true, controlVisibil label={label} selected={selected} select={select} + onMouseEnter={() => ctx.highlightService.highlight(model.id)} + onMouseLeave={() => ctx.highlightService.unHighlight(model.id)} controls={ <> {controlVisibility && } diff --git a/web/app/cad/craft/ui/VisibleSwitch.tsx b/web/app/cad/craft/ui/VisibleSwitch.tsx deleted file mode 100644 index ea77f726..00000000 --- a/web/app/cad/craft/ui/VisibleSwitch.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from "react"; -import {AiOutlineEye} from "react-icons/ai"; -import {state, StateStream} from "lstream"; -import {useStreamWithPatcher} from "ui/effects"; - -class LazyStreams { - - index = new Map>(); - proto: (id: string) => T; - - constructor(proto?: (id: string) => T) { - this.proto = proto || ((id: string) => null); - } - - get(id: string) { - - let state$: StateStream = this.index[id]; - if (state$ == null) { - state$ = state(this.proto(id)); - this.index[id] = state$; - } - return state$; - } - -} - -export interface ModelAttributes { - - hidden: boolean - -} - -const modelAttrStreams = new LazyStreams(id => ({} as ModelAttributes)); - diff --git a/web/app/cad/craft/wizard/components/form/EntityList.jsx b/web/app/cad/craft/wizard/components/form/EntityList.jsx index 3af44cc9..84026c0b 100644 --- a/web/app/cad/craft/wizard/components/form/EntityList.jsx +++ b/web/app/cad/craft/wizard/components/form/EntityList.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useContext} from 'react'; import ls from './EntityList.less'; import Label from 'ui/components/controls/Label'; import Field from 'ui/components/controls/Field'; @@ -6,12 +6,17 @@ import Fa from 'ui/components/Fa'; import {attachToForm} from './Form'; import {camelCaseSplitToStr} from 'gems/camelCaseSplit'; import {EMPTY_ARRAY, removeInPlace} from 'gems/iterables'; +import {AppContext} from "cad/dom/components/AppContext"; -@attachToForm -export default class EntityList extends React.Component { - deselect = (entityId) => { - let {value, onChange} = this.props; +function EntityList(props) { + + const ctx = useContext(AppContext); + + let {name, label, active, setActive, value, placeholder, readOnly, entityRenderer = e => e} = props; + + const deselect = (entityId) => { + let {value, onChange} = props; if (Array.isArray(value)) { onChange(removeInPlace(value, entityId)); } else { @@ -19,26 +24,27 @@ export default class EntityList extends React.Component { } }; - render() { - let {name, label, active, setActive, value, placeholder, readOnly, onEntityEnter, onEntityLeave, entityRenderer = e => e} = this.props; - if (!Array.isArray(value)) { - value = value ? asArray(value) : EMPTY_ARRAY; - } - return - -
{value.length === 0 ? - {placeholder || ''} : - value.map((entity, i) => onEntityEnter&&onEntityEnter(entity)} - onMouseLeave={() => onEntityLeave&&onEntityLeave(entity)}> - {entityRenderer(entity)} - {!readOnly && this.deselect(entity)}> } - )} -
-
; + + if (!Array.isArray(value)) { + value = value ? asArray(value) : EMPTY_ARRAY; } + return + +
{value.length === 0 ? + {placeholder || ''} : + value.map((entity, i) => ctx.highlightService.highlight(entity)} + onMouseLeave={() => ctx.highlightService.unHighlight(entity)}> + {entityRenderer(entity)} + {!readOnly && deselect(entity)}> } + )} +
+
; + } +export default attachToForm(EntityList); + function asArray(val) { _arr[0] = val; return _arr; diff --git a/web/app/cad/dom/components/ContributedComponents.jsx b/web/app/cad/dom/components/ContributedComponents.jsx index 0d67c525..25ffdb51 100644 --- a/web/app/cad/dom/components/ContributedComponents.jsx +++ b/web/app/cad/dom/components/ContributedComponents.jsx @@ -1,7 +1,7 @@ import React from 'react'; import {useStream} from 'ui/effects'; import {state} from 'lstream'; -import {Scope} from "../../../sketcher/components/Scope"; +import {Scope} from "sketcher/components/Scope"; const CONTRIBUTED_COMPONENTS$ = state([]); diff --git a/web/app/cad/dom/menu/MenuHolder.jsx b/web/app/cad/dom/menu/MenuHolder.jsx index 0fde1d56..10575a0c 100644 --- a/web/app/cad/dom/menu/MenuHolder.jsx +++ b/web/app/cad/dom/menu/MenuHolder.jsx @@ -23,29 +23,34 @@ function ActionMenu({actions, keymap, ...menuState}) { ; } -function ActionMenuItem({label, cssIcons, icon32, icon96, enabled, hotKey, visible, actionId, ...props}) { +function ActionMenuItem({label, cssIcons, icon, icon32, icon96, enabled, hotKey, visible, actionId, ...props}) { if (!visible) { return null; } - let icon, style; - if (icon32 || icon96) { - let size = 16; - icon = ; - style = { - backgroundImage: `url(${icon32 || icon96})`, - backgroundRepeat: 'no-repeat', - backgroundSize: `${size}px ${size}px`, - backgroundPositionX: 5, - backgroundPositionY: 4, - }; - if (!enabled) { - style.filter = 'grayscale(90%)'; + let renderedIcon, style; + if (icon) { + const Icon = icon; + renderedIcon = ; + } else { + if (icon32 || icon96) { + let size = 16; + renderedIcon = ; + style = { + backgroundImage: `url(${icon32 || icon96})`, + backgroundRepeat: 'no-repeat', + backgroundSize: `${size}px ${size}px`, + backgroundPositionX: 5, + backgroundPositionY: 4, + }; + if (!enabled) { + style.filter = 'grayscale(90%)'; + } + } else if (cssIcons) { + renderedIcon = ; } - } else if (cssIcons) { - icon = ; } - - return ; + + return ; } const ConnectedActionMenu = connect((streams, props) => diff --git a/web/app/cad/init/startApplication.js b/web/app/cad/init/startApplication.js index 6e060de9..16ccd830 100644 --- a/web/app/cad/init/startApplication.js +++ b/web/app/cad/init/startApplication.js @@ -25,7 +25,7 @@ import * as SketcherPlugin from '../sketch/sketcherPlugin'; import * as SketcherStoragePlugin from '../sketch/sketchStoragePlugin'; import * as ExportPlugin from '../exportPlugin'; import * as ExposurePlugin from '../exposure/exposurePlugin'; -import * as ViewSyncPlugin from '../scene/viewSyncPlugin'; +import {ViewSyncPlugin} from '../scene/viewSyncPlugin'; import * as EntityContextPlugin from '../scene/entityContextPlugin'; import * as OCCTPlugin from '../craft/e0/occtPlugin'; @@ -41,6 +41,7 @@ import * as AssemblyPlugin from "../assembly/assemblyPlugin"; import {WorkbenchesLoaderPlugin} from "cad/workbench/workbenchesLoaderPlugin"; import {PluginSystem} from "plugable/pluginSystem"; import {AttributesPlugin} from "cad/attributes/attributesPlugin"; +import {HighlightPlugin} from "cad/scene/highlightPlugin"; export default function startApplication(callback) { @@ -85,7 +86,8 @@ export default function startApplication(callback) { RemotePartsPlugin, ViewSyncPlugin, WizardSelectionPlugin, - AttributesPlugin + AttributesPlugin, + HighlightPlugin ]; let allPlugins = [...preUIPlugins, ...plugins]; diff --git a/web/app/cad/scene/controls/mouseEventSystemPlugin.js b/web/app/cad/scene/controls/mouseEventSystemPlugin.js index d2b1ff09..d1427f90 100644 --- a/web/app/cad/scene/controls/mouseEventSystemPlugin.js +++ b/web/app/cad/scene/controls/mouseEventSystemPlugin.js @@ -1,8 +1,8 @@ import {printRaycastDebugInfo, RayCastDebugInfo} from "./rayCastDebug"; import {LOG_FLAGS} from "../../logFlags"; -export function activate(context) { - const {services, streams} = context; +export function activate(ctx) { + const {services, streams} = ctx; const domElement = services.viewer.sceneSetup.domElement(); const event = { viewer: services.viewer @@ -11,8 +11,22 @@ export function activate(context) { domElement.addEventListener('mousedown', mousedown, false); domElement.addEventListener('mouseup', mouseup, false); domElement.addEventListener('mousemove', mousemove, false); + domElement.addEventListener('contextmenu', (e) => ctx.actionService.run('menu.contextual', { + x: e.offsetX, + y: e.offsetY + }), false); - let performRaycast = e => services.viewer.raycast(e, services.cadScene.workGroup.children, RayCastDebugInfo); + + let performRaycast = e => { + const hits = services.viewer.raycast(e, services.cadScene.workGroup.children, RayCastDebugInfo); + hits.sort((a, b) => { + if (Math.abs(a.distance - b.distance) < 0.01 && (a.object.raycastPriority || b.object.raycastPriority)) { + return b.object.raycastPriority||0 - a.object.raycastPriority||0; + } + return a.distance - b.distance; + }) + return hits; + } let toDrag = null; let pressed = new Set(); @@ -102,35 +116,6 @@ export function activate(context) { } else { let hits = performRaycast(e); dispatchMousemove(e, hits) - event.hits = hits; - - valid.clear(); - for (let hit of hits) { - valid.add(hit.object); - if (!hit.object.passMouseEvent || !hit.object.passMouseEvent(event)) { - break; - } - } - - entered.forEach(el => { - if (!valid.has(el) && el.onMouseLeave) { - el.onMouseLeave(event); - } - }); - - valid.forEach(el => { - if (!entered.has(el) && el.onMouseEnter) { - el.onMouseEnter(event); - } - if (el.onMouseMove) { - el.onMouseMove(event); - } - }); - - let t = valid; - valid = entered; - entered = t; - valid.clear(); } } @@ -147,7 +132,8 @@ export function activate(context) { } entered.forEach(el => { - if (!valid.has(el) && el.onMouseLeave) { + //need to check parent in case of object removed + if (!valid.has(el) && el.onMouseLeave && el.parent) { el.onMouseLeave(event); } }); @@ -167,7 +153,7 @@ export function activate(context) { valid.clear(); } - context.services.modelMouseEventSystem = { + ctx.services.modelMouseEventSystem = { dispatchMousedown, dispatchMouseup, dispatchMousemove } } diff --git a/web/app/cad/scene/controls/pickControlPlugin.ts b/web/app/cad/scene/controls/pickControlPlugin.ts index ae205241..d012532e 100644 --- a/web/app/cad/scene/controls/pickControlPlugin.ts +++ b/web/app/cad/scene/controls/pickControlPlugin.ts @@ -1,8 +1,7 @@ import * as mask from 'gems/mask' -import {getAttribute, setAttribute} from 'scene/objectData'; +import {getAttribute} from 'scene/objectData'; import {FACE, EDGE, SKETCH_OBJECT, DATUM, SHELL, DATUM_AXIS, LOOP} from '../../model/entities'; -import {LOG_FLAGS} from '../../logFlags'; -import * as vec from 'math/vec'; +import {LOG_FLAGS} from 'cad/logFlags'; import {initRayCastDebug, printRaycastDebugInfo, RayCastDebugInfo} from "./rayCastDebug"; export interface PickControlService { @@ -157,7 +156,9 @@ export function activate(context) { function handleSolidPick(e) { let pickResults = services.viewer.raycast(e, services.cadScene.workGroup.children); traversePickResults(e, pickResults, PICK_KIND.FACE, (sketchFace) => { - context.locationService.edit(sketchFace.shell); + const shell = sketchFace.shell; + services.marker.markExclusively(shell.TYPE, shell.id); + context.locationService.edit(shell); return false; }); } diff --git a/web/app/cad/scene/entityContextPlugin.ts b/web/app/cad/scene/entityContextPlugin.ts index 22c6b0c7..f550462d 100644 --- a/web/app/cad/scene/entityContextPlugin.ts +++ b/web/app/cad/scene/entityContextPlugin.ts @@ -1,9 +1,7 @@ -import {state, StateStream} from 'lstream'; +import {combine, state, StateStream, Stream} from 'lstream'; -import {addToListInMap} from 'gems/iterables'; -import {EMPTY_ARRAY} from 'gems/iterables'; -import {DATUM, FACE, SHELL, SKETCH_OBJECT, EDGE, LOOP} from '../model/entities'; -import {combine} from "lstream"; +import {addToListInMap, EMPTY_ARRAY} from 'gems/iterables'; +import {DATUM, EDGE, FACE, LOOP, SHELL, SKETCH_OBJECT} from '../model/entities'; import {MObject} from "cad/model/mobject"; export const SELECTABLE_ENTITIES = [FACE, EDGE, SKETCH_OBJECT, DATUM, SHELL]; @@ -14,7 +12,7 @@ export function defineStreams(ctx) { ctx.streams.selection[entity] = state([]); }); ctx.streams.selection.all = combine(...(Object.values(ctx.streams.selection) as StateStream[])) - .map(selection => [].concat(...selection)).throttle(); + .map((selection:string[]) => [].concat(...selection)).remember(); } export function activate(ctx) { @@ -58,6 +56,9 @@ export function activate(ctx) { }) ctx.entityContextService = { + get selectedIds() { + return ctx.streams.selection.all.value + }, selectedEntities: ctx.streams.selection.all.map(ids => ids.map(ctx.cadRegistry.find)).remember() } } @@ -66,6 +67,7 @@ declare module 'context' { interface CoreContext { entityContextService: { + selectedIds: string[], selectedEntities: StateStream }; } diff --git a/web/app/cad/scene/highlightPlugin.ts b/web/app/cad/scene/highlightPlugin.ts new file mode 100644 index 00000000..59835caa --- /dev/null +++ b/web/app/cad/scene/highlightPlugin.ts @@ -0,0 +1,56 @@ +import {Plugin} from "plugable/pluginSystem"; +import {combine, stream} from "lstream"; +import Viewer from "cad/scene/viewer"; + +export class HighlightService { + + highlightEvents = stream(); + unHighlightEvents = stream(); + + constructor(viewer: Viewer) { + combine(this.highlightEvents, this.unHighlightEvents) + .throttle() + .attach(() => viewer.requestRender()) + } + + highlight(id: string) { + this.highlightEvents.next(id); + } + + unHighlight(id: string) { + this.unHighlightEvents.next(id); + } + +} + +interface HighlightPluginInputContext { + viewer: Viewer; +} + +export interface HighlightPluginContext { + highlightService: HighlightService; +} + +type HighlightPluginWorkingContext = HighlightPluginInputContext&HighlightPluginContext; + +declare module 'context' { + interface ApplicationContext extends HighlightPluginContext {} +} + +export const HighlightPlugin: Plugin = { + + inputContextSpec: { + viewer: 'required', + }, + + outputContextSpec: { + highlightService: 'required', + }, + + activate(ctx: HighlightPluginWorkingContext) { + ctx.highlightService = new HighlightService(ctx.viewer); + }, + +} + + diff --git a/web/app/cad/scene/selectionMarker/markerPlugin.ts b/web/app/cad/scene/selectionMarker/markerPlugin.ts index 30cd2058..a32e0d47 100644 --- a/web/app/cad/scene/selectionMarker/markerPlugin.ts +++ b/web/app/cad/scene/selectionMarker/markerPlugin.ts @@ -52,12 +52,12 @@ function createMarker(findEntity, requestRender) { return; } marked.set(id, mObj); - mObj.ext.view && mObj.ext.view.mark(color); + mObj.ext.view && mObj.ext.view.mark('selection'); } function doWithdraw(obj) { marked.delete(obj.id); - obj.ext.view && obj.ext.view.withdraw(); + obj.ext.view && obj.ext.view.withdraw('selection'); } function onUpdate() { @@ -67,7 +67,7 @@ function createMarker(findEntity, requestRender) { function clear() { if (marked.size !== 0) { - marked.forEach(m => m.ext.view && m.ext.view.withdraw()); + marked.forEach(m => m.ext.view && m.ext.view.withdraw('selection')); marked.clear(); onUpdate(); } diff --git a/web/app/cad/scene/viewSyncPlugin.js b/web/app/cad/scene/viewSyncPlugin.js index f9030477..9406b584 100644 --- a/web/app/cad/scene/viewSyncPlugin.js +++ b/web/app/cad/scene/viewSyncPlugin.js @@ -9,15 +9,39 @@ import DatumView from './views/datumView'; import {View} from './views/view'; import {SketchingView} from "cad/scene/views/faceView"; -export function activate(context) { - let {streams} = context; - streams.cadRegistry.update.attach(sceneSynchronizer(context)); - streams.sketcher.update.attach(mFace => mFace.ext.view.updateSketch()); +export const ViewSyncPlugin = { + + inputContextSpec: { + highlightService: 'required', + attributesService: 'required', + }, + + outputContextSpec: { + }, + + activate(ctx) { + let {streams} = ctx; + ctx.highlightService.highlightEvents.attach(id => { + const model = ctx.cadRegistry.find(id); + model?.ext?.view?.mark('highlight'); + }); + ctx.highlightService.unHighlightEvents.attach(id => { + const model = ctx.cadRegistry.find(id); + model?.ext?.view?.withdraw('highlight'); + }); + + streams.cadRegistry.update.attach(sceneSynchronizer(ctx)); + streams.sketcher.update.attach(mFace => mFace.ext.view.updateSketch()); + }, } + function sceneSynchronizer(ctx) { const {services: {cadScene, cadRegistry, viewer, wizard, action, pickControl}} = ctx; + let xxx = 0; return function() { + console.log("sceneSynchronizer update" + (xxx++)) + let wgChildren = cadScene.workGroup.children; let existent = new Set(); for (let i = wgChildren.length - 1; i >= 0; --i) { @@ -25,12 +49,12 @@ function sceneSynchronizer(ctx) { let shellView = getAttribute(obj, View.MARKER); if (shellView) { let exists = cadRegistry.modelIndex.has(shellView.model.id); - if (!exists) { + // if (!exists) { SceneGraph.removeFromGroup(cadScene.workGroup, obj); shellView.dispose(); - } else { - existent.add(shellView.model.id); - } + // } else { + // existent.add(shellView.model.id); + // } } } @@ -38,12 +62,11 @@ function sceneSynchronizer(ctx) { if (!existent.has(model.id)) { let modelView; if (model instanceof MOpenFaceShell) { - modelView = new OpenFaceShellView(model); + modelView = new OpenFaceShellView(ctx, model); } else if (model instanceof MShell) { - modelView = new ShellView(model, undefined, viewer); + modelView = new ShellView(ctx, model, undefined,); } else if (model instanceof MDatum) { - modelView = new DatumView(model, viewer, - wizard.open, + modelView = new DatumView(ctx, model, wizard.open, datum => pickControl.pick(datum), e => action.run('menu.datum', e), wizard.isInProgress); @@ -69,6 +92,7 @@ function sceneSynchronizer(ctx) { SceneGraph.addToGroup(cadScene.workGroup, modelView.rootGroup); } } + viewer.requestRender(); } } \ No newline at end of file diff --git a/web/app/cad/scene/views/curveBasedView.js b/web/app/cad/scene/views/curveBasedView.js index 8b0fcc73..3b647aae 100644 --- a/web/app/cad/scene/views/curveBasedView.js +++ b/web/app/cad/scene/views/curveBasedView.js @@ -4,12 +4,12 @@ import {setAttribute} from 'scene/objectData'; import ScalableLine from 'scene/objects/scalableLine'; export class CurveBasedView extends View { - constructor(model, tessellation, visualWidth, markerWidth, color, defaultMarkColor) { - super(model); + constructor(ctx, model, tessellation, visualWidth, markerWidth, color, defaultMarkColor, offset, markTable) { + super(ctx, model, undefined, markTable); this.rootGroup = SceneGraph.createGroup(); - this.representation = new ScalableLine(tessellation, visualWidth, color, undefined, false, true); - this.marker = new ScalableLine(tessellation, markerWidth, defaultMarkColor, undefined, false, true); - this.picker = new ScalableLine(tessellation, 10, 0xFA8072, undefined, false, true); + this.representation = new ScalableLine(tessellation, visualWidth, color, undefined, false, true, offset); + this.marker = new ScalableLine(tessellation, markerWidth, defaultMarkColor, undefined, false, true, offset); + this.picker = new ScalableLine(tessellation, 10, 0xFA8072, undefined, false, true, offset); this.marker.visible = false; this.picker.material.visible = false; @@ -19,14 +19,25 @@ export class CurveBasedView extends View { this.rootGroup.add(this.representation); this.rootGroup.add(this.marker); this.rootGroup.add(this.picker); + this.picker.onMouseEnter = () => { + this.ctx.highlightService.highlight(this.model.id); + } + this.picker.onMouseLeave = () => { + this.ctx.highlightService.unHighlight(this.model.id); + } } - mark(color) { - this.marker.visible = true; - } - withdraw(color) { - this.marker.visible = false; + updateVisuals() { + const markColor = this.markColor; + if (!markColor) { + this.marker.visible = false; + this.representation.visible = true; + } else { + this.marker.material.color.set(markColor); + this.marker.visible = true; + this.representation.visible = false; + } } dispose() { diff --git a/web/app/cad/scene/views/datumView.js b/web/app/cad/scene/views/datumView.js index 0744470b..2573b084 100644 --- a/web/app/cad/scene/views/datumView.js +++ b/web/app/cad/scene/views/datumView.js @@ -7,8 +7,10 @@ import {CSYS_SIZE_MODEL} from '../../craft/datum/csysObject'; export default class DatumView extends View { - constructor(datum, viewer, beginOperation, selectDatum, showDatumMenu, isReadOnly) { - super(datum); + constructor(ctx, datum, beginOperation, selectDatum, showDatumMenu, isReadOnly) { + super(ctx, datum); + + const viewer = ctx.viewer; class MenuButton extends Mesh { @@ -137,9 +139,9 @@ export default class DatumView extends View { setAttribute(this.rootGroup, DATUM, this); setAttribute(this.rootGroup, View.MARKER, this); - this.xAxisView = new DatumAxisView(this.model.xAxis, dv.csysObj.xAxis); - this.yAxisView = new DatumAxisView(this.model.yAxis, dv.csysObj.yAxis); - this.zAxisView = new DatumAxisView(this.model.zAxis, dv.csysObj.zAxis); + this.xAxisView = new DatumAxisView(ctx, this.model.xAxis, dv.csysObj.xAxis); + this.yAxisView = new DatumAxisView(ctx, this.model.yAxis, dv.csysObj.yAxis); + this.zAxisView = new DatumAxisView(ctx, this.model.zAxis, dv.csysObj.zAxis); } dispose() { @@ -178,8 +180,8 @@ class AffordanceBox extends Mesh { class DatumAxisView extends View { - constructor(model, axisArrow) { - super(model); + constructor(ctx, model, axisArrow) { + super(ctx, model); this.axisArrow = axisArrow; setAttribute(this.axisArrow.handle, DATUM_AXIS, this); } diff --git a/web/app/cad/scene/views/edgeView.js b/web/app/cad/scene/views/edgeView.js index a95bc651..dbdb25e5 100644 --- a/web/app/cad/scene/views/edgeView.js +++ b/web/app/cad/scene/views/edgeView.js @@ -1,10 +1,23 @@ import {CurveBasedView} from './curveBasedView'; +const MarkerTable = [ + { + type: 'selection', + priority: 10, + colors: [0xc42720], + }, + { + type: 'highlight', + priority: 1, + colors: [0xffebcd, 0xFF00FF], + }, +]; + export class EdgeView extends CurveBasedView { - constructor(edge) { + constructor(ctx, edge) { let brepEdge = edge.brepEdge; let tess = brepEdge.data.tessellation ? brepEdge.data.tessellation : brepEdge.curve.tessellateToData(); - super(edge, tess, 2, 3, 0x2B3856, 0xc42720); + super(ctx, edge, tess, 2, 4, 0x2B3856, 0xc42720, false, MarkerTable); } } diff --git a/web/app/cad/scene/views/faceView.js b/web/app/cad/scene/views/faceView.js index 9d34324f..83dfa400 100644 --- a/web/app/cad/scene/views/faceView.js +++ b/web/app/cad/scene/views/faceView.js @@ -5,11 +5,14 @@ import * as SceneGraph from 'scene/sceneGraph'; import {SketchObjectView} from './sketchObjectView'; import {View} from './view'; import {SketchLoopView} from './sketchLoopView'; +import {createSolidMaterial} from "cad/scene/wrappers/sceneObject"; +import {SketchMesh} from "cad/scene/views/shellView"; +import {Geometry} from "three"; export class SketchingView extends View { - constructor(face) { - super(face); + constructor(ctx, face, parent) { + super(ctx, face, parent); this.sketchGroup = SceneGraph.createGroup(); this.sketchObjectViews = []; this.sketchLoopViews = []; @@ -24,17 +27,25 @@ export class SketchingView extends View { const sketchTr = this.model.sketchToWorldTransformation; for (let sketchObject of this.model.sketchObjects) { - let sov = new SketchObjectView(sketchObject, sketchTr); + let sov = new SketchObjectView(this.ctx, sketchObject, sketchTr); SceneGraph.addToGroup(this.sketchGroup, sov.rootGroup); this.sketchObjectViews.push(sov); } this.model.sketchLoops.forEach(mLoop => { - let loopView = new SketchLoopView(mLoop); + let loopView = new SketchLoopView(this.ctx, mLoop); SceneGraph.addToGroup(this.sketchGroup, loopView.rootGroup); this.sketchLoopViews.push(loopView); }); } - + + setColor(color) { + this.color = color; + } + + updateVisuals() { + this.mesh.material.color.set(this.markColor||this.parent.markColor||this.color||this.parent.color||NULL_COLOR); + } + disposeSketch() { this.sketchObjectViews.forEach(o => o.dispose()); this.sketchLoopViews.forEach(o => o.dispose()); @@ -51,34 +62,40 @@ export class SketchingView extends View { export class FaceView extends SketchingView { - constructor(face, geometry) { - super(face); - this.geometry = geometry; + constructor(ctx, face, parent, skin) { + super(ctx, face, parent); + const geom = new Geometry(); + geom.dynamic = true; + this.geometry = geom; + + this.material = createSolidMaterial(skin); this.meshFaces = []; - let off = geometry.faces.length; + + const off = geom.faces.length; if (face.brepFace.data.tessellation) { - tessDataToGeom(face.brepFace.data.tessellation.data, geometry) + tessDataToGeom(face.brepFace.data.tessellation.data, geom) } else { - brepFaceToGeom(face.brepFace, geometry); + brepFaceToGeom(face.brepFace, geom); } - for (let i = off; i < geometry.faces.length; i++) { - const meshFace = geometry.faces[i]; + for (let i = off; i < geom.faces.length; i++) { + const meshFace = geom.faces[i]; this.meshFaces.push(meshFace); setAttribute(meshFace, FACE, this); } - } - - mark(color) { - this.updateColor(color || SELECTION_COLOR); + geom.mergeVertices(); + this.mesh = new SketchMesh(geom, this.material); + this.mesh.onMouseEnter = () => { + this.ctx.highlightService.highlight(this.model.id); + } + this.mesh.onMouseLeave = () => { + this.ctx.highlightService.unHighlight(this.model.id); + } + this.rootGroup.add(this.mesh); } - withdraw(color) { - this.updateColor(null); - } - - updateColor(color) { - setFacesColor(this.meshFaces, color||this.color); - this.geometry.colorsNeedUpdate = true; + dispose() { + super.dispose(); + this.material.dispose(); } } @@ -93,4 +110,4 @@ export function setFacesColor(faces, color) { } export const NULL_COLOR = new THREE.Color(); -export const SELECTION_COLOR = 0xffff80; + diff --git a/web/app/cad/scene/views/openFaceView.js b/web/app/cad/scene/views/openFaceView.js index 17a8612b..b96e121c 100644 --- a/web/app/cad/scene/views/openFaceView.js +++ b/web/app/cad/scene/views/openFaceView.js @@ -1,14 +1,14 @@ import {setAttribute} from 'scene/objectData'; import {FACE, SHELL} from '../../model/entities'; -import {NULL_COLOR, SELECTION_COLOR, setFacesColor, SketchingView} from './faceView'; +import {SketchingView} from './faceView'; import {View} from './view'; import {SketchMesh} from './shellView'; export class OpenFaceShellView extends View { - constructor(shell) { - super(shell); - this.openFace = new OpenFaceView(shell.face); + constructor(ctx, shell) { + super(ctx, shell); + this.openFace = new OpenFaceView(ctx, shell.face, this); setAttribute(this.rootGroup, SHELL, this); setAttribute(this.rootGroup, View.MARKER, this); } @@ -24,8 +24,8 @@ export class OpenFaceShellView extends View { export class OpenFaceView extends SketchingView { - constructor(mFace) { - super(mFace); + constructor(ctx, mFace, parent) { + super(ctx, mFace, parent); this.material = new THREE.MeshPhongMaterial({ vertexColors: THREE.FaceColors, // color: 0xB0C4DE, @@ -57,10 +57,15 @@ export class OpenFaceView extends SketchingView { geometry.computeFaceNormals(); this.mesh = new SketchMesh(geometry, this.material); this.rootGroup.add(this.mesh); + this.mesh.onMouseEnter = () => { + this.ctx.highlightService.highlight(this.model.id); + } + this.mesh.onMouseLeave = () => { + this.ctx.highlightService.unHighlight(this.model.id); + } } updateBounds() { - let markedColor = this.markedColor; this.dropGeometry(); let bounds2d = []; @@ -72,7 +77,7 @@ export class OpenFaceView extends SketchingView { surface.northEastPoint(), surface.northWestPoint()]; this.createGeometry(); - this.updateColor(markedColor || this.color); + this.updateVisuals(); } traverse(visitor) { @@ -84,27 +89,6 @@ export class OpenFaceView extends SketchingView { this.updateBounds(); } - mark(color) { - this.updateColor(color || SELECTION_COLOR); - } - - withdraw(color) { - this.updateColor(null); - } - - updateColor(color) { - setFacesColor(this.mesh.geometry.faces, color||this.color); - this.mesh.geometry.colorsNeedUpdate = true; - } - - get markedColor() { - let face = this.mesh && this.mesh.geometry && this.mesh.geometry.faces[0]; - if (face) { - return face.color === NULL_COLOR ? null : face.color; - } - return null; - } - dispose() { this.dropGeometry(); this.material.dispose(); diff --git a/web/app/cad/scene/views/shellView.js b/web/app/cad/scene/views/shellView.js index 760a23f7..61b4eba9 100644 --- a/web/app/cad/scene/views/shellView.js +++ b/web/app/cad/scene/views/shellView.js @@ -1,19 +1,18 @@ import {View} from './view'; import * as SceneGraph from 'scene/sceneGraph'; import {getAttribute, setAttribute} from 'scene/objectData'; -import {createSolidMaterial} from '../wrappers/sceneObject'; import {FaceView, SELECTION_COLOR} from './faceView'; import {EdgeView} from './edgeView'; -import {FACE, SHELL} from '../../model/entities'; +import {FACE, LOOP, SHELL} from '../../model/entities'; import {Mesh} from 'three'; import {VertexView} from "./vertexView"; +import {MSketchLoop} from "cad/model/mloop"; export class ShellView extends View { - constructor(shell, skin, viewer) { - super(shell); + constructor(ctx, shell, skin) { + super(ctx, shell); - this.material = createSolidMaterial(skin); this.rootGroup = SceneGraph.createGroup(); this.edgeGroup = SceneGraph.createGroup(); this.vertexGroup = SceneGraph.createGroup(); @@ -27,29 +26,20 @@ export class ShellView extends View { setAttribute(this.rootGroup, SHELL, this); setAttribute(this.rootGroup, View.MARKER, this); - const geometry = new THREE.Geometry(); - geometry.dynamic = true; - this.mesh = new SketchMesh(geometry, this.material); - // this.mesh.visible = false; - this.rootGroup.add(this.mesh); - - - const geom = this.mesh.geometry; for (let face of shell.faces) { - const faceView = new FaceView(face, geom); + const faceView = new FaceView(ctx, face, this, skin); this.faceViews.push(faceView); this.rootGroup.add(faceView.rootGroup); } - geom.mergeVertices(); for (let edge of shell.edges) { - const edgeView = new EdgeView(edge); + const edgeView = new EdgeView(ctx, edge); SceneGraph.addToGroup(this.edgeGroup, edgeView.rootGroup); this.edgeViews.push(edgeView); } for (let vertex of shell.vertices) { - const vertexView = new VertexView(vertex, viewer); + const vertexView = new VertexView(ctx, vertex); SceneGraph.addToGroup(this.vertexGroup, vertexView.rootGroup); this.vertexViews.push(vertexView); } @@ -58,29 +48,24 @@ export class ShellView extends View { this.model.location$.attach(loc => { loc.setToMatrix4x4(this.rootGroup.matrix); this.rootGroup.matrixWorldNeedsUpdate = true; - viewer.requestRender(); + ctx.viewer.requestRender(); }); } - mark(color) { - this.faceViews.forEach(faceView => faceView.setColor(color || SELECTION_COLOR)); - } - - withdraw(color) { - this.faceViews.forEach(faceView => faceView.setColor(null)); - } - - traverse(visitor) { - super.traverse(visitor); + traverse(visitor, includeSelf = true) { + super.traverse(visitor, includeSelf); this.faceViews.forEach(f => f.traverse(visitor)); this.edgeViews.forEach(e => e.traverse(visitor)); this.vertexViews.forEach(e => e.traverse(visitor)); } + updateVisuals(color) { + super.updateVisuals(color); + this.faceViews.forEach(f => f.updateVisuals(color)); + } + dispose() { - this.mesh.material.dispose(); - this.mesh.geometry.dispose(); for (let faceView of this.faceViews) { faceView.dispose(); } @@ -100,22 +85,4 @@ export class SketchMesh extends Mesh { super(geometry, material); } - passRayCast(hits) { - for (let hit of hits) { - if (hit.object === this && hit.face) { - let faceView = getAttribute(hit.face, FACE); - if (faceView) { - if (faceView.sketchLoopViews.find(v => hits.find(h => v.mesh.geometry.faces.indexOf(h.face) !== -1))) { - return true; - } - } - - } - } - } - - passMouseEvent(e) { - return this.passRayCast(e.hits); - }; - } \ No newline at end of file diff --git a/web/app/cad/scene/views/sketchLoopView.js b/web/app/cad/scene/views/sketchLoopView.js index 18c1d4ee..be96e15a 100644 --- a/web/app/cad/scene/views/sketchLoopView.js +++ b/web/app/cad/scene/views/sketchLoopView.js @@ -9,21 +9,40 @@ import Vector from 'math/vector'; import {LOOP} from '../../model/entities'; import {setAttribute} from 'scene/objectData'; -export class SketchLoopView extends MarkTracker(View) { - constructor(mLoop) { - super(mLoop); +const HIGHLIGHT_COLOR = 0xDBFFD9; +const SELECT_COLOR = 0xCCEFCA; + +const MarkerTable = [ + { + type: 'selection', + priority: 10, + colors: [SELECT_COLOR], + }, + { + type: 'highlight', + priority: 1, + colors: [HIGHLIGHT_COLOR], + }, +]; + + +export class SketchLoopView extends View { + + constructor(ctx, mLoop) { + super(ctx, mLoop, MarkerTable); this.rootGroup = SceneGraph.createGroup(); const geometry = new Geometry(); geometry.dynamic = true; this.mesh = new Mesh(geometry, createSolidMaterial({ - color: SKETCH_LOOP_DEFAULT_HIGHLIGHT_COLOR, + // color: HIGHLIGHT_COLOR, side: DoubleSide, // transparent: true, - depthTest: true, - depthWrite: false, + // depthTest: true, + // depthWrite: false, polygonOffset: true, - polygonOffsetFactor: -4, + polygonOffsetFactor: -1.0, // should less than offset of loop lines + polygonOffsetUnits: -1.0, visible: false })); let surface = mLoop.face.surface; @@ -45,27 +64,23 @@ export class SketchLoopView extends MarkTracker(View) { } this.rootGroup.add(this.mesh); - this.mesh.onMouseEnter = (e) => { - this.mark(SKETCH_LOOP_DEFAULT_HIGHLIGHT_COLOR, 5); - e.viewer.requestRender(); - }; - this.mesh.onMouseLeave = (e) => { - this.withdraw(5); - e.viewer.requestRender(); - }; + this.mesh.onMouseEnter = () => { + this.ctx.highlightService.highlight(this.model.id); + } + this.mesh.onMouseLeave = () => { + this.ctx.highlightService.unHighlight(this.model.id); + } + this.mesh.raycastPriority = 10; } - mark(color = SKETCH_LOOP_DEFAULT_SELECT_COLOR, priority = 10) { - super.mark(color, priority); - } - - markImpl(color) { - this.mesh.material.visible = true; - this.mesh.material.color.setHex(color) - } - - withdrawImpl() { - this.mesh.material.visible = false; + updateVisuals() { + const markColor = this.markColor; + if (!markColor) { + this.mesh.material.visible = false; + } else { + this.mesh.material.color.set(markColor); + this.mesh.material.visible = true; + } } dispose() { @@ -74,6 +89,3 @@ export class SketchLoopView extends MarkTracker(View) { super.dispose(); } } - -const SKETCH_LOOP_DEFAULT_HIGHLIGHT_COLOR = 0xDBFFD9; -const SKETCH_LOOP_DEFAULT_SELECT_COLOR = 0xCCEFCA; \ No newline at end of file diff --git a/web/app/cad/scene/views/sketchObjectView.js b/web/app/cad/scene/views/sketchObjectView.js index e8c81b5e..807c942d 100644 --- a/web/app/cad/scene/views/sketchObjectView.js +++ b/web/app/cad/scene/views/sketchObjectView.js @@ -2,9 +2,9 @@ import {CurveBasedView} from './curveBasedView'; export class SketchObjectView extends CurveBasedView { - constructor(mSketchObject, sketchToWorldTransformation) { + constructor(ctx, mSketchObject, sketchToWorldTransformation) { const color = mSketchObject.construction ? 0x777777 : 0x0000FF; const tess = mSketchObject.sketchPrimitive.tessellate(10).map(sketchToWorldTransformation.apply).map(v => v.data()); - super(mSketchObject, tess, 3, 4, color, 0x49FFA5); + super(ctx, mSketchObject, tess, 3, 4, color, 0x49FFA5, true); } } diff --git a/web/app/cad/scene/views/vertexView.js b/web/app/cad/scene/views/vertexView.js index a8fc8ff2..be05241f 100644 --- a/web/app/cad/scene/views/vertexView.js +++ b/web/app/cad/scene/views/vertexView.js @@ -5,9 +5,9 @@ import {ConstantScaleGroup} from "scene/scaleHelper"; export class VertexView extends View { - constructor(vertex, viewer) { - super(vertex); - this.rootGroup = new VertexObject(viewer, 50, 100, () => this.rootGroup.position); + constructor(ctx, vertex) { + super(ctx, vertex); + this.rootGroup = new VertexObject(ctx.viewer, 50, 100, () => this.rootGroup.position); this.rootGroup.position.x = vertex.brepVertex.point.x; this.rootGroup.position.y = vertex.brepVertex.point.y; diff --git a/web/app/cad/scene/views/view.js b/web/app/cad/scene/views/view.js index 91b8b12a..349ac636 100644 --- a/web/app/cad/scene/views/view.js +++ b/web/app/cad/scene/views/view.js @@ -1,37 +1,73 @@ import {createFunctionList} from "gems/func"; -import {Color} from "three"; +import {createIndex} from "gems/indexed"; + +const MarkerTable = [ + { + type: 'selection', + priority: 10, + colors: [0xffff80], + }, + { + type: 'highlight', + priority: 1, + colors: [0xffebcd, 0xffdf00], + }, +]; + export class View { static MARKER = 'ModelView'; disposers = createFunctionList(); - color = new Color(); - constructor(model) { + constructor(ctx, model, parent, markerTable = MarkerTable) { + this.ctx = ctx; this.model = model; + this.parent = parent; model.ext.view = this; + this.marks = []; + this.markerTable = createIndex(markerTable, i => i.type); + } + + setColor(color) { + } + + get markColor() { + if (this.marks.length !== 0) { + const baseMark = this.marks[0]; + return baseMark.colors[Math.min(baseMark.colors.length, this.marks.length) - 1]; + } else { + return null; + } } setVisible(value) { } - mark(color, priority) { - } - - withdraw(priority) { - } - - setColor(color) { - if (!color) { - this.color = new Color(); - } else { - this.color.setStyle(color); + mark(type = 'selection') { + const marker = this.markerTable[type]; + const found = this.marks.find(c => c.type === marker.type); + if (found) { + return; } + this.marks.push(marker); + this.marks.sort((c1, c2) => c1.priority - c2.priority); + this.updateVisuals(); } - traverse(visitor) { - visitor(this); + withdraw(type) { + this.marks = this.marks.filter(c => c.type !== type) + this.updateVisuals(); + } + + updateVisuals() { + } + + traverse(visitor, includeSelf = true) { + if (includeSelf) { + visitor(this); + } } addDisposer(disposer) { @@ -48,8 +84,8 @@ export class View { export const MarkTracker = ViewClass => class extends ViewClass { - constructor(model) { - super(model); + constructor(ctx, model) { + super(ctx, model); this.marks = new Map(); } diff --git a/web/app/cad/workbench/menuConfig.js b/web/app/cad/workbench/menuConfig.js index ed426309..fa69134b 100644 --- a/web/app/cad/workbench/menuConfig.js +++ b/web/app/cad/workbench/menuConfig.js @@ -57,5 +57,11 @@ export default [ // actions: ['DATUM_MOVE', 'DATUM_ROTATE', 'DATUM_REBASE', '-', 'PLANE_FROM_DATUM', 'BOX', 'SPHERE', 'TORUS', // 'CONE', 'CYLINDER'] }, - + { + id: 'contextual', + label: 'contextual', + cssIcons: ['magic'], + info: 'contextual actions', + actions: ['ModelDisplayOptions', 'ModelAttributesEditor'] + } ];