mirror of
https://github.com/xibyte/jsketcher
synced 2025-12-06 08:25:19 +01:00
pick list dialog
This commit is contained in:
parent
4e9961669b
commit
f6c75d9c23
21 changed files with 401 additions and 95 deletions
2
modules/lstream/index.d.ts
vendored
2
modules/lstream/index.d.ts
vendored
|
|
@ -11,7 +11,7 @@ interface Stream<T> extends Observable<T> {
|
||||||
|
|
||||||
pairwise(first?: T): Stream<[T, T]>;
|
pairwise(first?: T): Stream<[T, T]>;
|
||||||
|
|
||||||
scan<T>(seed: T, scanFn: (accum: T, current: T) => T): Stream<T>;
|
scan<R>(seed: R, scanFn: (accum: R, current: T) => R): Stream<R>;
|
||||||
|
|
||||||
remember(initialValue: T, usingStream?: any): StateStream<T>
|
remember(initialValue: T, usingStream?: any): StateStream<T>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ export class ScanStream extends StreamBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
attach(observer) {
|
attach(observer) {
|
||||||
|
observer(this.value);
|
||||||
return this.stream.attach(v => {
|
return this.stream.attach(v => {
|
||||||
this.value = this.scanFunc(this.value, v);
|
this.value = this.scanFunc(this.value, v);
|
||||||
observer(this.value);
|
observer(this.value);
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
.objectItem {
|
|
||||||
@itemRadius: 5px;
|
|
||||||
@alt-color: #9c9c9c;
|
|
||||||
|
|
||||||
.button {
|
.button() {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: #0074D9;
|
background-color: #0074D9;
|
||||||
|
|
@ -12,6 +9,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.objectItem {
|
||||||
|
@itemRadius: 5px;
|
||||||
|
@alt-color: #9c9c9c;
|
||||||
|
|
||||||
background-color: rgba(0,0,0, 0.6);
|
background-color: rgba(0,0,0, 0.6);
|
||||||
border: 1px solid #000;
|
border: 1px solid #000;
|
||||||
|
|
@ -48,7 +48,13 @@
|
||||||
&.selected {
|
&.selected {
|
||||||
background: linear-gradient(#59acff, #0074D9);
|
background: linear-gradient(#59acff, #0074D9);
|
||||||
}
|
}
|
||||||
|
&.highlighted {
|
||||||
|
background-color: #0074D9;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.onOffButton {
|
.onOffButton {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -66,7 +72,3 @@
|
||||||
background-color: #5f5f5f;
|
background-color: #5f5f5f;
|
||||||
.button;
|
.button;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ interface GenericExplorerNodeProps {
|
||||||
controls: any;
|
controls: any;
|
||||||
label: any;
|
label: any;
|
||||||
selected: boolean;
|
selected: boolean;
|
||||||
|
highlighted: boolean;
|
||||||
defaultExpanded?: boolean;
|
defaultExpanded?: boolean;
|
||||||
expandable: boolean;
|
expandable: boolean;
|
||||||
select: any;
|
select: any;
|
||||||
|
|
@ -46,7 +47,7 @@ export function GenericExplorerNode(props: GenericExplorerNodeProps) {
|
||||||
{props.controls}
|
{props.controls}
|
||||||
|
|
||||||
<span onClick={props.select}
|
<span onClick={props.select}
|
||||||
className={cx(ls.objectLabel, props.selected && ls.selected)}>
|
className={cx(ls.objectLabel, props.selected && ls.selected, props.highlighted && ls.highlighted)}>
|
||||||
{props.label}
|
{props.label}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,23 @@ pre {
|
||||||
background-color: @color-highlight;
|
background-color: @color-highlight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.highlighted {
|
||||||
|
background-color: @on-color-highlight;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected {
|
||||||
|
background-color: @color-btn-selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pointer {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.centered-row {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
path {
|
path {
|
||||||
stroke: currentColor;
|
stroke: currentColor;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,9 +40,9 @@
|
||||||
@color-neutral: #66727d;
|
@color-neutral: #66727d;
|
||||||
@color-highlight: #003f5d;
|
@color-highlight: #003f5d;
|
||||||
|
|
||||||
@color-btn-selected: hsl(@hue-prim, 55%, 46%);
|
@color-btn-selected: #285f7a;
|
||||||
|
|
||||||
@on-color-highlight: lightskyblue;
|
@on-color-highlight: #5A93BBFF;
|
||||||
@on-color-highlight-variant-yellow: bisque;
|
@on-color-highlight-variant-yellow: bisque;
|
||||||
@on-color-highlight-variant-pink: hotpink;
|
@on-color-highlight-variant-pink: hotpink;
|
||||||
@on-color-highlight-variant-red: tomato;
|
@on-color-highlight-variant-red: tomato;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, {useContext} from "react";
|
import React, {useContext, useEffect} from "react";
|
||||||
import {useStreamWithPatcher, useStreamWithUpdater} from "ui/effects";
|
import {useStreamWithPatcher, useStreamWithUpdater} from "ui/effects";
|
||||||
import Stack from "ui/components/Stack";
|
import Stack from "ui/components/Stack";
|
||||||
import Field from "ui/components/controls/Field";
|
import Field from "ui/components/controls/Field";
|
||||||
|
|
@ -8,6 +8,7 @@ import CheckboxControl from "ui/components/controls/CheckboxControl";
|
||||||
import {AppContext} from "cad/dom/components/AppContext";
|
import {AppContext} from "cad/dom/components/AppContext";
|
||||||
import {ModelAttributes} from "cad/attributes/attributesService";
|
import {ModelAttributes} from "cad/attributes/attributesService";
|
||||||
import {GenericWizard} from "ui/components/GenericWizard";
|
import {GenericWizard} from "ui/components/GenericWizard";
|
||||||
|
import {View} from "cad/scene/views/view";
|
||||||
|
|
||||||
export function DisplayOptionsDialogManager() {
|
export function DisplayOptionsDialogManager() {
|
||||||
|
|
||||||
|
|
@ -46,12 +47,20 @@ export function DisplayOptionsView(props: DisplayOptionsViewProps) {
|
||||||
const ctx = useContext(AppContext);
|
const ctx = useContext(AppContext);
|
||||||
const streamsAndPatchers: [ModelAttributes, any][] = [];
|
const streamsAndPatchers: [ModelAttributes, any][] = [];
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
return () => {
|
||||||
|
View.SUPPRESS_HIGHLIGHTS = false;
|
||||||
|
ctx.viewer.requestRender();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
for (let modelId of props.modelIds) {
|
for (let modelId of props.modelIds) {
|
||||||
const streamAndPatcher = useStreamWithPatcher(ctx => ctx.attributesService.streams.get(modelId));
|
const streamAndPatcher = useStreamWithPatcher(ctx => ctx.attributesService.streams.get(modelId));
|
||||||
streamsAndPatchers.push(streamAndPatcher);
|
streamsAndPatchers.push(streamAndPatcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
function patchAttrs(mutator) {
|
function patchAttrs(mutator) {
|
||||||
|
View.SUPPRESS_HIGHLIGHTS = true;
|
||||||
for (let [model, patch] of streamsAndPatchers) {
|
for (let [model, patch] of streamsAndPatchers) {
|
||||||
patch(mutator);
|
patch(mutator);
|
||||||
}
|
}
|
||||||
|
|
@ -69,7 +78,7 @@ export function DisplayOptionsView(props: DisplayOptionsViewProps) {
|
||||||
<Field active={false} name='label' onFocus={DO_NOTHING} onClick={DO_NOTHING}>
|
<Field active={false} name='label' onFocus={DO_NOTHING} onClick={DO_NOTHING}>
|
||||||
<Label>Color</Label>
|
<Label>Color</Label>
|
||||||
<ColorControl
|
<ColorControl
|
||||||
dialogTitle={`Color for `}
|
dialogTitle={`Color for ${props.modelIds.length} object${props.modelIds.length>0?'s':''}`}
|
||||||
value={attrs.color} onChange={val => patchAttrs(attrs => attrs.color = val)}/>
|
value={attrs.color} onChange={val => patchAttrs(attrs => attrs.color = val)}/>
|
||||||
</Field>
|
</Field>
|
||||||
</Stack>;
|
</Stack>;
|
||||||
|
|
|
||||||
16
web/app/cad/craft/ui/ModelButton.less
Normal file
16
web/app/cad/craft/ui/ModelButton.less
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
.root {
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
31
web/app/cad/craft/ui/ModelButton.tsx
Normal file
31
web/app/cad/craft/ui/ModelButton.tsx
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
import React from "react";
|
||||||
|
import {ModelButtonBehavior} from "cad/craft/ui/ModelButtonBehaviour";
|
||||||
|
import {MObject} from "cad/model/mobject";
|
||||||
|
import cx from "classnames";
|
||||||
|
import ls from './ModelButton.less'
|
||||||
|
|
||||||
|
interface ModelButtonProps {
|
||||||
|
model: MObject;
|
||||||
|
controlVisibility?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ModelButton(props: ModelButtonProps) {
|
||||||
|
return <ModelButtonBehavior model={props.model} controlVisibility={props.controlVisibility}>
|
||||||
|
{behaviour => <div
|
||||||
|
className={cx(ls.root,
|
||||||
|
behaviour.selected&&'selected',
|
||||||
|
behaviour.highlighted&&'highlighted'
|
||||||
|
)}
|
||||||
|
onMouseEnter={behaviour.onMouseEnter}
|
||||||
|
onMouseLeave={behaviour.onMouseLeave}
|
||||||
|
onClick={behaviour.select}
|
||||||
|
>
|
||||||
|
<span className={ls.label}>
|
||||||
|
{behaviour.label}
|
||||||
|
</span>
|
||||||
|
<span className={ls.controls}>
|
||||||
|
{behaviour.controls}
|
||||||
|
</span>
|
||||||
|
</div>}
|
||||||
|
</ModelButtonBehavior>;
|
||||||
|
}
|
||||||
69
web/app/cad/craft/ui/ModelButtonBehaviour.tsx
Normal file
69
web/app/cad/craft/ui/ModelButtonBehaviour.tsx
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
import React, {useContext} from 'react';
|
||||||
|
import {AppContext} from "cad/dom/components/AppContext";
|
||||||
|
import {useStream} from "ui/effects";
|
||||||
|
import {MSketchObject} from "cad/model/msketchObject";
|
||||||
|
import {VisibleSwitch} from "cad/craft/ui/SceneInlineObjectExplorer";
|
||||||
|
import {MOpenFaceShell} from "cad/model/mopenFace";
|
||||||
|
import {MObject} from "cad/model/mobject";
|
||||||
|
import {ModelIcon} from "cad/craft/ui/ModelIcon";
|
||||||
|
|
||||||
|
interface IModelButtonBehavior {
|
||||||
|
select: () => void;
|
||||||
|
selected: boolean;
|
||||||
|
highlighted: boolean;
|
||||||
|
label: any;
|
||||||
|
controls: any;
|
||||||
|
onMouseEnter: () => void;
|
||||||
|
onMouseLeave: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ModelButtonBehavior({children, model, controlVisibility}: {
|
||||||
|
children: (props:IModelButtonBehavior) => any,
|
||||||
|
model: MObject,
|
||||||
|
controlVisibility?: boolean
|
||||||
|
}) {
|
||||||
|
|
||||||
|
const ctx = useContext(AppContext);
|
||||||
|
|
||||||
|
if (controlVisibility === undefined) {
|
||||||
|
controlVisibility = !model.parent
|
||||||
|
}
|
||||||
|
|
||||||
|
const selection: string[] = useStream(ctx => ctx.streams.selection.all);
|
||||||
|
const highlights = useStream(ctx => ctx.highlightService.highlighted$);
|
||||||
|
|
||||||
|
let typeLabel = model.TYPE as string;
|
||||||
|
let idLabel = model.id;
|
||||||
|
let visibilityOf = model;
|
||||||
|
if (model instanceof MSketchObject) {
|
||||||
|
typeLabel = model.sketchPrimitive.constructor.name
|
||||||
|
} else if (model instanceof MOpenFaceShell) {
|
||||||
|
typeLabel='surface';
|
||||||
|
model = model.face;
|
||||||
|
}
|
||||||
|
|
||||||
|
const select = () => ctx.services.pickControl.pick(model);
|
||||||
|
const selected = selection.indexOf(model.id) !== -1;
|
||||||
|
const highlighted = highlights.has(model.id)
|
||||||
|
|
||||||
|
const onMouseEnter= () => ctx.highlightService.highlight(model.id);
|
||||||
|
const onMouseLeave= () => ctx.highlightService.unHighlight(model.id);
|
||||||
|
|
||||||
|
const label = <>
|
||||||
|
<ModelIcon entityType={model.TYPE} style={{marginRight: 5}} /> {typeLabel} {idLabel}
|
||||||
|
</>;
|
||||||
|
|
||||||
|
const controls = <>
|
||||||
|
{controlVisibility && <VisibleSwitch modelId={visibilityOf.id}/>}
|
||||||
|
</>;
|
||||||
|
|
||||||
|
return children({
|
||||||
|
select,
|
||||||
|
selected,
|
||||||
|
highlighted,
|
||||||
|
label,
|
||||||
|
controls,
|
||||||
|
onMouseEnter,
|
||||||
|
onMouseLeave
|
||||||
|
});
|
||||||
|
}
|
||||||
33
web/app/cad/craft/ui/ModelIcon.tsx
Normal file
33
web/app/cad/craft/ui/ModelIcon.tsx
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
import {EntityKind} from "cad/model/entities";
|
||||||
|
import React from "react";
|
||||||
|
import {BiCubeAlt} from "react-icons/bi";
|
||||||
|
import {HiOutlineCubeTransparent} from "react-icons/hi";
|
||||||
|
import {AiOutlineRadiusSetting} from "react-icons/ai";
|
||||||
|
import {IoMdSquareOutline} from "react-icons/io";
|
||||||
|
import {GiThreePointedShuriken} from "react-icons/gi";
|
||||||
|
import {VscDebugBreakpointLogUnverified} from "react-icons/vsc";
|
||||||
|
import {FaVectorSquare} from "react-icons/fa";
|
||||||
|
import {CgArrowLongRightL, CgBorderRight} from "react-icons/cg";
|
||||||
|
|
||||||
|
export function ModelIcon(props: any) {
|
||||||
|
|
||||||
|
const {entityType, ...otherProps} = props;
|
||||||
|
|
||||||
|
const Comp = getIconComp(entityType);
|
||||||
|
|
||||||
|
return <Comp {...otherProps} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function getIconComp(entityType) {
|
||||||
|
switch (entityType) {
|
||||||
|
case EntityKind.SHELL: return BiCubeAlt;
|
||||||
|
case EntityKind.EDGE: return CgBorderRight;
|
||||||
|
case EntityKind.SKETCH_OBJECT: return AiOutlineRadiusSetting;
|
||||||
|
case EntityKind.FACE: return IoMdSquareOutline;
|
||||||
|
case EntityKind.LOOP: return FaVectorSquare;
|
||||||
|
case EntityKind.VERTEX: return VscDebugBreakpointLogUnverified;
|
||||||
|
case EntityKind.DATUM: return GiThreePointedShuriken;
|
||||||
|
case EntityKind.DATUM_AXIS: return CgArrowLongRightL;
|
||||||
|
default: return HiOutlineCubeTransparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
import React, {useContext, useState} from 'react';
|
import React, {useState} from 'react';
|
||||||
import {MShell} from 'cad/model/mshell';
|
import {MShell} from 'cad/model/mshell';
|
||||||
import {MDatum} from 'cad/model/mdatum';
|
import {MDatum} from 'cad/model/mdatum';
|
||||||
import {MOpenFaceShell} from "cad/model/mopenFace";
|
import {MOpenFaceShell} from "cad/model/mopenFace";
|
||||||
import {useStream, useStreamWithPatcher} from "ui/effects";
|
import {useStream, useStreamWithPatcher} from "ui/effects";
|
||||||
import {AppContext} from "cad/dom/components/AppContext";
|
|
||||||
import {MObject} from "cad/model/mobject";
|
import {MObject} from "cad/model/mobject";
|
||||||
import {SceneInlineDelineation, SceneInlineSection, SceneInlineTitleBar} from "ui/components/SceneInlineSection";
|
import {SceneInlineDelineation, SceneInlineSection} from "ui/components/SceneInlineSection";
|
||||||
import {GenericExplorerControl, GenericExplorerNode} from "ui/components/GenericExplorer";
|
import {GenericExplorerControl, GenericExplorerNode} from "ui/components/GenericExplorer";
|
||||||
import ls from "cad/craft/ui/ObjectExplorer.less";
|
import ls from "cad/craft/ui/ObjectExplorer.less";
|
||||||
import Fa from "ui/components/Fa";
|
import Fa from "ui/components/Fa";
|
||||||
import {AiOutlineEye, AiOutlineEyeInvisible} from "react-icons/ai";
|
import {AiOutlineEye, AiOutlineEyeInvisible} from "react-icons/ai";
|
||||||
import {ModelAttributes} from "cad/craft/ui/VisibleSwitch";
|
import {ModelButtonBehavior} from "cad/craft/ui/ModelButtonBehaviour";
|
||||||
|
import {ModelAttributes} from "cad/attributes/attributesService";
|
||||||
|
|
||||||
|
|
||||||
export function SceneInlineObjectExplorer() {
|
export function SceneInlineObjectExplorer() {
|
||||||
|
|
@ -25,7 +25,7 @@ export function SceneInlineObjectExplorer() {
|
||||||
if (m instanceof MOpenFaceShell) {
|
if (m instanceof MOpenFaceShell) {
|
||||||
return <OpenFaceSection shell={m} key={m.id} />
|
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 model={m} key={m.id} controlVisibility>
|
||||||
<Section label='faces' defaultOpen={true}>
|
<Section label='faces' defaultOpen={true}>
|
||||||
{
|
{
|
||||||
m.faces.map(f => <FaceSection face={f} key={f.id}/>)
|
m.faces.map(f => <FaceSection face={f} key={f.id}/>)
|
||||||
|
|
@ -37,7 +37,7 @@ export function SceneInlineObjectExplorer() {
|
||||||
|
|
||||||
</ModelSection>
|
</ModelSection>
|
||||||
} else if (m instanceof MDatum) {
|
} else if (m instanceof MDatum) {
|
||||||
return <ModelSection type='datum' model={m} key={m.id} controlVisibility/>;
|
return <ModelSection model={m} key={m.id} controlVisibility/>;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -46,13 +46,13 @@ export function SceneInlineObjectExplorer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function EdgeSection({edge}) {
|
function EdgeSection({edge}) {
|
||||||
return <ModelSection type='edge' model={edge} key={edge.id}>
|
return <ModelSection model={edge} key={edge.id}>
|
||||||
{edge.adjacentFaces.map(f => <FaceSection face={f} key={f.id}/>)}
|
{edge.adjacentFaces.map(f => <FaceSection face={f} key={f.id}/>)}
|
||||||
</ModelSection>
|
</ModelSection>
|
||||||
}
|
}
|
||||||
|
|
||||||
function FaceSection({face}) {
|
function FaceSection({face}) {
|
||||||
return <ModelSection type='face' model={face} key={face.id}>
|
return <ModelSection model={face} key={face.id}>
|
||||||
|
|
||||||
{(face.productionInfo && face.productionInfo.role) &&
|
{(face.productionInfo && face.productionInfo.role) &&
|
||||||
<Section label={<span>role: {face.productionInfo.role}</span>}/>}
|
<Section label={<span>role: {face.productionInfo.role}</span>}/>}
|
||||||
|
|
@ -70,53 +70,37 @@ function SketchesList({face}) {
|
||||||
label={face.sketchObjects.length ? 'sketch' : <span className={ls.hint}>{'<no sketch assigned>'}</span>}>
|
label={face.sketchObjects.length ? 'sketch' : <span className={ls.hint}>{'<no sketch assigned>'}</span>}>
|
||||||
{face.sketchObjects.map(o => <ModelSection
|
{face.sketchObjects.map(o => <ModelSection
|
||||||
key={o.id}
|
key={o.id}
|
||||||
typeLabel={o.sketchPrimitive.constructor.name}
|
|
||||||
model={o}
|
model={o}
|
||||||
type={'sketchObject'}
|
|
||||||
expandable={false}
|
expandable={false}
|
||||||
/>)}
|
/>)}
|
||||||
</Section>;
|
</Section>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function ModelSection({model, type, typeLabel, expandable = true, controlVisibility = false, visibilityOf = null, ...props}: {
|
export function ModelSection({model, expandable = true, controlVisibility = false, ...props}: {
|
||||||
model: MObject,
|
model: MObject,
|
||||||
visibilityOf?: MObject,
|
|
||||||
typeLabel?: any,
|
|
||||||
type: string,
|
|
||||||
children?: any,
|
children?: any,
|
||||||
controlVisibility?: boolean,
|
controlVisibility?: boolean,
|
||||||
expandable?: boolean,
|
expandable?: boolean,
|
||||||
}) {
|
}) {
|
||||||
const ctx = useContext(AppContext);
|
|
||||||
const selection: string[] = useStream(ctx => ctx.streams.selection[type]);
|
|
||||||
|
|
||||||
const select = () => ctx.services.pickControl.pick(model);
|
return <ModelButtonBehavior model={model} controlVisibility={controlVisibility}>
|
||||||
const selected = selection.indexOf(model.id) !== -1;
|
{behavior => <GenericExplorerNode defaultExpanded={false}
|
||||||
|
|
||||||
let label = <>{typeLabel === undefined ? type:typeLabel} {model.id}</>;
|
|
||||||
visibilityOf = visibilityOf||model;
|
|
||||||
return <GenericExplorerNode defaultExpanded={false}
|
|
||||||
expandable={expandable}
|
expandable={expandable}
|
||||||
label={label}
|
label={behavior.label}
|
||||||
selected={selected}
|
selected={behavior.selected}
|
||||||
select={select}
|
select={behavior.select}
|
||||||
onMouseEnter={() => ctx.highlightService.highlight(model.id)}
|
highlighted={behavior.highlighted}
|
||||||
onMouseLeave={() => ctx.highlightService.unHighlight(model.id)}
|
onMouseEnter={behavior.onMouseEnter}
|
||||||
controls={
|
onMouseLeave={behavior.onMouseLeave}
|
||||||
<>
|
controls={behavior.controls}>
|
||||||
{controlVisibility && <VisibleSwitch modelId={visibilityOf.id}/>}
|
|
||||||
</>
|
|
||||||
|
|
||||||
}>
|
|
||||||
{props.children}
|
{props.children}
|
||||||
</GenericExplorerNode>
|
</GenericExplorerNode>}
|
||||||
|
</ModelButtonBehavior>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function OpenFaceSection({shell}) {
|
function OpenFaceSection({shell}) {
|
||||||
return <ModelSection type='face' model={shell.face} key={shell.face.id} typeLabel='surface'
|
return <ModelSection model={shell} key={shell.id} controlVisibility>
|
||||||
controlVisibility
|
|
||||||
visibilityOf={shell}>
|
|
||||||
<SketchesList face={shell.face}/>
|
<SketchesList face={shell.face}/>
|
||||||
</ModelSection>;
|
</ModelSection>;
|
||||||
}
|
}
|
||||||
|
|
@ -142,13 +126,15 @@ export function VisibleSwitch({modelId}) {
|
||||||
|
|
||||||
let [attrs, patch] = useStreamWithPatcher<ModelAttributes>(ctx => ctx.attributesService.streams.get(modelId));
|
let [attrs, patch] = useStreamWithPatcher<ModelAttributes>(ctx => ctx.attributesService.streams.get(modelId));
|
||||||
|
|
||||||
const onClick = () => {
|
const onClick = (e) => {
|
||||||
patch(attr => {
|
patch(attr => {
|
||||||
attr.hidden = !attr.hidden
|
attr.hidden = !attr.hidden
|
||||||
})
|
});
|
||||||
|
e.stopPropagation();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <GenericExplorerControl onClick={onClick} title={'test'} on={attrs.hidden}>
|
return <GenericExplorerControl onClick={onClick} title={attrs.hidden ? 'show' : 'hide'} on={attrs.hidden}>
|
||||||
{attrs.hidden ? <AiOutlineEyeInvisible /> : <AiOutlineEye />}
|
{attrs.hidden ? <AiOutlineEyeInvisible /> : <AiOutlineEye />}
|
||||||
</GenericExplorerControl>
|
</GenericExplorerControl>
|
||||||
}
|
}
|
||||||
|
|
@ -8,6 +8,8 @@ export interface DomService {
|
||||||
|
|
||||||
contributeComponent: (comp: () => JSX.Element) => void
|
contributeComponent: (comp: () => JSX.Element) => void
|
||||||
|
|
||||||
|
setCursor(cursor: string);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DomPluginInputContext {
|
interface DomPluginInputContext {
|
||||||
|
|
@ -39,7 +41,14 @@ export const DomPlugin: Plugin<DomPluginInputContext, DomPluginContext, DomPlugi
|
||||||
activate(ctx: DomPluginInputContext&DomPluginContext) {
|
activate(ctx: DomPluginInputContext&DomPluginContext) {
|
||||||
ctx.domService = {
|
ctx.domService = {
|
||||||
viewerContainer: document.getElementById('viewer-container'),
|
viewerContainer: document.getElementById('viewer-container'),
|
||||||
contributeComponent
|
contributeComponent,
|
||||||
|
setCursor(cursor: string) {
|
||||||
|
if (cursor) {
|
||||||
|
ctx.domService.viewerContainer.style.cursor = cursor;
|
||||||
|
} else {
|
||||||
|
ctx.domService.viewerContainer.style.removeProperty('cursor');
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ctx.services.dom = ctx.domService;
|
ctx.services.dom = ctx.domService;
|
||||||
|
|
|
||||||
48
web/app/cad/scene/controls/PickListDialog.tsx
Normal file
48
web/app/cad/scene/controls/PickListDialog.tsx
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
import React from "react";
|
||||||
|
import {state} from "lstream";
|
||||||
|
import {useStreamWithUpdater} from "ui/effects";
|
||||||
|
import Window from "ui/components/Window";
|
||||||
|
import {MObject} from "cad/model/mobject";
|
||||||
|
import Stack from "ui/components/Stack";
|
||||||
|
import {ModelSection} from "cad/craft/ui/SceneInlineObjectExplorer";
|
||||||
|
import {ModelButton} from "cad/craft/ui/ModelButton";
|
||||||
|
|
||||||
|
|
||||||
|
export interface PickListDialogRequest {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
token: any;
|
||||||
|
capture: MObject[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PickListDialogRequest$ = state<PickListDialogRequest>(null);
|
||||||
|
|
||||||
|
export function PickListDialog() {
|
||||||
|
|
||||||
|
const [req, setReq] = useStreamWithUpdater(() => PickListDialogRequest$);
|
||||||
|
|
||||||
|
const close = () => setReq(null);
|
||||||
|
|
||||||
|
if (!req) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Window key={req.token}
|
||||||
|
initWidth={250}
|
||||||
|
initLeft={req.x}
|
||||||
|
initTop={req.y}
|
||||||
|
title='pick list'
|
||||||
|
className='small-typography'
|
||||||
|
onClose={close}>
|
||||||
|
<Stack>
|
||||||
|
{req.capture.map(model => {
|
||||||
|
return <ModelButton
|
||||||
|
key={model.id}
|
||||||
|
model={model}
|
||||||
|
/>
|
||||||
|
})}
|
||||||
|
</Stack>
|
||||||
|
</Window>
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,8 +1,13 @@
|
||||||
import * as mask from 'gems/mask'
|
import * as mask from 'gems/mask'
|
||||||
import {getAttribute} from 'scene/objectData';
|
import {getAttribute} from 'scene/objectData';
|
||||||
import {FACE, EDGE, SKETCH_OBJECT, DATUM, SHELL, DATUM_AXIS, LOOP} from '../../model/entities';
|
import {DATUM, DATUM_AXIS, EDGE, FACE, LOOP, SHELL, SKETCH_OBJECT} from '../../model/entities';
|
||||||
import {LOG_FLAGS} from 'cad/logFlags';
|
import {LOG_FLAGS} from 'cad/logFlags';
|
||||||
import {initRayCastDebug, printRaycastDebugInfo, RayCastDebugInfo} from "./rayCastDebug";
|
import {initRayCastDebug, printRaycastDebugInfo, RayCastDebugInfo} from "./rayCastDebug";
|
||||||
|
import {PickListDialog, PickListDialogRequest$} from "cad/scene/controls/PickListDialog";
|
||||||
|
import {contributeComponent} from "cad/dom/components/ContributedComponents";
|
||||||
|
import {MObject} from "cad/model/mobject";
|
||||||
|
import {MFace} from "cad/model/mface";
|
||||||
|
import {MOpenFaceShell} from "cad/model/mopenFace";
|
||||||
|
|
||||||
export interface PickControlService {
|
export interface PickControlService {
|
||||||
setPickHandler(wizardPickHandler: (model) => boolean)
|
setPickHandler(wizardPickHandler: (model) => boolean)
|
||||||
|
|
@ -40,10 +45,12 @@ const DEFAULT_SELECTION_MODE = Object.freeze({
|
||||||
|
|
||||||
|
|
||||||
export const ALL_EXCLUDING_SOLID_KINDS = PICK_KIND.FACE | PICK_KIND.SKETCH | PICK_KIND.EDGE | PICK_KIND.DATUM_AXIS | PICK_KIND.LOOP;
|
export const ALL_EXCLUDING_SOLID_KINDS = PICK_KIND.FACE | PICK_KIND.SKETCH | PICK_KIND.EDGE | PICK_KIND.DATUM_AXIS | PICK_KIND.LOOP;
|
||||||
|
export const ALL_POSSIBLE_KIND = Number.MAX_SAFE_INTEGER;
|
||||||
export function activate(context) {
|
export function activate(context) {
|
||||||
const {services} = context;
|
const {services} = context;
|
||||||
|
|
||||||
|
context.domService.contributeComponent(PickListDialog);
|
||||||
|
|
||||||
const defaultHandler = (model, event, rayCastData?) => {
|
const defaultHandler = (model, event, rayCastData?) => {
|
||||||
if (LOG_FLAGS.PICK) {
|
if (LOG_FLAGS.PICK) {
|
||||||
printPickInfo(model, rayCastData);
|
printPickInfo(model, rayCastData);
|
||||||
|
|
@ -62,6 +69,10 @@ export function activate(context) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (type === SHELL) {
|
||||||
|
if (dispatchSelection(SHELL, modelId, event)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else if (type === SKETCH_OBJECT) {
|
} else if (type === SKETCH_OBJECT) {
|
||||||
if (dispatchSelection(SKETCH_OBJECT, modelId, event)) {
|
if (dispatchSelection(SKETCH_OBJECT, modelId, event)) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -85,12 +96,30 @@ 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('dblclick', mousedblclick, false);
|
domElement.addEventListener('dblclick', mousedblclick, false);
|
||||||
|
domElement.addEventListener('mousemove', mousemove, false);
|
||||||
|
|
||||||
let mouseState = {
|
let mouseState = {
|
||||||
startX: 0,
|
startX: 0,
|
||||||
startY: 0
|
startY: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let timeoutId = null;
|
||||||
|
let pickListDialogMode = false;
|
||||||
|
|
||||||
|
function mousemove(e) {
|
||||||
|
if (pickListDialogMode) {
|
||||||
|
context.domService.setCursor(null);
|
||||||
|
pickListDialogMode = false;
|
||||||
|
}
|
||||||
|
if (timeoutId) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
pickListDialogMode = true;
|
||||||
|
context.domService.setCursor('crosshair');
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
function mousedown(e) {
|
function mousedown(e) {
|
||||||
mouseState.startX = e.offsetX;
|
mouseState.startX = e.offsetX;
|
||||||
mouseState.startY = e.offsetY;
|
mouseState.startY = e.offsetY;
|
||||||
|
|
@ -122,6 +151,25 @@ export function activate(context) {
|
||||||
|
|
||||||
function handlePick(event) {
|
function handlePick(event) {
|
||||||
let pickResults = services.viewer.raycast(event, services.cadScene.workGroup.children, RayCastDebugInfo);
|
let pickResults = services.viewer.raycast(event, services.cadScene.workGroup.children, RayCastDebugInfo);
|
||||||
|
if (pickListDialogMode) {
|
||||||
|
const capture = new Set<MObject>();
|
||||||
|
traversePickResults(event, pickResults, ALL_POSSIBLE_KIND, (model) => {
|
||||||
|
if (!(model.parent instanceof MOpenFaceShell)) {
|
||||||
|
capture.add(model);
|
||||||
|
}
|
||||||
|
if (model instanceof MFace) {
|
||||||
|
capture.add(model.shell);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
PickListDialogRequest$.next({
|
||||||
|
x: event.offsetX,
|
||||||
|
y: event.offsetY,
|
||||||
|
token: Date.now(),
|
||||||
|
capture: Array.from(capture)
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
traversePickResults(event, pickResults, ALL_EXCLUDING_SOLID_KINDS, pickHandler);
|
traversePickResults(event, pickResults, ALL_EXCLUDING_SOLID_KINDS, pickHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -141,7 +189,8 @@ export function activate(context) {
|
||||||
function dispatchSelection(entityType, selectee, event) {
|
function dispatchSelection(entityType, selectee, event) {
|
||||||
let marker = services.marker;
|
let marker = services.marker;
|
||||||
if (marker.isMarked(selectee)) {
|
if (marker.isMarked(selectee)) {
|
||||||
return false;
|
marker.withdraw(selectee);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
let multiMode = event && event.shiftKey;
|
let multiMode = event && event.shiftKey;
|
||||||
|
|
||||||
|
|
@ -158,7 +207,7 @@ export function activate(context) {
|
||||||
traversePickResults(e, pickResults, PICK_KIND.FACE, (sketchFace) => {
|
traversePickResults(e, pickResults, PICK_KIND.FACE, (sketchFace) => {
|
||||||
const shell = sketchFace.shell;
|
const shell = sketchFace.shell;
|
||||||
services.marker.markExclusively(shell.TYPE, shell.id);
|
services.marker.markExclusively(shell.TYPE, shell.id);
|
||||||
context.locationService.edit(shell);
|
// context.locationService.edit(shell);
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,36 @@
|
||||||
import {Plugin} from "plugable/pluginSystem";
|
import {Plugin} from "plugable/pluginSystem";
|
||||||
import {combine, stream} from "lstream";
|
import {combine, merge, Stream, stream} from "lstream";
|
||||||
import Viewer from "cad/scene/viewer";
|
import Viewer from "cad/scene/viewer";
|
||||||
|
import {ScanStream} from "lstream/scan";
|
||||||
|
|
||||||
export class HighlightService {
|
export class HighlightService {
|
||||||
|
|
||||||
highlightEvents = stream<string>();
|
highlightEvents = stream<string>();
|
||||||
unHighlightEvents = stream<string>();
|
unHighlightEvents = stream<string>();
|
||||||
|
highlighted$: Stream<Set<string>>;
|
||||||
|
|
||||||
constructor(viewer: Viewer) {
|
constructor(viewer: Viewer) {
|
||||||
combine(this.highlightEvents, this.unHighlightEvents)
|
combine(this.highlightEvents, this.unHighlightEvents)
|
||||||
.throttle()
|
.throttle()
|
||||||
.attach(() => viewer.requestRender())
|
.attach(() => viewer.requestRender())
|
||||||
|
|
||||||
|
this.highlighted$ = merge(
|
||||||
|
this.highlightEvents.map(id => ({type: '+', id})),
|
||||||
|
this.unHighlightEvents.map(id => ({type: '-', id})),
|
||||||
|
).scan(new Set<string>(), (highlight, event) => {
|
||||||
|
switch (event.type) {
|
||||||
|
case '+': {
|
||||||
|
highlight.add(event.id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case '-': {
|
||||||
|
highlight.delete(event.id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: throw 'illegal state'
|
||||||
|
}
|
||||||
|
return highlight;
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
highlight(id: string) {
|
highlight(id: string) {
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ export interface MarkerService {
|
||||||
|
|
||||||
mark(id: any, color: any)
|
mark(id: any, color: any)
|
||||||
|
|
||||||
|
withdraw(id: any);
|
||||||
|
|
||||||
commit()
|
commit()
|
||||||
|
|
||||||
markExclusively()
|
markExclusively()
|
||||||
|
|
@ -48,7 +50,7 @@ function createMarker(findEntity, requestRender) {
|
||||||
function doMark(id, color) {
|
function doMark(id, color) {
|
||||||
let mObj = findEntity(id);
|
let mObj = findEntity(id);
|
||||||
if (!mObj) {
|
if (!mObj) {
|
||||||
console.warn('no entity found to highlight: ' + id);
|
console.warn('no entity found to select: ' + id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
marked.set(id, mObj);
|
marked.set(id, mObj);
|
||||||
|
|
@ -60,6 +62,16 @@ function createMarker(findEntity, requestRender) {
|
||||||
obj.ext.view && obj.ext.view.withdraw('selection');
|
obj.ext.view && obj.ext.view.withdraw('selection');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function withdraw(id) {
|
||||||
|
let mObj = findEntity(id);
|
||||||
|
if (!mObj) {
|
||||||
|
console.warn('no entity found to deselect: ' + id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
doWithdraw(mObj);
|
||||||
|
onUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
function onUpdate() {
|
function onUpdate() {
|
||||||
requestRender();
|
requestRender();
|
||||||
notify();
|
notify();
|
||||||
|
|
@ -134,7 +146,8 @@ function createMarker(findEntity, requestRender) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
clear, startSession, mark, commit, markExclusively, markArrayExclusively, markAdding, isMarked, $markedEntities
|
clear, startSession, mark, commit, markExclusively, markArrayExclusively, markAdding, isMarked, $markedEntities,
|
||||||
|
withdraw
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,10 +38,6 @@ export class SketchingView extends View {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setColor(color) {
|
|
||||||
this.color = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateVisuals() {
|
updateVisuals() {
|
||||||
this.mesh.material.color.set(this.markColor||this.parent.markColor||this.color||this.parent.color||NULL_COLOR);
|
this.mesh.material.color.set(this.markColor||this.parent.markColor||this.color||this.parent.color||NULL_COLOR);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,9 +60,9 @@ export class ShellView extends View {
|
||||||
this.vertexViews.forEach(e => e.traverse(visitor));
|
this.vertexViews.forEach(e => e.traverse(visitor));
|
||||||
}
|
}
|
||||||
|
|
||||||
updateVisuals(color) {
|
updateVisuals() {
|
||||||
super.updateVisuals(color);
|
super.updateVisuals();
|
||||||
this.faceViews.forEach(f => f.updateVisuals(color));
|
this.faceViews.forEach(f => f.updateVisuals());
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,10 @@ const MarkerTable = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
export class View {
|
export class View {
|
||||||
|
|
||||||
|
static SUPPRESS_HIGHLIGHTS = false
|
||||||
|
|
||||||
static MARKER = 'ModelView';
|
static MARKER = 'ModelView';
|
||||||
|
|
||||||
disposers = createFunctionList();
|
disposers = createFunctionList();
|
||||||
|
|
@ -31,9 +32,14 @@ export class View {
|
||||||
}
|
}
|
||||||
|
|
||||||
setColor(color) {
|
setColor(color) {
|
||||||
|
this.color = color;
|
||||||
|
this.updateVisuals();
|
||||||
}
|
}
|
||||||
|
|
||||||
get markColor() {
|
get markColor() {
|
||||||
|
if (View.SUPPRESS_HIGHLIGHTS) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
if (this.marks.length !== 0) {
|
if (this.marks.length !== 0) {
|
||||||
const baseMark = this.marks[0];
|
const baseMark = this.marks[0];
|
||||||
return baseMark.colors[Math.min(baseMark.colors.length, this.marks.length) - 1];
|
return baseMark.colors[Math.min(baseMark.colors.length, this.marks.length) - 1];
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ export class SceneSolid {
|
||||||
export function createSolidMaterial(skin) {
|
export function createSolidMaterial(skin) {
|
||||||
return new THREE.MeshPhongMaterial(Object.assign({
|
return new THREE.MeshPhongMaterial(Object.assign({
|
||||||
vertexColors: THREE.FaceColors,
|
vertexColors: THREE.FaceColors,
|
||||||
// color: 0xB0C4DE,
|
color: 0xaeaeae,
|
||||||
shininess: 0,
|
shininess: 0,
|
||||||
polygonOffset : true,
|
polygonOffset : true,
|
||||||
polygonOffsetFactor : 1,
|
polygonOffsetFactor : 1,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue