mirror of
https://github.com/xibyte/jsketcher
synced 2025-12-07 17:04:58 +01:00
objects highlight
This commit is contained in:
parent
ed5b40878d
commit
3dea461392
43 changed files with 657 additions and 354 deletions
8
modules/gems/indexed.ts
Normal file
8
modules/gems/indexed.ts
Normal 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;
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
|
@ -10,8 +10,8 @@ import {ORIGIN} from "math/vector";
|
||||||
|
|
||||||
export default class ScalableLine extends Mesh {
|
export default class ScalableLine extends Mesh {
|
||||||
|
|
||||||
constructor(tessellation, width, color, opacity, smooth, ambient) {
|
constructor(tessellation, width, color, opacity, smooth, ambient, offset) {
|
||||||
super(createGeometry(tessellation, smooth), createMaterial(color, opacity, ambient));
|
super(createGeometry(tessellation, smooth), createMaterial(color, opacity, ambient, offset));
|
||||||
this.width = width;
|
this.width = width;
|
||||||
this.morphTargetInfluences = [0];
|
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 = {
|
let materialParams = {
|
||||||
vertexColors: FaceColors,
|
vertexColors: FaceColors,
|
||||||
morphTargets: true,
|
morphTargets: true,
|
||||||
color,
|
color,
|
||||||
};
|
};
|
||||||
|
if (offset) {
|
||||||
|
Object.assign(materialParams, {
|
||||||
|
polygonOffset: true,
|
||||||
|
polygonOffsetFactor: -2.0,
|
||||||
|
polygonOffsetUnits: -1.0,
|
||||||
|
});
|
||||||
|
}
|
||||||
if (!ambient) {
|
if (!ambient) {
|
||||||
materialParams.shininess = 0;
|
materialParams.shininess = 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -217,7 +217,31 @@ export default class SceneSetUp {
|
||||||
if (logInfoOut !== null) {
|
if (logInfoOut !== null) {
|
||||||
logInfoOut.ray = raycaster.ray
|
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) {
|
customRaycast(from3, to3, objects) {
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@ interface GenericExplorerNodeProps {
|
||||||
defaultExpanded?: boolean;
|
defaultExpanded?: boolean;
|
||||||
expandable: boolean;
|
expandable: boolean;
|
||||||
select: any;
|
select: any;
|
||||||
|
onMouseEnter?: any;
|
||||||
|
onMouseLeave?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GenericExplorerNode(props: GenericExplorerNodeProps) {
|
export function GenericExplorerNode(props: GenericExplorerNodeProps) {
|
||||||
|
|
@ -36,7 +38,7 @@ export function GenericExplorerNode(props: GenericExplorerNodeProps) {
|
||||||
const toggle = expandable ? (() => setExpanded(expanded => !expanded)) : undefined;
|
const toggle = expandable ? (() => setExpanded(expanded => !expanded)) : undefined;
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<div className={ls.objectItem}>
|
<div className={ls.objectItem} onMouseEnter={props.onMouseEnter} onMouseLeave={props.onMouseLeave}>
|
||||||
<span className={expandable ? ls.expandHandle : ls.expandHandleInactive} onClick={toggle}>
|
<span className={expandable ? ls.expandHandle : ls.expandHandleInactive} onClick={toggle}>
|
||||||
{expandable ? (expanded ? <AiFillCaretDown/> : <AiFillCaretRight/>)
|
{expandable ? (expanded ? <AiFillCaretDown/> : <AiFillCaretRight/>)
|
||||||
: <BsDot/>}
|
: <BsDot/>}
|
||||||
|
|
|
||||||
|
|
@ -7,18 +7,20 @@ import Stack from "ui/components/Stack";
|
||||||
import ButtonGroup from "ui/components/controls/ButtonGroup";
|
import ButtonGroup from "ui/components/controls/ButtonGroup";
|
||||||
import Button from "ui/components/controls/Button";
|
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,
|
topicId: string,
|
||||||
title: string,
|
title: string,
|
||||||
left?: number,
|
left?: number,
|
||||||
|
top?: number,
|
||||||
onCancel: () => any,
|
onCancel: () => any,
|
||||||
onOK: () => any,
|
onOK: () => any,
|
||||||
onKeyDown?: (e) => any,
|
onKeyDown?: (e) => any,
|
||||||
infoText: any
|
infoText?: any
|
||||||
} & WindowProps ) {
|
} & WindowProps ) {
|
||||||
|
|
||||||
return <Window initWidth={250}
|
return <Window initWidth={250}
|
||||||
initLeft={left || 15}
|
initLeft={left || 15}
|
||||||
|
initTop={top}
|
||||||
title={(title||'').toUpperCase()}
|
title={(title||'').toUpperCase()}
|
||||||
className={cx('mid-typography', className)}
|
className={cx('mid-typography', className)}
|
||||||
controlButtons={<>
|
controlButtons={<>
|
||||||
|
|
|
||||||
|
|
@ -37,9 +37,9 @@ export function MenuItem({icon, label, hotKey, style, disabled, onClick, childre
|
||||||
hotKey = null;
|
hotKey = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let clickHandler = disabled ? undefined : () => {
|
let clickHandler = disabled ? undefined : (e) => {
|
||||||
closeAllUpPopups();
|
closeAllUpPopups();
|
||||||
onClick();
|
onClick(e);
|
||||||
};
|
};
|
||||||
return <div className={cx(ls.item, disabled && ls.disabled)}
|
return <div className={cx(ls.item, disabled && ls.disabled)}
|
||||||
onMouseDown={e => e.stopPropagation()} style={style} onClick={clickHandler} {...props}>
|
onMouseDown={e => e.stopPropagation()} style={style} onClick={clickHandler} {...props}>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@ import React from 'react';
|
||||||
import ls from './Stack.less'
|
import ls from './Stack.less'
|
||||||
import cx from "classnames";
|
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} />
|
return <div className={cx(ls.root, className)} {...props} />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2,16 +2,17 @@ import React from 'react';
|
||||||
|
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
|
|
||||||
export default function Button({type, onClick, className, ...props}: {
|
export default function Button({type, onClick, className, compact, ...props}: {
|
||||||
type?: string,
|
type?: string,
|
||||||
onClick: () => void,
|
onClick: () => void,
|
||||||
className? : string,
|
className? : string,
|
||||||
children?: any,
|
children?: any,
|
||||||
|
compact?: boolean
|
||||||
} & JSX.IntrinsicAttributes) {
|
} & JSX.IntrinsicAttributes) {
|
||||||
|
|
||||||
type = type || 'neutral';
|
type = type || 'neutral';
|
||||||
|
|
||||||
return <button onClick={onClick} className={cx(type, className)} {...props} />
|
return <button onClick={onClick} className={cx(type, compact&&'compact', className)} {...props} />
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
render() {
|
const fieldId = useContext(FieldId);
|
||||||
let {onChange, value} = this.props;
|
return <input id={fieldId}
|
||||||
return <input type='checkbox'
|
type='checkbox'
|
||||||
checked={value}
|
checked={value}
|
||||||
onChange={e => onChange(e.target.checked)} />
|
onChange={e => onChange(e.target.checked)}/>
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,13 +24,14 @@ export function ColorControl(props: ColorControlProps) {
|
||||||
return <React.Fragment>
|
return <React.Fragment>
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
<span style={{
|
<span style={{
|
||||||
display: 'inline-block',
|
alignItems: 'center',
|
||||||
|
display: 'inline-flex',
|
||||||
padding: '0 3px',
|
padding: '0 3px',
|
||||||
fontFamily: 'monospace',
|
fontFamily: 'monospace',
|
||||||
...style
|
...style
|
||||||
}}>{value||<NoValue/>}</span>
|
}}>{value||<NoValue/>}</span>
|
||||||
<Button onClick={() => onChange(null)}><RiDeleteBack2Line /></Button>
|
<Button compact onClick={() => onChange(null)}><RiDeleteBack2Line /></Button>
|
||||||
<Button onClick={() => setOpen(true)}><CgColorPicker /></Button>
|
<Button compact onClick={() => setOpen(true)}><CgColorPicker /></Button>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
{open && <ColorDialog value={value||'white'} onChange={onChange} onClose={() => setOpen(false)} title={props.dialogTitle}/>}
|
{open && <ColorDialog value={value||'white'} onChange={onChange} onClose={() => setOpen(false)} title={props.dialogTitle}/>}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,17 @@
|
||||||
import React from 'react';
|
import React, {useMemo} from 'react';
|
||||||
|
|
||||||
import ls from './Field.less'
|
import ls from './Field.less'
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
|
|
||||||
|
export const FieldId = React.createContext(-1);
|
||||||
|
|
||||||
|
let ID_GENERATOR = 0;
|
||||||
|
|
||||||
export default function Field({active, name, ...props}) {
|
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>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,8 @@
|
||||||
.root {
|
.root {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: baseline;
|
align-items: center;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.active {
|
.active {
|
||||||
|
|
|
||||||
|
|
@ -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}) {
|
export default function Label({children}) {
|
||||||
return <span>{children}</span>
|
const fieldId = useContext(FieldId);
|
||||||
|
return <label htmlFor={fieldId}>{children}</label>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,6 @@
|
||||||
"marked": "^1.0.0",
|
"marked": "^1.0.0",
|
||||||
"mousetrap": "1.6.1",
|
"mousetrap": "1.6.1",
|
||||||
"numeric": "1.2.6",
|
"numeric": "1.2.6",
|
||||||
"opencascade.js": "^1.1.1",
|
|
||||||
"prop-types": "15.6.0",
|
"prop-types": "15.6.0",
|
||||||
"react": "^16.13.1",
|
"react": "^16.13.1",
|
||||||
"react-color": "^2.19.3",
|
"react-color": "^2.19.3",
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,10 @@ export function ActionButtonBehavior({children, actionId}) {
|
||||||
|
|
||||||
return children({
|
return children({
|
||||||
'data-action-id': actionId,
|
'data-action-id': actionId,
|
||||||
onClick: e => actionService.run(actionId, e),
|
onClick: e => {
|
||||||
|
canceled = true;
|
||||||
|
actionService.run(actionId, e);
|
||||||
|
},
|
||||||
onMouseEnter: e => {
|
onMouseEnter: e => {
|
||||||
updateCoords(e);
|
updateCoords(e);
|
||||||
canceled = false;
|
canceled = false;
|
||||||
|
|
|
||||||
|
|
@ -29,3 +29,13 @@ export function requiresSolidSelection(amount) {
|
||||||
update: checkForSelectedSolids(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';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -155,10 +155,12 @@ export interface ActionService {
|
||||||
hint$: StateStream<Hint>;
|
hint$: StateStream<Hint>;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module 'context' {
|
export interface ActionSystemPlugin {
|
||||||
interface CoreContext {
|
|
||||||
|
|
||||||
actionService: ActionService;
|
actionService: ActionService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
declare module 'context' {
|
||||||
|
interface CoreContext extends ActionSystemPlugin {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,14 @@
|
||||||
import {Plugin} from "plugable/pluginSystem";
|
import {Plugin} from "plugable/pluginSystem";
|
||||||
import {AttributesService} from "cad/attributes/attributesService";
|
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 {
|
export interface AttributesPluginContext {
|
||||||
attributesService: AttributesService;
|
attributesService: AttributesService;
|
||||||
|
|
@ -17,6 +23,7 @@ declare module 'context' {
|
||||||
export const AttributesPlugin: Plugin<AttributesPluginInputContext, AttributesPluginContext, AttributesPluginWorkingContext> = {
|
export const AttributesPlugin: Plugin<AttributesPluginInputContext, AttributesPluginContext, AttributesPluginWorkingContext> = {
|
||||||
|
|
||||||
inputContextSpec: {
|
inputContextSpec: {
|
||||||
|
actionService: 'required',
|
||||||
},
|
},
|
||||||
|
|
||||||
outputContextSpec: {
|
outputContextSpec: {
|
||||||
|
|
@ -25,6 +32,31 @@ export const AttributesPlugin: Plugin<AttributesPluginInputContext, AttributesPl
|
||||||
|
|
||||||
activate(ctx: AttributesPluginWorkingContext) {
|
activate(ctx: AttributesPluginWorkingContext) {
|
||||||
ctx.attributesService = new AttributesService();
|
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,
|
||||||
|
},
|
||||||
|
])
|
||||||
},
|
},
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,11 @@
|
||||||
import {LazyStreams} from "lstream/lazyStreams";
|
import {LazyStreams} from "lstream/lazyStreams";
|
||||||
|
import {state} from "lstream";
|
||||||
|
|
||||||
|
export interface ModelAttributes {
|
||||||
|
hidden: boolean;
|
||||||
|
label: string;
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class AttributesService {
|
export class AttributesService {
|
||||||
|
|
||||||
|
|
@ -8,12 +15,28 @@ export class AttributesService {
|
||||||
color: null
|
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 {
|
export type EditorSet = {
|
||||||
hidden: boolean;
|
[key: string]: {
|
||||||
label: string;
|
x: number,
|
||||||
color: string;
|
y: number,
|
||||||
|
models: string[]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
76
web/app/cad/attributes/ui/DisplayOptionsDialog.tsx
Normal file
76
web/app/cad/attributes/ui/DisplayOptionsDialog.tsx
Normal 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>;
|
||||||
|
}
|
||||||
|
|
@ -23,7 +23,7 @@ export function SceneInlineObjectExplorer() {
|
||||||
|
|
||||||
return <SceneInlineSection title='OBJECTS'> {models.map(m => {
|
return <SceneInlineSection title='OBJECTS'> {models.map(m => {
|
||||||
if (m instanceof MOpenFaceShell) {
|
if (m instanceof MOpenFaceShell) {
|
||||||
return <OpenFaceSection shell={m} />
|
return <OpenFaceSection shell={m} key={m.id} />
|
||||||
} else if (m instanceof MShell) {
|
} else if (m instanceof MShell) {
|
||||||
return <ModelSection type='shell' model={m} key={m.id} controlVisibility>
|
return <ModelSection type='shell' model={m} key={m.id} controlVisibility>
|
||||||
<Section label='faces' defaultOpen={true}>
|
<Section label='faces' defaultOpen={true}>
|
||||||
|
|
@ -101,6 +101,8 @@ function ModelSection({model, type, typeLabel, expandable = true, controlVisibil
|
||||||
label={label}
|
label={label}
|
||||||
selected={selected}
|
selected={selected}
|
||||||
select={select}
|
select={select}
|
||||||
|
onMouseEnter={() => ctx.highlightService.highlight(model.id)}
|
||||||
|
onMouseLeave={() => ctx.highlightService.unHighlight(model.id)}
|
||||||
controls={
|
controls={
|
||||||
<>
|
<>
|
||||||
{controlVisibility && <VisibleSwitch modelId={visibilityOf.id}/>}
|
{controlVisibility && <VisibleSwitch modelId={visibilityOf.id}/>}
|
||||||
|
|
|
||||||
|
|
@ -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));
|
|
||||||
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React from 'react';
|
import React, {useContext} from 'react';
|
||||||
import ls from './EntityList.less';
|
import ls from './EntityList.less';
|
||||||
import Label from 'ui/components/controls/Label';
|
import Label from 'ui/components/controls/Label';
|
||||||
import Field from 'ui/components/controls/Field';
|
import Field from 'ui/components/controls/Field';
|
||||||
|
|
@ -6,12 +6,17 @@ import Fa from 'ui/components/Fa';
|
||||||
import {attachToForm} from './Form';
|
import {attachToForm} from './Form';
|
||||||
import {camelCaseSplitToStr} from 'gems/camelCaseSplit';
|
import {camelCaseSplitToStr} from 'gems/camelCaseSplit';
|
||||||
import {EMPTY_ARRAY, removeInPlace} from 'gems/iterables';
|
import {EMPTY_ARRAY, removeInPlace} from 'gems/iterables';
|
||||||
|
import {AppContext} from "cad/dom/components/AppContext";
|
||||||
|
|
||||||
@attachToForm
|
|
||||||
export default class EntityList extends React.Component {
|
|
||||||
|
|
||||||
deselect = (entityId) => {
|
function EntityList(props) {
|
||||||
let {value, onChange} = this.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)) {
|
if (Array.isArray(value)) {
|
||||||
onChange(removeInPlace(value, entityId));
|
onChange(removeInPlace(value, entityId));
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -19,8 +24,7 @@ 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)) {
|
if (!Array.isArray(value)) {
|
||||||
value = value ? asArray(value) : EMPTY_ARRAY;
|
value = value ? asArray(value) : EMPTY_ARRAY;
|
||||||
}
|
}
|
||||||
|
|
@ -29,16 +33,18 @@ export default class EntityList extends React.Component {
|
||||||
<div>{value.length === 0 ?
|
<div>{value.length === 0 ?
|
||||||
<span className={ls.emptySelection}>{placeholder || '<not selected>'}</span> :
|
<span className={ls.emptySelection}>{placeholder || '<not selected>'}</span> :
|
||||||
value.map((entity, i) => <span className={ls.entityRef} key={i}
|
value.map((entity, i) => <span className={ls.entityRef} key={i}
|
||||||
onMouseEnter={() => onEntityEnter&&onEntityEnter(entity)}
|
onMouseEnter={() => ctx.highlightService.highlight(entity)}
|
||||||
onMouseLeave={() => onEntityLeave&&onEntityLeave(entity)}>
|
onMouseLeave={() => ctx.highlightService.unHighlight(entity)}>
|
||||||
{entityRenderer(entity)}
|
{entityRenderer(entity)}
|
||||||
{!readOnly && <span className={ls.rm} onClick={() => this.deselect(entity)}> <Fa icon='times'/></span>}
|
{!readOnly && <span className={ls.rm} onClick={() => deselect(entity)}> <Fa icon='times'/></span>}
|
||||||
</span>)}
|
</span>)}
|
||||||
</div>
|
</div>
|
||||||
</Field>;
|
</Field>;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default attachToForm(EntityList);
|
||||||
|
|
||||||
function asArray(val) {
|
function asArray(val) {
|
||||||
_arr[0] = val;
|
_arr[0] = val;
|
||||||
return _arr;
|
return _arr;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {useStream} from 'ui/effects';
|
import {useStream} from 'ui/effects';
|
||||||
import {state} from 'lstream';
|
import {state} from 'lstream';
|
||||||
import {Scope} from "../../../sketcher/components/Scope";
|
import {Scope} from "sketcher/components/Scope";
|
||||||
|
|
||||||
const CONTRIBUTED_COMPONENTS$ = state([]);
|
const CONTRIBUTED_COMPONENTS$ = state([]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,14 +23,18 @@ function ActionMenu({actions, keymap, ...menuState}) {
|
||||||
</Menu>;
|
</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) {
|
if (!visible) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
let icon, style;
|
let renderedIcon, style;
|
||||||
|
if (icon) {
|
||||||
|
const Icon = icon;
|
||||||
|
renderedIcon = <Icon />;
|
||||||
|
} else {
|
||||||
if (icon32 || icon96) {
|
if (icon32 || icon96) {
|
||||||
let size = 16;
|
let size = 16;
|
||||||
icon = <Filler width={size} height='1.18em'/>;
|
renderedIcon = <Filler width={size} height='1.18em'/>;
|
||||||
style = {
|
style = {
|
||||||
backgroundImage: `url(${icon32 || icon96})`,
|
backgroundImage: `url(${icon32 || icon96})`,
|
||||||
backgroundRepeat: 'no-repeat',
|
backgroundRepeat: 'no-repeat',
|
||||||
|
|
@ -42,10 +46,11 @@ function ActionMenuItem({label, cssIcons, icon32, icon96, enabled, hotKey, visib
|
||||||
style.filter = 'grayscale(90%)';
|
style.filter = 'grayscale(90%)';
|
||||||
}
|
}
|
||||||
} else if (cssIcons) {
|
} else if (cssIcons) {
|
||||||
icon = <Fa fw fa={cssIcons} />;
|
renderedIcon = <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) =>
|
const ConnectedActionMenu = connect((streams, props) =>
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import * as SketcherPlugin from '../sketch/sketcherPlugin';
|
||||||
import * as SketcherStoragePlugin from '../sketch/sketchStoragePlugin';
|
import * as SketcherStoragePlugin from '../sketch/sketchStoragePlugin';
|
||||||
import * as ExportPlugin from '../exportPlugin';
|
import * as ExportPlugin from '../exportPlugin';
|
||||||
import * as ExposurePlugin from '../exposure/exposurePlugin';
|
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 EntityContextPlugin from '../scene/entityContextPlugin';
|
||||||
import * as OCCTPlugin from '../craft/e0/occtPlugin';
|
import * as OCCTPlugin from '../craft/e0/occtPlugin';
|
||||||
|
|
||||||
|
|
@ -41,6 +41,7 @@ import * as AssemblyPlugin from "../assembly/assemblyPlugin";
|
||||||
import {WorkbenchesLoaderPlugin} from "cad/workbench/workbenchesLoaderPlugin";
|
import {WorkbenchesLoaderPlugin} from "cad/workbench/workbenchesLoaderPlugin";
|
||||||
import {PluginSystem} from "plugable/pluginSystem";
|
import {PluginSystem} from "plugable/pluginSystem";
|
||||||
import {AttributesPlugin} from "cad/attributes/attributesPlugin";
|
import {AttributesPlugin} from "cad/attributes/attributesPlugin";
|
||||||
|
import {HighlightPlugin} from "cad/scene/highlightPlugin";
|
||||||
|
|
||||||
export default function startApplication(callback) {
|
export default function startApplication(callback) {
|
||||||
|
|
||||||
|
|
@ -85,7 +86,8 @@ export default function startApplication(callback) {
|
||||||
RemotePartsPlugin,
|
RemotePartsPlugin,
|
||||||
ViewSyncPlugin,
|
ViewSyncPlugin,
|
||||||
WizardSelectionPlugin,
|
WizardSelectionPlugin,
|
||||||
AttributesPlugin
|
AttributesPlugin,
|
||||||
|
HighlightPlugin
|
||||||
];
|
];
|
||||||
|
|
||||||
let allPlugins = [...preUIPlugins, ...plugins];
|
let allPlugins = [...preUIPlugins, ...plugins];
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import {printRaycastDebugInfo, RayCastDebugInfo} from "./rayCastDebug";
|
import {printRaycastDebugInfo, RayCastDebugInfo} from "./rayCastDebug";
|
||||||
import {LOG_FLAGS} from "../../logFlags";
|
import {LOG_FLAGS} from "../../logFlags";
|
||||||
|
|
||||||
export function activate(context) {
|
export function activate(ctx) {
|
||||||
const {services, streams} = context;
|
const {services, streams} = ctx;
|
||||||
const domElement = services.viewer.sceneSetup.domElement();
|
const domElement = services.viewer.sceneSetup.domElement();
|
||||||
const event = {
|
const event = {
|
||||||
viewer: services.viewer
|
viewer: services.viewer
|
||||||
|
|
@ -11,8 +11,22 @@ export function activate(context) {
|
||||||
domElement.addEventListener('mousedown', mousedown, false);
|
domElement.addEventListener('mousedown', mousedown, false);
|
||||||
domElement.addEventListener('mouseup', mouseup, false);
|
domElement.addEventListener('mouseup', mouseup, false);
|
||||||
domElement.addEventListener('mousemove', mousemove, 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 toDrag = null;
|
||||||
let pressed = new Set();
|
let pressed = new Set();
|
||||||
|
|
@ -102,35 +116,6 @@ export function activate(context) {
|
||||||
} else {
|
} else {
|
||||||
let hits = performRaycast(e);
|
let hits = performRaycast(e);
|
||||||
dispatchMousemove(e, hits)
|
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 => {
|
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);
|
el.onMouseLeave(event);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -167,7 +153,7 @@ export function activate(context) {
|
||||||
valid.clear();
|
valid.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
context.services.modelMouseEventSystem = {
|
ctx.services.modelMouseEventSystem = {
|
||||||
dispatchMousedown, dispatchMouseup, dispatchMousemove
|
dispatchMousedown, dispatchMouseup, dispatchMousemove
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
import * as mask from 'gems/mask'
|
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 {FACE, EDGE, SKETCH_OBJECT, DATUM, SHELL, DATUM_AXIS, LOOP} from '../../model/entities';
|
||||||
import {LOG_FLAGS} from '../../logFlags';
|
import {LOG_FLAGS} from 'cad/logFlags';
|
||||||
import * as vec from 'math/vec';
|
|
||||||
import {initRayCastDebug, printRaycastDebugInfo, RayCastDebugInfo} from "./rayCastDebug";
|
import {initRayCastDebug, printRaycastDebugInfo, RayCastDebugInfo} from "./rayCastDebug";
|
||||||
|
|
||||||
export interface PickControlService {
|
export interface PickControlService {
|
||||||
|
|
@ -157,7 +156,9 @@ export function activate(context) {
|
||||||
function handleSolidPick(e) {
|
function handleSolidPick(e) {
|
||||||
let pickResults = services.viewer.raycast(e, services.cadScene.workGroup.children);
|
let pickResults = services.viewer.raycast(e, services.cadScene.workGroup.children);
|
||||||
traversePickResults(e, pickResults, PICK_KIND.FACE, (sketchFace) => {
|
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;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
import {state, StateStream} from 'lstream';
|
import {combine, state, StateStream, Stream} from 'lstream';
|
||||||
|
|
||||||
import {addToListInMap} from 'gems/iterables';
|
import {addToListInMap, EMPTY_ARRAY} from 'gems/iterables';
|
||||||
import {EMPTY_ARRAY} from 'gems/iterables';
|
import {DATUM, EDGE, FACE, LOOP, SHELL, SKETCH_OBJECT} from '../model/entities';
|
||||||
import {DATUM, FACE, SHELL, SKETCH_OBJECT, EDGE, LOOP} from '../model/entities';
|
|
||||||
import {combine} from "lstream";
|
|
||||||
import {MObject} from "cad/model/mobject";
|
import {MObject} from "cad/model/mobject";
|
||||||
|
|
||||||
export const SELECTABLE_ENTITIES = [FACE, EDGE, SKETCH_OBJECT, DATUM, SHELL];
|
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[entity] = state([]);
|
||||||
});
|
});
|
||||||
ctx.streams.selection.all = combine(...(Object.values(ctx.streams.selection) as StateStream<string[]>[]))
|
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) {
|
export function activate(ctx) {
|
||||||
|
|
@ -58,6 +56,9 @@ export function activate(ctx) {
|
||||||
})
|
})
|
||||||
|
|
||||||
ctx.entityContextService = {
|
ctx.entityContextService = {
|
||||||
|
get selectedIds() {
|
||||||
|
return ctx.streams.selection.all.value
|
||||||
|
},
|
||||||
selectedEntities: ctx.streams.selection.all.map(ids => ids.map(ctx.cadRegistry.find)).remember()
|
selectedEntities: ctx.streams.selection.all.map(ids => ids.map(ctx.cadRegistry.find)).remember()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -66,6 +67,7 @@ declare module 'context' {
|
||||||
interface CoreContext {
|
interface CoreContext {
|
||||||
|
|
||||||
entityContextService: {
|
entityContextService: {
|
||||||
|
selectedIds: string[],
|
||||||
selectedEntities: StateStream<MObject[]>
|
selectedEntities: StateStream<MObject[]>
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
56
web/app/cad/scene/highlightPlugin.ts
Normal file
56
web/app/cad/scene/highlightPlugin.ts
Normal 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);
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -52,12 +52,12 @@ function createMarker(findEntity, requestRender) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
marked.set(id, mObj);
|
marked.set(id, mObj);
|
||||||
mObj.ext.view && mObj.ext.view.mark(color);
|
mObj.ext.view && mObj.ext.view.mark('selection');
|
||||||
}
|
}
|
||||||
|
|
||||||
function doWithdraw(obj) {
|
function doWithdraw(obj) {
|
||||||
marked.delete(obj.id);
|
marked.delete(obj.id);
|
||||||
obj.ext.view && obj.ext.view.withdraw();
|
obj.ext.view && obj.ext.view.withdraw('selection');
|
||||||
}
|
}
|
||||||
|
|
||||||
function onUpdate() {
|
function onUpdate() {
|
||||||
|
|
@ -67,7 +67,7 @@ function createMarker(findEntity, requestRender) {
|
||||||
|
|
||||||
function clear() {
|
function clear() {
|
||||||
if (marked.size !== 0) {
|
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();
|
marked.clear();
|
||||||
onUpdate();
|
onUpdate();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,15 +9,39 @@ import DatumView from './views/datumView';
|
||||||
import {View} from './views/view';
|
import {View} from './views/view';
|
||||||
import {SketchingView} from "cad/scene/views/faceView";
|
import {SketchingView} from "cad/scene/views/faceView";
|
||||||
|
|
||||||
export function activate(context) {
|
export const ViewSyncPlugin = {
|
||||||
let {streams} = context;
|
|
||||||
streams.cadRegistry.update.attach(sceneSynchronizer(context));
|
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());
|
streams.sketcher.update.attach(mFace => mFace.ext.view.updateSketch());
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function sceneSynchronizer(ctx) {
|
function sceneSynchronizer(ctx) {
|
||||||
const {services: {cadScene, cadRegistry, viewer, wizard, action, pickControl}} = ctx;
|
const {services: {cadScene, cadRegistry, viewer, wizard, action, pickControl}} = ctx;
|
||||||
|
let xxx = 0;
|
||||||
return function() {
|
return function() {
|
||||||
|
console.log("sceneSynchronizer update" + (xxx++))
|
||||||
|
|
||||||
let wgChildren = cadScene.workGroup.children;
|
let wgChildren = cadScene.workGroup.children;
|
||||||
let existent = new Set();
|
let existent = new Set();
|
||||||
for (let i = wgChildren.length - 1; i >= 0; --i) {
|
for (let i = wgChildren.length - 1; i >= 0; --i) {
|
||||||
|
|
@ -25,12 +49,12 @@ function sceneSynchronizer(ctx) {
|
||||||
let shellView = getAttribute(obj, View.MARKER);
|
let shellView = getAttribute(obj, View.MARKER);
|
||||||
if (shellView) {
|
if (shellView) {
|
||||||
let exists = cadRegistry.modelIndex.has(shellView.model.id);
|
let exists = cadRegistry.modelIndex.has(shellView.model.id);
|
||||||
if (!exists) {
|
// if (!exists) {
|
||||||
SceneGraph.removeFromGroup(cadScene.workGroup, obj);
|
SceneGraph.removeFromGroup(cadScene.workGroup, obj);
|
||||||
shellView.dispose();
|
shellView.dispose();
|
||||||
} else {
|
// } else {
|
||||||
existent.add(shellView.model.id);
|
// existent.add(shellView.model.id);
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -38,12 +62,11 @@ function sceneSynchronizer(ctx) {
|
||||||
if (!existent.has(model.id)) {
|
if (!existent.has(model.id)) {
|
||||||
let modelView;
|
let modelView;
|
||||||
if (model instanceof MOpenFaceShell) {
|
if (model instanceof MOpenFaceShell) {
|
||||||
modelView = new OpenFaceShellView(model);
|
modelView = new OpenFaceShellView(ctx, model);
|
||||||
} else if (model instanceof MShell) {
|
} else if (model instanceof MShell) {
|
||||||
modelView = new ShellView(model, undefined, viewer);
|
modelView = new ShellView(ctx, model, undefined,);
|
||||||
} else if (model instanceof MDatum) {
|
} else if (model instanceof MDatum) {
|
||||||
modelView = new DatumView(model, viewer,
|
modelView = new DatumView(ctx, model, wizard.open,
|
||||||
wizard.open,
|
|
||||||
datum => pickControl.pick(datum),
|
datum => pickControl.pick(datum),
|
||||||
e => action.run('menu.datum', e),
|
e => action.run('menu.datum', e),
|
||||||
wizard.isInProgress);
|
wizard.isInProgress);
|
||||||
|
|
@ -69,6 +92,7 @@ function sceneSynchronizer(ctx) {
|
||||||
SceneGraph.addToGroup(cadScene.workGroup, modelView.rootGroup);
|
SceneGraph.addToGroup(cadScene.workGroup, modelView.rootGroup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
viewer.requestRender();
|
viewer.requestRender();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,12 +4,12 @@ import {setAttribute} from 'scene/objectData';
|
||||||
import ScalableLine from 'scene/objects/scalableLine';
|
import ScalableLine from 'scene/objects/scalableLine';
|
||||||
|
|
||||||
export class CurveBasedView extends View {
|
export class CurveBasedView extends View {
|
||||||
constructor(model, tessellation, visualWidth, markerWidth, color, defaultMarkColor) {
|
constructor(ctx, model, tessellation, visualWidth, markerWidth, color, defaultMarkColor, offset, markTable) {
|
||||||
super(model);
|
super(ctx, model, undefined, markTable);
|
||||||
this.rootGroup = SceneGraph.createGroup();
|
this.rootGroup = SceneGraph.createGroup();
|
||||||
this.representation = new ScalableLine(tessellation, visualWidth, color, undefined, false, true);
|
this.representation = new ScalableLine(tessellation, visualWidth, color, undefined, false, true, offset);
|
||||||
this.marker = new ScalableLine(tessellation, markerWidth, defaultMarkColor, undefined, false, true);
|
this.marker = new ScalableLine(tessellation, markerWidth, defaultMarkColor, undefined, false, true, offset);
|
||||||
this.picker = new ScalableLine(tessellation, 10, 0xFA8072, undefined, false, true);
|
this.picker = new ScalableLine(tessellation, 10, 0xFA8072, undefined, false, true, offset);
|
||||||
this.marker.visible = false;
|
this.marker.visible = false;
|
||||||
this.picker.material.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.representation);
|
||||||
this.rootGroup.add(this.marker);
|
this.rootGroup.add(this.marker);
|
||||||
this.rootGroup.add(this.picker);
|
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) {
|
updateVisuals() {
|
||||||
|
const markColor = this.markColor;
|
||||||
|
if (!markColor) {
|
||||||
this.marker.visible = false;
|
this.marker.visible = false;
|
||||||
|
this.representation.visible = true;
|
||||||
|
} else {
|
||||||
|
this.marker.material.color.set(markColor);
|
||||||
|
this.marker.visible = true;
|
||||||
|
this.representation.visible = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,10 @@ import {CSYS_SIZE_MODEL} from '../../craft/datum/csysObject';
|
||||||
|
|
||||||
export default class DatumView extends View {
|
export default class DatumView extends View {
|
||||||
|
|
||||||
constructor(datum, viewer, beginOperation, selectDatum, showDatumMenu, isReadOnly) {
|
constructor(ctx, datum, beginOperation, selectDatum, showDatumMenu, isReadOnly) {
|
||||||
super(datum);
|
super(ctx, datum);
|
||||||
|
|
||||||
|
const viewer = ctx.viewer;
|
||||||
|
|
||||||
class MenuButton extends Mesh {
|
class MenuButton extends Mesh {
|
||||||
|
|
||||||
|
|
@ -137,9 +139,9 @@ export default class DatumView extends View {
|
||||||
setAttribute(this.rootGroup, DATUM, this);
|
setAttribute(this.rootGroup, DATUM, this);
|
||||||
setAttribute(this.rootGroup, View.MARKER, this);
|
setAttribute(this.rootGroup, View.MARKER, this);
|
||||||
|
|
||||||
this.xAxisView = new DatumAxisView(this.model.xAxis, dv.csysObj.xAxis);
|
this.xAxisView = new DatumAxisView(ctx, this.model.xAxis, dv.csysObj.xAxis);
|
||||||
this.yAxisView = new DatumAxisView(this.model.yAxis, dv.csysObj.yAxis);
|
this.yAxisView = new DatumAxisView(ctx, this.model.yAxis, dv.csysObj.yAxis);
|
||||||
this.zAxisView = new DatumAxisView(this.model.zAxis, dv.csysObj.zAxis);
|
this.zAxisView = new DatumAxisView(ctx, this.model.zAxis, dv.csysObj.zAxis);
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
|
|
@ -178,8 +180,8 @@ class AffordanceBox extends Mesh {
|
||||||
|
|
||||||
class DatumAxisView extends View {
|
class DatumAxisView extends View {
|
||||||
|
|
||||||
constructor(model, axisArrow) {
|
constructor(ctx, model, axisArrow) {
|
||||||
super(model);
|
super(ctx, model);
|
||||||
this.axisArrow = axisArrow;
|
this.axisArrow = axisArrow;
|
||||||
setAttribute(this.axisArrow.handle, DATUM_AXIS, this);
|
setAttribute(this.axisArrow.handle, DATUM_AXIS, this);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,23 @@
|
||||||
import {CurveBasedView} from './curveBasedView';
|
import {CurveBasedView} from './curveBasedView';
|
||||||
|
|
||||||
|
const MarkerTable = [
|
||||||
|
{
|
||||||
|
type: 'selection',
|
||||||
|
priority: 10,
|
||||||
|
colors: [0xc42720],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'highlight',
|
||||||
|
priority: 1,
|
||||||
|
colors: [0xffebcd, 0xFF00FF],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export class EdgeView extends CurveBasedView {
|
export class EdgeView extends CurveBasedView {
|
||||||
|
|
||||||
constructor(edge) {
|
constructor(ctx, edge) {
|
||||||
let brepEdge = edge.brepEdge;
|
let brepEdge = edge.brepEdge;
|
||||||
let tess = brepEdge.data.tessellation ? brepEdge.data.tessellation : brepEdge.curve.tessellateToData();
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,14 @@ import * as SceneGraph from 'scene/sceneGraph';
|
||||||
import {SketchObjectView} from './sketchObjectView';
|
import {SketchObjectView} from './sketchObjectView';
|
||||||
import {View} from './view';
|
import {View} from './view';
|
||||||
import {SketchLoopView} from './sketchLoopView';
|
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 {
|
export class SketchingView extends View {
|
||||||
|
|
||||||
constructor(face) {
|
constructor(ctx, face, parent) {
|
||||||
super(face);
|
super(ctx, face, parent);
|
||||||
this.sketchGroup = SceneGraph.createGroup();
|
this.sketchGroup = SceneGraph.createGroup();
|
||||||
this.sketchObjectViews = [];
|
this.sketchObjectViews = [];
|
||||||
this.sketchLoopViews = [];
|
this.sketchLoopViews = [];
|
||||||
|
|
@ -24,17 +27,25 @@ export class SketchingView extends View {
|
||||||
|
|
||||||
const sketchTr = this.model.sketchToWorldTransformation;
|
const sketchTr = this.model.sketchToWorldTransformation;
|
||||||
for (let sketchObject of this.model.sketchObjects) {
|
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);
|
SceneGraph.addToGroup(this.sketchGroup, sov.rootGroup);
|
||||||
this.sketchObjectViews.push(sov);
|
this.sketchObjectViews.push(sov);
|
||||||
}
|
}
|
||||||
this.model.sketchLoops.forEach(mLoop => {
|
this.model.sketchLoops.forEach(mLoop => {
|
||||||
let loopView = new SketchLoopView(mLoop);
|
let loopView = new SketchLoopView(this.ctx, mLoop);
|
||||||
SceneGraph.addToGroup(this.sketchGroup, loopView.rootGroup);
|
SceneGraph.addToGroup(this.sketchGroup, loopView.rootGroup);
|
||||||
this.sketchLoopViews.push(loopView);
|
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() {
|
disposeSketch() {
|
||||||
this.sketchObjectViews.forEach(o => o.dispose());
|
this.sketchObjectViews.forEach(o => o.dispose());
|
||||||
this.sketchLoopViews.forEach(o => o.dispose());
|
this.sketchLoopViews.forEach(o => o.dispose());
|
||||||
|
|
@ -51,34 +62,40 @@ export class SketchingView extends View {
|
||||||
|
|
||||||
export class FaceView extends SketchingView {
|
export class FaceView extends SketchingView {
|
||||||
|
|
||||||
constructor(face, geometry) {
|
constructor(ctx, face, parent, skin) {
|
||||||
super(face);
|
super(ctx, face, parent);
|
||||||
this.geometry = geometry;
|
const geom = new Geometry();
|
||||||
|
geom.dynamic = true;
|
||||||
|
this.geometry = geom;
|
||||||
|
|
||||||
|
this.material = createSolidMaterial(skin);
|
||||||
this.meshFaces = [];
|
this.meshFaces = [];
|
||||||
let off = geometry.faces.length;
|
|
||||||
|
const off = geom.faces.length;
|
||||||
if (face.brepFace.data.tessellation) {
|
if (face.brepFace.data.tessellation) {
|
||||||
tessDataToGeom(face.brepFace.data.tessellation.data, geometry)
|
tessDataToGeom(face.brepFace.data.tessellation.data, geom)
|
||||||
} else {
|
} else {
|
||||||
brepFaceToGeom(face.brepFace, geometry);
|
brepFaceToGeom(face.brepFace, geom);
|
||||||
}
|
}
|
||||||
for (let i = off; i < geometry.faces.length; i++) {
|
for (let i = off; i < geom.faces.length; i++) {
|
||||||
const meshFace = geometry.faces[i];
|
const meshFace = geom.faces[i];
|
||||||
this.meshFaces.push(meshFace);
|
this.meshFaces.push(meshFace);
|
||||||
setAttribute(meshFace, FACE, this);
|
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) {
|
dispose() {
|
||||||
this.updateColor(color || SELECTION_COLOR);
|
super.dispose();
|
||||||
}
|
this.material.dispose();
|
||||||
|
|
||||||
withdraw(color) {
|
|
||||||
this.updateColor(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateColor(color) {
|
|
||||||
setFacesColor(this.meshFaces, color||this.color);
|
|
||||||
this.geometry.colorsNeedUpdate = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -93,4 +110,4 @@ export function setFacesColor(faces, color) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NULL_COLOR = new THREE.Color();
|
export const NULL_COLOR = new THREE.Color();
|
||||||
export const SELECTION_COLOR = 0xffff80;
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
import {setAttribute} from 'scene/objectData';
|
import {setAttribute} from 'scene/objectData';
|
||||||
import {FACE, SHELL} from '../../model/entities';
|
import {FACE, SHELL} from '../../model/entities';
|
||||||
import {NULL_COLOR, SELECTION_COLOR, setFacesColor, SketchingView} from './faceView';
|
import {SketchingView} from './faceView';
|
||||||
import {View} from './view';
|
import {View} from './view';
|
||||||
import {SketchMesh} from './shellView';
|
import {SketchMesh} from './shellView';
|
||||||
|
|
||||||
export class OpenFaceShellView extends View {
|
export class OpenFaceShellView extends View {
|
||||||
|
|
||||||
constructor(shell) {
|
constructor(ctx, shell) {
|
||||||
super(shell);
|
super(ctx, shell);
|
||||||
this.openFace = new OpenFaceView(shell.face);
|
this.openFace = new OpenFaceView(ctx, shell.face, this);
|
||||||
setAttribute(this.rootGroup, SHELL, this);
|
setAttribute(this.rootGroup, SHELL, this);
|
||||||
setAttribute(this.rootGroup, View.MARKER, this);
|
setAttribute(this.rootGroup, View.MARKER, this);
|
||||||
}
|
}
|
||||||
|
|
@ -24,8 +24,8 @@ export class OpenFaceShellView extends View {
|
||||||
|
|
||||||
export class OpenFaceView extends SketchingView {
|
export class OpenFaceView extends SketchingView {
|
||||||
|
|
||||||
constructor(mFace) {
|
constructor(ctx, mFace, parent) {
|
||||||
super(mFace);
|
super(ctx, mFace, parent);
|
||||||
this.material = new THREE.MeshPhongMaterial({
|
this.material = new THREE.MeshPhongMaterial({
|
||||||
vertexColors: THREE.FaceColors,
|
vertexColors: THREE.FaceColors,
|
||||||
// color: 0xB0C4DE,
|
// color: 0xB0C4DE,
|
||||||
|
|
@ -57,10 +57,15 @@ export class OpenFaceView extends SketchingView {
|
||||||
geometry.computeFaceNormals();
|
geometry.computeFaceNormals();
|
||||||
this.mesh = new SketchMesh(geometry, this.material);
|
this.mesh = new SketchMesh(geometry, this.material);
|
||||||
this.rootGroup.add(this.mesh);
|
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() {
|
updateBounds() {
|
||||||
let markedColor = this.markedColor;
|
|
||||||
this.dropGeometry();
|
this.dropGeometry();
|
||||||
|
|
||||||
let bounds2d = [];
|
let bounds2d = [];
|
||||||
|
|
@ -72,7 +77,7 @@ export class OpenFaceView extends SketchingView {
|
||||||
surface.northEastPoint(), surface.northWestPoint()];
|
surface.northEastPoint(), surface.northWestPoint()];
|
||||||
|
|
||||||
this.createGeometry();
|
this.createGeometry();
|
||||||
this.updateColor(markedColor || this.color);
|
this.updateVisuals();
|
||||||
}
|
}
|
||||||
|
|
||||||
traverse(visitor) {
|
traverse(visitor) {
|
||||||
|
|
@ -84,27 +89,6 @@ export class OpenFaceView extends SketchingView {
|
||||||
this.updateBounds();
|
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() {
|
dispose() {
|
||||||
this.dropGeometry();
|
this.dropGeometry();
|
||||||
this.material.dispose();
|
this.material.dispose();
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,18 @@
|
||||||
import {View} from './view';
|
import {View} from './view';
|
||||||
import * as SceneGraph from 'scene/sceneGraph';
|
import * as SceneGraph from 'scene/sceneGraph';
|
||||||
import {getAttribute, setAttribute} from 'scene/objectData';
|
import {getAttribute, setAttribute} from 'scene/objectData';
|
||||||
import {createSolidMaterial} from '../wrappers/sceneObject';
|
|
||||||
import {FaceView, SELECTION_COLOR} from './faceView';
|
import {FaceView, SELECTION_COLOR} from './faceView';
|
||||||
import {EdgeView} from './edgeView';
|
import {EdgeView} from './edgeView';
|
||||||
import {FACE, SHELL} from '../../model/entities';
|
import {FACE, LOOP, SHELL} from '../../model/entities';
|
||||||
import {Mesh} from 'three';
|
import {Mesh} from 'three';
|
||||||
import {VertexView} from "./vertexView";
|
import {VertexView} from "./vertexView";
|
||||||
|
import {MSketchLoop} from "cad/model/mloop";
|
||||||
|
|
||||||
export class ShellView extends View {
|
export class ShellView extends View {
|
||||||
|
|
||||||
constructor(shell, skin, viewer) {
|
constructor(ctx, shell, skin) {
|
||||||
super(shell);
|
super(ctx, shell);
|
||||||
|
|
||||||
this.material = createSolidMaterial(skin);
|
|
||||||
this.rootGroup = SceneGraph.createGroup();
|
this.rootGroup = SceneGraph.createGroup();
|
||||||
this.edgeGroup = SceneGraph.createGroup();
|
this.edgeGroup = SceneGraph.createGroup();
|
||||||
this.vertexGroup = SceneGraph.createGroup();
|
this.vertexGroup = SceneGraph.createGroup();
|
||||||
|
|
@ -27,29 +26,20 @@ export class ShellView extends View {
|
||||||
setAttribute(this.rootGroup, SHELL, this);
|
setAttribute(this.rootGroup, SHELL, this);
|
||||||
setAttribute(this.rootGroup, View.MARKER, 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) {
|
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.faceViews.push(faceView);
|
||||||
this.rootGroup.add(faceView.rootGroup);
|
this.rootGroup.add(faceView.rootGroup);
|
||||||
}
|
}
|
||||||
geom.mergeVertices();
|
|
||||||
|
|
||||||
for (let edge of shell.edges) {
|
for (let edge of shell.edges) {
|
||||||
const edgeView = new EdgeView(edge);
|
const edgeView = new EdgeView(ctx, edge);
|
||||||
SceneGraph.addToGroup(this.edgeGroup, edgeView.rootGroup);
|
SceneGraph.addToGroup(this.edgeGroup, edgeView.rootGroup);
|
||||||
this.edgeViews.push(edgeView);
|
this.edgeViews.push(edgeView);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let vertex of shell.vertices) {
|
for (let vertex of shell.vertices) {
|
||||||
const vertexView = new VertexView(vertex, viewer);
|
const vertexView = new VertexView(ctx, vertex);
|
||||||
SceneGraph.addToGroup(this.vertexGroup, vertexView.rootGroup);
|
SceneGraph.addToGroup(this.vertexGroup, vertexView.rootGroup);
|
||||||
this.vertexViews.push(vertexView);
|
this.vertexViews.push(vertexView);
|
||||||
}
|
}
|
||||||
|
|
@ -58,29 +48,24 @@ export class ShellView extends View {
|
||||||
this.model.location$.attach(loc => {
|
this.model.location$.attach(loc => {
|
||||||
loc.setToMatrix4x4(this.rootGroup.matrix);
|
loc.setToMatrix4x4(this.rootGroup.matrix);
|
||||||
this.rootGroup.matrixWorldNeedsUpdate = true;
|
this.rootGroup.matrixWorldNeedsUpdate = true;
|
||||||
viewer.requestRender();
|
ctx.viewer.requestRender();
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mark(color) {
|
traverse(visitor, includeSelf = true) {
|
||||||
this.faceViews.forEach(faceView => faceView.setColor(color || SELECTION_COLOR));
|
super.traverse(visitor, includeSelf);
|
||||||
}
|
|
||||||
|
|
||||||
withdraw(color) {
|
|
||||||
this.faceViews.forEach(faceView => faceView.setColor(null));
|
|
||||||
}
|
|
||||||
|
|
||||||
traverse(visitor) {
|
|
||||||
super.traverse(visitor);
|
|
||||||
this.faceViews.forEach(f => f.traverse(visitor));
|
this.faceViews.forEach(f => f.traverse(visitor));
|
||||||
this.edgeViews.forEach(e => e.traverse(visitor));
|
this.edgeViews.forEach(e => e.traverse(visitor));
|
||||||
this.vertexViews.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() {
|
dispose() {
|
||||||
this.mesh.material.dispose();
|
|
||||||
this.mesh.geometry.dispose();
|
|
||||||
for (let faceView of this.faceViews) {
|
for (let faceView of this.faceViews) {
|
||||||
faceView.dispose();
|
faceView.dispose();
|
||||||
}
|
}
|
||||||
|
|
@ -100,22 +85,4 @@ export class SketchMesh extends Mesh {
|
||||||
super(geometry, material);
|
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);
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -9,21 +9,40 @@ import Vector from 'math/vector';
|
||||||
import {LOOP} from '../../model/entities';
|
import {LOOP} from '../../model/entities';
|
||||||
import {setAttribute} from 'scene/objectData';
|
import {setAttribute} from 'scene/objectData';
|
||||||
|
|
||||||
export class SketchLoopView extends MarkTracker(View) {
|
const HIGHLIGHT_COLOR = 0xDBFFD9;
|
||||||
constructor(mLoop) {
|
const SELECT_COLOR = 0xCCEFCA;
|
||||||
super(mLoop);
|
|
||||||
|
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();
|
this.rootGroup = SceneGraph.createGroup();
|
||||||
|
|
||||||
const geometry = new Geometry();
|
const geometry = new Geometry();
|
||||||
geometry.dynamic = true;
|
geometry.dynamic = true;
|
||||||
this.mesh = new Mesh(geometry, createSolidMaterial({
|
this.mesh = new Mesh(geometry, createSolidMaterial({
|
||||||
color: SKETCH_LOOP_DEFAULT_HIGHLIGHT_COLOR,
|
// color: HIGHLIGHT_COLOR,
|
||||||
side: DoubleSide,
|
side: DoubleSide,
|
||||||
// transparent: true,
|
// transparent: true,
|
||||||
depthTest: true,
|
// depthTest: true,
|
||||||
depthWrite: false,
|
// depthWrite: false,
|
||||||
polygonOffset: true,
|
polygonOffset: true,
|
||||||
polygonOffsetFactor: -4,
|
polygonOffsetFactor: -1.0, // should less than offset of loop lines
|
||||||
|
polygonOffsetUnits: -1.0,
|
||||||
visible: false
|
visible: false
|
||||||
}));
|
}));
|
||||||
let surface = mLoop.face.surface;
|
let surface = mLoop.face.surface;
|
||||||
|
|
@ -45,27 +64,23 @@ export class SketchLoopView extends MarkTracker(View) {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.rootGroup.add(this.mesh);
|
this.rootGroup.add(this.mesh);
|
||||||
this.mesh.onMouseEnter = (e) => {
|
this.mesh.onMouseEnter = () => {
|
||||||
this.mark(SKETCH_LOOP_DEFAULT_HIGHLIGHT_COLOR, 5);
|
this.ctx.highlightService.highlight(this.model.id);
|
||||||
e.viewer.requestRender();
|
}
|
||||||
};
|
this.mesh.onMouseLeave = () => {
|
||||||
this.mesh.onMouseLeave = (e) => {
|
this.ctx.highlightService.unHighlight(this.model.id);
|
||||||
this.withdraw(5);
|
}
|
||||||
e.viewer.requestRender();
|
this.mesh.raycastPriority = 10;
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mark(color = SKETCH_LOOP_DEFAULT_SELECT_COLOR, priority = 10) {
|
updateVisuals() {
|
||||||
super.mark(color, priority);
|
const markColor = this.markColor;
|
||||||
}
|
if (!markColor) {
|
||||||
|
|
||||||
markImpl(color) {
|
|
||||||
this.mesh.material.visible = true;
|
|
||||||
this.mesh.material.color.setHex(color)
|
|
||||||
}
|
|
||||||
|
|
||||||
withdrawImpl() {
|
|
||||||
this.mesh.material.visible = false;
|
this.mesh.material.visible = false;
|
||||||
|
} else {
|
||||||
|
this.mesh.material.color.set(markColor);
|
||||||
|
this.mesh.material.visible = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
|
|
@ -74,6 +89,3 @@ export class SketchLoopView extends MarkTracker(View) {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const SKETCH_LOOP_DEFAULT_HIGHLIGHT_COLOR = 0xDBFFD9;
|
|
||||||
const SKETCH_LOOP_DEFAULT_SELECT_COLOR = 0xCCEFCA;
|
|
||||||
|
|
@ -2,9 +2,9 @@ import {CurveBasedView} from './curveBasedView';
|
||||||
|
|
||||||
export class SketchObjectView extends CurveBasedView {
|
export class SketchObjectView extends CurveBasedView {
|
||||||
|
|
||||||
constructor(mSketchObject, sketchToWorldTransformation) {
|
constructor(ctx, mSketchObject, sketchToWorldTransformation) {
|
||||||
const color = mSketchObject.construction ? 0x777777 : 0x0000FF;
|
const color = mSketchObject.construction ? 0x777777 : 0x0000FF;
|
||||||
const tess = mSketchObject.sketchPrimitive.tessellate(10).map(sketchToWorldTransformation.apply).map(v => v.data());
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@ import {ConstantScaleGroup} from "scene/scaleHelper";
|
||||||
|
|
||||||
export class VertexView extends View {
|
export class VertexView extends View {
|
||||||
|
|
||||||
constructor(vertex, viewer) {
|
constructor(ctx, vertex) {
|
||||||
super(vertex);
|
super(ctx, vertex);
|
||||||
this.rootGroup = new VertexObject(viewer, 50, 100, () => this.rootGroup.position);
|
this.rootGroup = new VertexObject(ctx.viewer, 50, 100, () => this.rootGroup.position);
|
||||||
|
|
||||||
this.rootGroup.position.x = vertex.brepVertex.point.x;
|
this.rootGroup.position.x = vertex.brepVertex.point.x;
|
||||||
this.rootGroup.position.y = vertex.brepVertex.point.y;
|
this.rootGroup.position.y = vertex.brepVertex.point.y;
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,74 @@
|
||||||
import {createFunctionList} from "gems/func";
|
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 {
|
export class View {
|
||||||
|
|
||||||
static MARKER = 'ModelView';
|
static MARKER = 'ModelView';
|
||||||
|
|
||||||
disposers = createFunctionList();
|
disposers = createFunctionList();
|
||||||
color = new Color();
|
|
||||||
|
|
||||||
constructor(model) {
|
constructor(ctx, model, parent, markerTable = MarkerTable) {
|
||||||
|
this.ctx = ctx;
|
||||||
this.model = model;
|
this.model = model;
|
||||||
|
this.parent = parent;
|
||||||
model.ext.view = this;
|
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) {
|
setVisible(value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
mark(color, priority) {
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
withdraw(priority) {
|
withdraw(type) {
|
||||||
|
this.marks = this.marks.filter(c => c.type !== type)
|
||||||
|
this.updateVisuals();
|
||||||
}
|
}
|
||||||
|
|
||||||
setColor(color) {
|
updateVisuals() {
|
||||||
if (!color) {
|
|
||||||
this.color = new Color();
|
|
||||||
} else {
|
|
||||||
this.color.setStyle(color);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
traverse(visitor) {
|
traverse(visitor, includeSelf = true) {
|
||||||
|
if (includeSelf) {
|
||||||
visitor(this);
|
visitor(this);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
addDisposer(disposer) {
|
addDisposer(disposer) {
|
||||||
this.disposers.add(disposer);
|
this.disposers.add(disposer);
|
||||||
|
|
@ -48,8 +84,8 @@ export class View {
|
||||||
|
|
||||||
export const MarkTracker = ViewClass => class extends ViewClass {
|
export const MarkTracker = ViewClass => class extends ViewClass {
|
||||||
|
|
||||||
constructor(model) {
|
constructor(ctx, model) {
|
||||||
super(model);
|
super(ctx, model);
|
||||||
this.marks = new Map();
|
this.marks = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,5 +57,11 @@ export default [
|
||||||
// actions: ['DATUM_MOVE', 'DATUM_ROTATE', 'DATUM_REBASE', '-', 'PLANE_FROM_DATUM', 'BOX', 'SPHERE', 'TORUS',
|
// actions: ['DATUM_MOVE', 'DATUM_ROTATE', 'DATUM_REBASE', '-', 'PLANE_FROM_DATUM', 'BOX', 'SPHERE', 'TORUS',
|
||||||
// 'CONE', 'CYLINDER']
|
// 'CONE', 'CYLINDER']
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'contextual',
|
||||||
|
label: 'contextual',
|
||||||
|
cssIcons: ['magic'],
|
||||||
|
info: 'contextual actions',
|
||||||
|
actions: ['ModelDisplayOptions', 'ModelAttributesEditor']
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue