objects highlight

This commit is contained in:
Val Erastov 2022-03-16 01:54:51 -07:00
parent ed5b40878d
commit 3dea461392
43 changed files with 657 additions and 354 deletions

8
modules/gems/indexed.ts Normal file
View file

@ -0,0 +1,8 @@
import {Index} from "gems/indexType";
export function createIndex<T>(arr: T[], indexProp: (item) => string): Index<T> {
return arr.reduce((index, item) => {
index[indexProp(item)] = item;
return index;
}, {})
}

View file

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

View file

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

View file

@ -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 <>
<div className={ls.objectItem}>
<div className={ls.objectItem} onMouseEnter={props.onMouseEnter} onMouseLeave={props.onMouseLeave}>
<span className={expandable ? ls.expandHandle : ls.expandHandleInactive} onClick={toggle}>
{expandable ? (expanded ? <AiFillCaretDown/> : <AiFillCaretRight/>)
: <BsDot/>}

View file

@ -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 <Window initWidth={250}
initLeft={left || 15}
initTop={top}
title={(title||'').toUpperCase()}
className={cx('mid-typography', className)}
controlButtons={<>

View file

@ -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 <div className={cx(ls.item, disabled && ls.disabled)}
onMouseDown={e => e.stopPropagation()} style={style} onClick={clickHandler} {...props}>

View file

@ -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<HTMLDivElement>;
export default function Stack({className, ...props}: StackProps) {
return <div className={cx(ls.root, className)} {...props} />
}

View file

@ -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 <button onClick={onClick} className={cx(type, className)} {...props} />
return <button onClick={onClick} className={cx(type, compact&&'compact', className)} {...props} />
}

View file

@ -1,11 +1,12 @@
import React from 'react';
import React, {useContext} from 'react';
import {FieldId} from "ui/components/controls/Field";
export default class CheckboxControl extends React.Component {
export default function CheckboxControl(props) {
let {onChange, value} = props;
const fieldId = useContext(FieldId);
return <input id={fieldId}
type='checkbox'
checked={value}
onChange={e => onChange(e.target.checked)}/>
render() {
let {onChange, value} = this.props;
return <input type='checkbox'
checked={value}
onChange={e => onChange(e.target.checked)} />
}
}

View file

@ -24,13 +24,14 @@ export function ColorControl(props: ColorControlProps) {
return <React.Fragment>
<ButtonGroup>
<span style={{
display: 'inline-block',
alignItems: 'center',
display: 'inline-flex',
padding: '0 3px',
fontFamily: 'monospace',
...style
}}>{value||<NoValue/>}</span>
<Button onClick={() => onChange(null)}><RiDeleteBack2Line /></Button>
<Button onClick={() => setOpen(true)}><CgColorPicker /></Button>
<Button compact onClick={() => onChange(null)}><RiDeleteBack2Line /></Button>
<Button compact onClick={() => setOpen(true)}><CgColorPicker /></Button>
</ButtonGroup>
{open && <ColorDialog value={value||'white'} onChange={onChange} onClose={() => setOpen(false)} title={props.dialogTitle}/>}
</React.Fragment>

View file

@ -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 <div className={cx(ls.root, active&&ls.active)} data-field-name={name} {...props} />
const fieldId = useMemo(() => 'Field_' + (ID_GENERATOR++), []);
return <FieldId.Provider value={fieldId}>
<div className={cx(ls.root, active&&ls.active)} data-field-name={name} {...props} />
</FieldId.Provider>;
}

View file

@ -3,7 +3,8 @@
.root {
display: flex;
justify-content: space-between;
align-items: baseline;
align-items: center;
}
.active {

View file

@ -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 <span>{children}</span>
const fieldId = useContext(FieldId);
return <label htmlFor={fieldId}>{children}</label>
}

View file

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

View file

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

View file

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

View file

@ -155,10 +155,12 @@ export interface ActionService {
hint$: StateStream<Hint>;
}
declare module 'context' {
interface CoreContext {
actionService: ActionService;
}
export interface ActionSystemPlugin {
actionService: ActionService;
}
declare module 'context' {
interface CoreContext extends ActionSystemPlugin {}
}

View file

@ -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<AttributesPluginInputContext, AttributesPluginContext, AttributesPluginWorkingContext> = {
inputContextSpec: {
actionService: 'required',
},
outputContextSpec: {
@ -25,6 +32,31 @@ export const AttributesPlugin: Plugin<AttributesPluginInputContext, AttributesPl
activate(ctx: AttributesPluginWorkingContext) {
ctx.attributesService = new AttributesService();
contributeComponent(DisplayOptionsDialogManager);
ctx.actionService.registerActions([
{
id: 'ModelDisplayOptions',
appearance: {
icon: IoColorPalette,
label: 'display options...',
info: 'open a dialog with display options for model(s)',
},
invoke: (ctx: ApplicationContext, e) => 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,
},
])
},
}

View file

@ -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<EditorSet>({});
attributesEditors$ = state<EditorSet>({});
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[]
}
}

View file

@ -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 <React.Fragment>
{Object.keys(editors).map(key => {
const request = editors[key];
return <GenericWizard key={key} title='display options'
onCancel={() => close(key)}
onOK={() => close(key)}
onClose={() => close(key)}
topicId='entity-display-options'
left={request.x}
top={request.y}>
<DisplayOptionsView modelIds={request.models} />
</GenericWizard>
})}
</React.Fragment>;
}
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 <Stack className='bg-color-4'>
<Field active={false} name='label' onFocus={DO_NOTHING} onClick={DO_NOTHING}>
<Label>Visible</Label>
<CheckboxControl value={!attrs.hidden} onChange={val => patchAttrs(attrs => attrs.hidden = !val)}/>
</Field>
<Field active={false} name='label' onFocus={DO_NOTHING} onClick={DO_NOTHING}>
<Label>Color</Label>
<ColorControl
dialogTitle={`Color for `}
value={attrs.color} onChange={val => patchAttrs(attrs => attrs.color = val)}/>
</Field>
</Stack>;
}

View file

@ -23,7 +23,7 @@ export function SceneInlineObjectExplorer() {
return <SceneInlineSection title='OBJECTS'> {models.map(m => {
if (m instanceof MOpenFaceShell) {
return <OpenFaceSection shell={m} />
return <OpenFaceSection shell={m} key={m.id} />
} else if (m instanceof MShell) {
return <ModelSection type='shell' model={m} key={m.id} controlVisibility>
<Section label='faces' defaultOpen={true}>
@ -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 && <VisibleSwitch modelId={visibilityOf.id}/>}

View file

@ -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<T> {
index = new Map<string, StateStream<T>>();
proto: (id: string) => T;
constructor(proto?: (id: string) => T) {
this.proto = proto || ((id: string) => null);
}
get(id: string) {
let state$: StateStream<T> = this.index[id];
if (state$ == null) {
state$ = state<T>(this.proto(id));
this.index[id] = state$;
}
return state$;
}
}
export interface ModelAttributes {
hidden: boolean
}
const modelAttrStreams = new LazyStreams<ModelAttributes>(id => ({} as ModelAttributes));

View file

@ -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 <Field active={active} name={name} onClick={setActive}>
<Label>{label||camelCaseSplitToStr(name)}:</Label>
<div>{value.length === 0 ?
<span className={ls.emptySelection}>{placeholder || '<not selected>'}</span> :
value.map((entity, i) => <span className={ls.entityRef} key={i}
onMouseEnter={() => onEntityEnter&&onEntityEnter(entity)}
onMouseLeave={() => onEntityLeave&&onEntityLeave(entity)}>
{entityRenderer(entity)}
{!readOnly && <span className={ls.rm} onClick={() => this.deselect(entity)}> <Fa icon='times'/></span>}
</span>)}
</div>
</Field>;
if (!Array.isArray(value)) {
value = value ? asArray(value) : EMPTY_ARRAY;
}
return <Field active={active} name={name} onClick={setActive}>
<Label>{label||camelCaseSplitToStr(name)}:</Label>
<div>{value.length === 0 ?
<span className={ls.emptySelection}>{placeholder || '<not selected>'}</span> :
value.map((entity, i) => <span className={ls.entityRef} key={i}
onMouseEnter={() => ctx.highlightService.highlight(entity)}
onMouseLeave={() => ctx.highlightService.unHighlight(entity)}>
{entityRenderer(entity)}
{!readOnly && <span className={ls.rm} onClick={() => deselect(entity)}> <Fa icon='times'/></span>}
</span>)}
</div>
</Field>;
}
export default attachToForm(EntityList);
function asArray(val) {
_arr[0] = val;
return _arr;

View file

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

View file

@ -23,29 +23,34 @@ function ActionMenu({actions, keymap, ...menuState}) {
</Menu>;
}
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 = <Filler width={size} height='1.18em'/>;
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 = <Icon />;
} else {
if (icon32 || icon96) {
let size = 16;
renderedIcon = <Filler width={size} height='1.18em'/>;
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 = <Fa fw fa={cssIcons} />;
}
} else if (cssIcons) {
icon = <Fa fw fa={cssIcons} />;
}
return <MenuItem {...{label, icon, style, disabled: !enabled, hotKey, ...props}} />;
return <MenuItem icon={renderedIcon} {...{label, style, disabled: !enabled, hotKey, ...props}} />;
}
const ConnectedActionMenu = connect((streams, props) =>

View file

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

View file

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

View file

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

View file

@ -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<string[]>[]))
.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<MObject[]>
};
}

View file

@ -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<string>();
unHighlightEvents = stream<string>();
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<HighlightPluginInputContext, HighlightPluginContext, HighlightPluginWorkingContext> = {
inputContextSpec: {
viewer: 'required',
},
outputContextSpec: {
highlightService: 'required',
},
activate(ctx: HighlightPluginWorkingContext) {
ctx.highlightService = new HighlightService(ctx.viewer);
},
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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);
}
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);
}
mark(color) {
this.updateColor(color || SELECTION_COLOR);
}
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;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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