mirror of
https://github.com/xibyte/jsketcher
synced 2025-12-06 16:33:15 +01:00
object highlight mode, reuse styles
This commit is contained in:
parent
279db19809
commit
547ec02b01
39 changed files with 409 additions and 243 deletions
|
|
@ -1,19 +1,15 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {NOOP} from "../gems/func";
|
||||
|
||||
//TODO: remove it
|
||||
export default class WindowSystem extends React.Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.moveHandler = null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.body.onmousemove = e => {
|
||||
if (this.moveHandler !== null) {
|
||||
this.moveHandler(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
componentWillUnMount() {
|
||||
|
|
@ -24,7 +20,7 @@ export default class WindowSystem extends React.Component {
|
|||
}
|
||||
|
||||
childContext = {
|
||||
setWindowMoveHandler: moveHandler => this.moveHandler = moveHandler
|
||||
setWindowMoveHandler: NOOP
|
||||
};
|
||||
|
||||
getChildContext() {
|
||||
|
|
|
|||
|
|
@ -5,16 +5,16 @@ import Fa from "./Fa";
|
|||
import WindowSystem from '../WindowSystem';
|
||||
import cx from 'classnames';
|
||||
|
||||
|
||||
export default class Window extends React.Component {
|
||||
|
||||
constructor({initWidth, initLeft, initTop, initHeight}) {
|
||||
constructor({initWidth, initLeft, initTop, initRight, initHeight}) {
|
||||
super();
|
||||
this.state = {
|
||||
width: initWidth,
|
||||
height: initHeight,
|
||||
left: initLeft,
|
||||
top: initTop
|
||||
top: initTop,
|
||||
right: initRight
|
||||
};
|
||||
this.dragOrigin = null;
|
||||
}
|
||||
|
|
@ -56,11 +56,22 @@ export default class Window extends React.Component {
|
|||
|
||||
startDrag = e => {
|
||||
this.dragOrigin = {x : e.pageX, y : e.pageY};
|
||||
let left = this.state.left;
|
||||
let top = this.state.top;
|
||||
if (left === undefined) {
|
||||
left = this.el.offsetLeft;
|
||||
}
|
||||
if (top === undefined) {
|
||||
top = this.el.offsetTop;
|
||||
}
|
||||
this.originLocation = {
|
||||
left: this.state.left,
|
||||
top: this.state.top
|
||||
left,
|
||||
top,
|
||||
right: undefined
|
||||
};
|
||||
this.context.setWindowMoveHandler(this.doDrag);
|
||||
|
||||
this.handlerToRestore = document.body.onmousemove;
|
||||
document.body.onmousemove = this.doDrag;
|
||||
};
|
||||
|
||||
doDrag = e => {
|
||||
|
|
@ -73,13 +84,11 @@ export default class Window extends React.Component {
|
|||
|
||||
stopDrag = e => {
|
||||
this.dragOrigin = null;
|
||||
this.context.setWindowMoveHandler(null);
|
||||
document.body.onmousemove = this.handlerToRestore;
|
||||
};
|
||||
|
||||
keepRef = el => this.el = el;
|
||||
|
||||
static contextTypes = WindowSystem.childContextTypes;
|
||||
|
||||
}
|
||||
|
||||
Window.defaultProps = {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
import React from 'react';
|
||||
|
||||
import ls from './Button.less'
|
||||
import cx from 'classnames';
|
||||
|
||||
export default function Button({type, onClick, className, children}) {
|
||||
|
||||
return <button onClick={onClick} className={cx(ls[type], ls.button, className)}>{children}</button>
|
||||
return <button onClick={onClick} className={cx(type, className)}>{children}</button>
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
@import '../../styles/theme.less';
|
||||
@import '../../styles/mixins.less';
|
||||
|
||||
.button {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.neutral, .accent, .danger {
|
||||
background-color: darken(@color-neutral, 10%);
|
||||
}
|
||||
|
||||
.neutral {
|
||||
.button-behavior(@color-neutral)
|
||||
}
|
||||
|
||||
.accent {
|
||||
.button-behavior(@color-accent)
|
||||
}
|
||||
|
||||
.highlihgt {
|
||||
.button-behavior(@color-highlight)
|
||||
}
|
||||
|
||||
.danger {
|
||||
.button-behavior(@color-danger)
|
||||
}
|
||||
|
||||
.minor {
|
||||
.button-behavior(@color-neutral)
|
||||
}
|
||||
|
|
@ -2,8 +2,8 @@ import React from 'react';
|
|||
|
||||
import ls from './ButtonGroup.less'
|
||||
|
||||
export default function ButtonGroup({children}) {
|
||||
export default function ButtonGroup(props) {
|
||||
|
||||
return <div className={ls.root}>{children}</div>
|
||||
return <div className={ls.root} {...props}/>;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
.root {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
& > * {
|
||||
& > *:not(:first-child) {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import ls from './InputControl.less'
|
||||
|
||||
export default class InputControl extends React.Component {
|
||||
|
||||
render() {
|
||||
let {type, inputRef, ...props} = this.props;
|
||||
|
||||
return <div className={ls[type]}>
|
||||
return <div className={type}>
|
||||
<input type='text' ref={inputRef} {...props} spellCheck='false' />
|
||||
</div>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
@import '../../styles/theme.less';
|
||||
|
||||
.number input {
|
||||
.colorStyling(@control-color-number, @control-bg);
|
||||
}
|
||||
|
||||
.text input {
|
||||
.colorStyling(@control-color-text, @control-bg);
|
||||
}
|
||||
|
||||
.colorStyling(@color, @bg) {
|
||||
color: @color;
|
||||
background: @bg;
|
||||
outline: none;
|
||||
border: @bg 1px solid;
|
||||
padding: 2px;
|
||||
&:focus {
|
||||
border: #444 1px solid;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,4 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import ls from './Label.less'
|
||||
|
||||
export default function Label({children}) {
|
||||
return <span>{children}</span>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
@import "./theme.less";
|
||||
@import "./table.less";
|
||||
|
||||
*.autoMarginLeft {
|
||||
margin-left: auto !important;
|
||||
|
|
@ -25,14 +24,6 @@
|
|||
display: inline-block;
|
||||
}
|
||||
|
||||
a {
|
||||
color: @font-color;
|
||||
text-decoration: underline;
|
||||
&:hover {
|
||||
color: @color-text-highlight
|
||||
}
|
||||
}
|
||||
|
||||
.scrollable {
|
||||
overflow: auto;
|
||||
}
|
||||
64
modules/ui/styles/init/form.less
Normal file
64
modules/ui/styles/init/form.less
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
button, input, select, textarea {
|
||||
font-family: inherit;
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 4px 8px;
|
||||
border: 0;
|
||||
background-color: darken(@color-neutral, 10%);
|
||||
color: @font-color;
|
||||
white-space: nowrap;
|
||||
.button-behavior(@color-neutral)
|
||||
//line-height: 1.5;
|
||||
}
|
||||
|
||||
button.neutral {
|
||||
.button-behavior(@color-neutral)
|
||||
}
|
||||
|
||||
button.accent {
|
||||
.button-behavior(@color-accent)
|
||||
}
|
||||
|
||||
button.highlight {
|
||||
.button-behavior(@color-highlight)
|
||||
}
|
||||
|
||||
button.danger {
|
||||
.button-behavior(@color-danger)
|
||||
}
|
||||
|
||||
button.minor {
|
||||
.button-behavior(@color-neutral)
|
||||
}
|
||||
|
||||
//--------------
|
||||
|
||||
input, textarea {
|
||||
outline: none;
|
||||
background: @control-bg;
|
||||
border: @control-bg 1px solid;
|
||||
padding: 2px;
|
||||
color: @control-color-text;
|
||||
&::selection {
|
||||
color: white;
|
||||
background: blue;
|
||||
|
||||
}
|
||||
&:focus {
|
||||
border: #326da3 1px solid;
|
||||
background: #173851;
|
||||
}
|
||||
}
|
||||
|
||||
input.number {
|
||||
border-color: @control-color-number;
|
||||
color: @control-color-number;
|
||||
}
|
||||
|
||||
input.text {
|
||||
border-color: @control-color-text;
|
||||
color: @control-color-text;
|
||||
}
|
||||
|
||||
7
modules/ui/styles/init/index.less
Normal file
7
modules/ui/styles/init/index.less
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
@import "./minireset.less";
|
||||
@import "../theme.less";
|
||||
@import "../mixins.less";
|
||||
@import "./main.less";
|
||||
@import "./links.less";
|
||||
@import "./form.less";
|
||||
@import "./tables.less";
|
||||
7
modules/ui/styles/init/links.less
Normal file
7
modules/ui/styles/init/links.less
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
a {
|
||||
color: @font-color;
|
||||
text-decoration: underline;
|
||||
&:hover {
|
||||
color: @color-text-highlight
|
||||
}
|
||||
}
|
||||
|
|
@ -1,39 +1,25 @@
|
|||
@import "../theme.less";
|
||||
@import "../mixins.less";
|
||||
|
||||
@fontSize: 11px;
|
||||
|
||||
html, pre {
|
||||
font: @fontSize 'Lucida Grande', sans-serif;
|
||||
font: @font-size 'Lucida Grande', sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: @bg-color;
|
||||
color: @font-color;
|
||||
font-family: 'Roboto', 'Helvetica Neue Light', HelveticaNeue-Light, 'Helvetica Neue', Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
iframe {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
:global(.disable-selection) {
|
||||
.disable-selection {
|
||||
.no-selection();
|
||||
}
|
||||
|
||||
:global(.compact-font) {
|
||||
font-family: 'Helvetica Neue Light', HelveticaNeue-Light, 'Helvetica Neue', Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
button {
|
||||
border: 0;
|
||||
background-color: inherit;
|
||||
color: inherit;
|
||||
.compact-font, .condensed {
|
||||
font-family: 'Roboto Condensed', 'Helvetica Neue Light', HelveticaNeue-Light, 'Helvetica Neue', Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
pre {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
table {
|
||||
font-size: @fontSize;
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
@import "./theme.less";
|
||||
table {
|
||||
font-size: @font-size;
|
||||
}
|
||||
|
||||
.stripedTable {
|
||||
table.striped {
|
||||
& tr:nth-child(even), & th {
|
||||
background-color: @bg-color
|
||||
}
|
||||
|
|
@ -12,7 +14,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.delineatedTable {
|
||||
table.delineated {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
& td, & th {
|
||||
|
|
@ -26,5 +26,7 @@
|
|||
|
||||
@color-text-highlight: #9cdaf7;
|
||||
|
||||
@font-size: 11px;
|
||||
|
||||
//@work-area-toolbar-bg-color: ;
|
||||
//@work-area-toolbar-font-color: ;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import React, {Fragment} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import 'ui/styles/init/minireset.css';
|
||||
import 'ui/styles/init/main.less';
|
||||
import 'ui/styles/init/index.less';
|
||||
import AppTabs from "./AppTabs";
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ const Script = bind(streams => streams.expressions.script)(
|
|||
|
||||
const VarTable = bind(streams => streams.expressions.list)(
|
||||
function VarTable({value}) {
|
||||
return <table className={cx(cmn.fullWidth, cmn.stripedTable, cmn.delineatedTable)}>
|
||||
return <table className={cx(cmn.fullWidth, 'striped', 'delineated')}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import * as toolkit from './ui/toolkit';
|
|||
import {Constraints} from './sketcher/parametric'
|
||||
import './utils/jqueryfy'
|
||||
import '../css/app.less'
|
||||
import 'ui/styles/init/index.less';
|
||||
|
||||
import ReactDOM from "react-dom";
|
||||
import {SketcherApp} from "./sketcher/components/SketcherApp";
|
||||
import React from "react";
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
import React, {useEffect, useState} from 'react';
|
||||
import Widget from "ui/components/Widget";
|
||||
import React, {useCallback, useContext, useEffect, useState} from 'react';
|
||||
import NumberControl from "ui/components/controls/NumberControl";
|
||||
import Stack from "ui/components/Stack";
|
||||
import ButtonGroup from "ui/components/controls/ButtonGroup";
|
||||
import Button from "ui/components/controls/Button";
|
||||
import {useStream} from "../../../../modules/ui/effects";
|
||||
import CheckboxControl from "../../../../modules/ui/components/controls/CheckboxControl";
|
||||
import {useStream} from "ui/effects";
|
||||
import CheckboxControl from "ui/components/controls/CheckboxControl";
|
||||
import Window from "ui/components/Window";
|
||||
import Field from "ui/components/controls/Field";
|
||||
import Label from "../../../../modules/ui/components/controls/Label";
|
||||
import {SketcherAppContext} from "./SketcherApp";
|
||||
|
||||
export function ConstraintEditor() {
|
||||
|
||||
|
|
@ -13,7 +16,17 @@ export function ConstraintEditor() {
|
|||
|
||||
const [values, setValues] = useState(null);
|
||||
|
||||
useEffect(() => setValues(req && {...req.constraint.constants}), [req]);
|
||||
useEffect(() => {
|
||||
setValues(req && {...req.constraint.constants})
|
||||
return () => {
|
||||
if (req) {
|
||||
viewer.unHighlight(req.constraint.objects);
|
||||
viewer.refresh();
|
||||
}
|
||||
}
|
||||
}, [req]);
|
||||
|
||||
const {viewer} = useContext(SketcherAppContext);
|
||||
|
||||
const setValue = (name, value) => {
|
||||
setValues({...value, [name]: value});
|
||||
|
|
@ -25,6 +38,17 @@ export function ConstraintEditor() {
|
|||
|
||||
const {constraint, onCancel, onApply} = req;
|
||||
|
||||
const highlight = () => {
|
||||
viewer.highlight(constraint.objects, true);
|
||||
viewer.refresh();
|
||||
};
|
||||
|
||||
const unHighlight = () => {
|
||||
viewer.unHighlightAll();
|
||||
viewer.refresh();
|
||||
};
|
||||
|
||||
|
||||
const apply = () => {
|
||||
Object.keys(constraint.schema.constants).map(name => {
|
||||
const val = values[name];
|
||||
|
|
@ -35,12 +59,14 @@ export function ConstraintEditor() {
|
|||
onApply();
|
||||
};
|
||||
|
||||
return <Widget>
|
||||
return <Window initWidth={250} initLeft={5} initTop={5} title={constraint.schema.name} onClose={onCancel}
|
||||
onMouseEnter={highlight}
|
||||
onMouseLeave={unHighlight}>
|
||||
|
||||
<Stack>
|
||||
|
||||
{Object.keys(constraint.schema.constants).sort().map(name => <div key={name}>
|
||||
|
||||
{Object.keys(constraint.schema.constants).sort().map(name => <Field key={name}>
|
||||
<Label>{name}</Label>
|
||||
{
|
||||
(() => {
|
||||
const def = constraint.schema.constants[name];
|
||||
|
|
@ -57,7 +83,7 @@ export function ConstraintEditor() {
|
|||
|
||||
}
|
||||
|
||||
</div>)}
|
||||
</Field>)}
|
||||
|
||||
|
||||
<ButtonGroup>
|
||||
|
|
@ -67,7 +93,7 @@ export function ConstraintEditor() {
|
|||
|
||||
</Stack>
|
||||
|
||||
</Widget>;
|
||||
</Window>;
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
import React, {useContext} from 'react';
|
||||
import React, {useContext, useEffect} from 'react';
|
||||
import ls from './ConstraintExplorer.less';
|
||||
import Fa from 'ui/components/Fa';
|
||||
import {useStream} from "../../../../modules/ui/effects";
|
||||
import {useStream} from "ui/effects";
|
||||
import {SketcherAppContext} from "./SketcherApp";
|
||||
import cx from 'classnames';
|
||||
import {editConstraint} from "./ConstraintEditor";
|
||||
import {NOOP} from "../../../../modules/gems/func";
|
||||
|
||||
|
||||
export function ConstraintExplorer(props) {
|
||||
|
|
@ -21,11 +20,25 @@ export function ConstraintList() {
|
|||
|
||||
const constraints = useStream(ctx => ctx.viewer.parametricManager.$constraints);
|
||||
|
||||
let i = 0;
|
||||
return constraints.map((c) => {
|
||||
if (c.internal) {
|
||||
return null;
|
||||
}
|
||||
i ++;
|
||||
return <ConstraintButton prefix={i+'.'} constraint={c} />
|
||||
})
|
||||
}
|
||||
|
||||
export function ConstraintButton({prefix='', constraint: c, ...props}) {
|
||||
|
||||
const {viewer, ui} = useContext(SketcherAppContext);
|
||||
|
||||
const edit = (constraint) => {
|
||||
if (constraint.editable) {
|
||||
editConstraint(ui.$constraintEditRequest, constraint, NOOP);
|
||||
editConstraint(ui.$constraintEditRequest, constraint, () => {
|
||||
viewer.parametricManager.reSolve();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -35,33 +48,31 @@ export function ConstraintList() {
|
|||
};
|
||||
|
||||
const highlight = constr => {
|
||||
viewer.select(constr.objects, true);
|
||||
viewer.capture('highlight', constr.objects, true);
|
||||
viewer.refresh();
|
||||
};
|
||||
|
||||
const withdraw = constr => {
|
||||
viewer.deselectAll();
|
||||
const withdraw = () => {
|
||||
viewer.withdrawAll('highlight');
|
||||
viewer.refresh();
|
||||
};
|
||||
|
||||
useEffect(() => withdraw, [c]);
|
||||
|
||||
return constraints.map((c, i) => {
|
||||
if (c.internal) {
|
||||
return null;
|
||||
}
|
||||
const conflicting = viewer.parametricManager.algNumSystem.conflicting.has(c);
|
||||
const redundant = viewer.parametricManager.algNumSystem.redundant.has(c);
|
||||
const conflicting = viewer.parametricManager.algNumSystem.conflicting.has(c);
|
||||
const redundant = viewer.parametricManager.algNumSystem.redundant.has(c);
|
||||
|
||||
return <div key={c.id} className={cx(ls.objectItem, conflicting&&ls.conflicting, redundant&&ls.redundant)}
|
||||
onClick={() => c.schema.constants && edit(c)}
|
||||
onMouseEnter={() => highlight(c)}
|
||||
onMouseLeave={() => withdraw(c)}>
|
||||
<span className={ls.objectIcon}><img width="15px" src='img/vec/pointOnArc.svg'/></span>
|
||||
<span className={ls.objectTag}>
|
||||
{i}. {c.schema.name}
|
||||
return <div key={c.id} className={cx(ls.objectItem, conflicting&&ls.conflicting, redundant&&ls.redundant)}
|
||||
onClick={() => c.schema.constants && edit(c)}
|
||||
onMouseEnter={() => highlight(c)}
|
||||
onMouseLeave={() => withdraw(c)}
|
||||
{...props}>
|
||||
<span className={ls.objectIcon}><img width="15px" src='img/vec/pointOnArc.svg'/></span>
|
||||
<span className={ls.objectTag}>
|
||||
{prefix} {c.schema.name}
|
||||
</span>
|
||||
<span className={ls.removeButton} onClick={() => remove(c)}><Fa icon='times'/></span>
|
||||
<span className={ls.removeButton} onClick={() => remove(c)}><Fa icon='times'/></span>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
})
|
||||
}
|
||||
|
|
@ -4,10 +4,12 @@ import {matchAvailableActions} from "../actions";
|
|||
import {useStream} from "../../../../modules/ui/effects";
|
||||
import {SketcherAppContext} from "./SketcherApp";
|
||||
import {MatchIndex, matchSelection} from "../selectionMatcher";
|
||||
import {ConstraintButton} from "./ConstraintExplorer";
|
||||
|
||||
export function ContextualControls() {
|
||||
|
||||
const selection = useStream(ctx => ctx.viewer.streams.selection);
|
||||
const ___ = useStream(ctx => ctx.viewer.parametricManager.$constraints);
|
||||
|
||||
const ctx = useContext(SketcherAppContext);
|
||||
|
||||
|
|
@ -15,8 +17,12 @@ export function ContextualControls() {
|
|||
return null;
|
||||
}
|
||||
|
||||
const obj = selection.length === 1 ? selection[0] : null;
|
||||
|
||||
const availableActions = matchAvailableActions(selection);
|
||||
|
||||
const nonInternalConstraints = obj && Array.from(obj.constraints).filter(c => !c.internal);
|
||||
|
||||
return <div className={ls.root}>
|
||||
|
||||
{
|
||||
|
|
@ -25,9 +31,25 @@ export function ContextualControls() {
|
|||
|
||||
<div className={ls.hr}>AVAILABLE ACTIONS:</div>
|
||||
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
maxWidth: 200,
|
||||
flexWrap: 'wrap',
|
||||
}}>
|
||||
{
|
||||
availableActions.map(a => <button
|
||||
style={{
|
||||
margin: 3
|
||||
}}
|
||||
onClick={() => a.invoke(ctx, matchSelection(a.selectionMatcher, new MatchIndex(selection), false))}
|
||||
title={a.description}>{a.shortName}</button>)
|
||||
}
|
||||
</div>
|
||||
{
|
||||
availableActions.map(a => <button onClick={() => a.invoke(ctx, matchSelection(a.selectionMatcher, new MatchIndex(selection), false))}
|
||||
title={a.description}>{a.shortName}</button>)
|
||||
nonInternalConstraints && nonInternalConstraints.length !== 0 && <>
|
||||
<div className={ls.hr}>PARTICIPATES IN CONSTRAINTS:</div>
|
||||
{nonInternalConstraints.map(c => <ConstraintButton constraint={c} key={c.id} style={{borderColor: 'white'}}/>)}
|
||||
</>
|
||||
}
|
||||
|
||||
</div>;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
|
||||
.root {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
margin: 5px;
|
||||
padding: 3px 5px;
|
||||
background-color: #000D;
|
||||
|
|
|
|||
|
|
@ -286,7 +286,7 @@ export const ConstraintDefinitions = {
|
|||
angle: {
|
||||
type: 'number',
|
||||
description: 'line angle',
|
||||
internal: true,
|
||||
readOnly: true,
|
||||
initialValue: ([segment1, segment2]) => {
|
||||
const a1 = segment1.params.ang.get();
|
||||
const a2 = segment2.params.ang.get();
|
||||
|
|
@ -315,7 +315,7 @@ export const ConstraintDefinitions = {
|
|||
angle: {
|
||||
type: 'number',
|
||||
description: 'line angle',
|
||||
internal: true,
|
||||
readOnly: true,
|
||||
initialValue: ([segment1, segment2]) => {
|
||||
const a1 = segment1.params.ang.get();
|
||||
const a2 = segment2.params.ang.get();
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import {eqEps} from "../../brep/geom/tolerance";
|
|||
import {Polynomial, POW_1_FN} from "./polynomial";
|
||||
import {compositeFn} from "gems/func";
|
||||
|
||||
const DEBUG = true;
|
||||
const DEBUG = false;
|
||||
|
||||
export class AlgNumSubSystem {
|
||||
|
||||
|
|
@ -32,7 +32,11 @@ export class AlgNumSubSystem {
|
|||
|
||||
inTransaction = false;
|
||||
|
||||
constructor() {
|
||||
visualLimit = 100;
|
||||
|
||||
constructor(calcVisualLimit) {
|
||||
|
||||
this.calcVisualLimit = calcVisualLimit;
|
||||
|
||||
this.solveStatus = {
|
||||
error: 0,
|
||||
|
|
@ -274,6 +278,8 @@ export class AlgNumSubSystem {
|
|||
iso.beingSolvedParams.forEach(solverParam => this.paramToIsolation.set(solverParam.objectParam, iso))
|
||||
});
|
||||
|
||||
this.visualLimit = this.calcVisualLimit();
|
||||
|
||||
if (DEBUG) {
|
||||
console.log('solving system:');
|
||||
this.polynomialIsolations.forEach((iso, i) => {
|
||||
|
|
@ -490,8 +496,8 @@ class Isolation {
|
|||
let val = solverParam.objectParam.get();
|
||||
|
||||
if (this.system.controlBounds) {
|
||||
if (solverParam.objectParam.min && val < solverParam.objectParam.min) {
|
||||
val = solverParam.objectParam.min;
|
||||
if (solverParam.objectParam.enforceVisualLimit && val < this.system.visualLimit) {
|
||||
val = this.system.visualLimit;
|
||||
}
|
||||
}
|
||||
solverParam.set(val);
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
import {askNumber} from '../utils/utils';
|
||||
import {Constraints} from './constraints';
|
||||
import {AlgNumConstraint, ConstraintDefinitions} from "./constr/ANConstraints";
|
||||
import {AlgNumSubSystem} from "./constr/AlgNumSystem";
|
||||
import {state, stream} from "../../../modules/lstream";
|
||||
import {stream} from "../../../modules/lstream";
|
||||
|
||||
export {Constraints, ParametricManager}
|
||||
|
||||
class ParametricManager {
|
||||
|
||||
algNumSystem = new AlgNumSubSystem();
|
||||
algNumSystem = null;;
|
||||
|
||||
constantTable = {};
|
||||
externalConstantResolver = null;
|
||||
|
|
@ -26,7 +25,7 @@ class ParametricManager {
|
|||
this.viewer.params.subscribe('constantDefinition', 'parametricManager', this.onConstantsExternalChange, this)();
|
||||
this.constantResolver = this.createConstantResolver();
|
||||
this.messageSink = msg => alert(msg);
|
||||
|
||||
this.reset();
|
||||
}
|
||||
|
||||
get allConstraints() {
|
||||
|
|
@ -34,7 +33,16 @@ class ParametricManager {
|
|||
}
|
||||
|
||||
reset() {
|
||||
this.algNumSystem = new AlgNumSubSystem();
|
||||
const pt = {x:0,y:0};
|
||||
const limit = 30; //px
|
||||
this.algNumSystem = new AlgNumSubSystem(() => {
|
||||
//100 px limit
|
||||
this.viewer.screenToModel2(0, 0, pt);
|
||||
const x1 = pt.x;
|
||||
this.viewer.screenToModel2(limit, 0, pt);
|
||||
const x2 = pt.x;
|
||||
return Math.abs(x2 - x1);
|
||||
});
|
||||
}
|
||||
|
||||
addAlgNum(constr) {
|
||||
|
|
@ -161,6 +169,11 @@ class ParametricManager {
|
|||
this.algNumSystem.solve(rough);
|
||||
}
|
||||
|
||||
reSolve() {
|
||||
this.prepare();
|
||||
this.solve(false);
|
||||
}
|
||||
|
||||
addModifier(modifier) {
|
||||
this.algNumSystem.addModifier(modifier);
|
||||
this.refresh();
|
||||
|
|
|
|||
|
|
@ -19,9 +19,9 @@ export class Arc extends SketchObject {
|
|||
c.parent = this;
|
||||
this.children.push(a, b, c);
|
||||
|
||||
this.r = new Param(MIN_RADIUS + 0.001, 'R');
|
||||
this.r = new Param(0, 'R');
|
||||
this.r.constraints = [greaterThanConstraint(MIN_RADIUS)];
|
||||
this.r.min = MIN_RADIUS;
|
||||
this.r.enforceVisualLimit = true;
|
||||
|
||||
this.ang1 = new Param(0, 'A');
|
||||
this.ang2 = new Param(0, 'A');
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ export class Circle extends SketchObject {
|
|||
this.c = c;
|
||||
c.parent = this;
|
||||
this.children.push(c);
|
||||
this.r = new Param(MIN_RADIUS + 0.001, 'R');
|
||||
this.r = new Param(0, 'R');
|
||||
this.r.constraints = [greaterThanConstraint(MIN_RADIUS)];
|
||||
this.r.min = MIN_RADIUS;
|
||||
this.r.enforceVisualLimit = true;
|
||||
}
|
||||
|
||||
visitParams(callback) {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export class Segment extends SketchObject {
|
|||
t: new Param(undefined, 'T')
|
||||
};
|
||||
this.params.ang.normalizer = makeAngle0_360;
|
||||
this.params.t.min = 100;
|
||||
this.params.t.enforceVisualLimit = true;
|
||||
this.syncGeometry();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@ export class SketchObject extends Shape {
|
|||
constructor() {
|
||||
super();
|
||||
this.id = Generator.genID();
|
||||
this.marked = null;
|
||||
this.markers = [];
|
||||
this.children = [];
|
||||
this.layer = null;
|
||||
this.fullyConstrained = false;
|
||||
this.constraints = new Set();
|
||||
this.readOnly = false;
|
||||
this.fullyConstrained = false;
|
||||
}
|
||||
|
||||
normalDistance(aim, scale) {
|
||||
|
|
@ -69,18 +69,32 @@ export class SketchObject extends Shape {
|
|||
});
|
||||
}
|
||||
|
||||
addMarker(style) {
|
||||
this.markers.push(style);
|
||||
this.markers.sort((a, b) => (a.priority||99999) - (b.priority||99999))
|
||||
}
|
||||
|
||||
removeMarker(style) {
|
||||
const index = this.markers.indexOf(style);
|
||||
if (index !== -1) {
|
||||
this.markers.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
get marked() {
|
||||
return this.markers.length !== 0;
|
||||
}
|
||||
|
||||
draw(ctx, scale, viewer) {
|
||||
if (!this.visible) return;
|
||||
if (this.marked != null) {
|
||||
const customStyle = this.markers.length !== 0 ? this.markers[0] : (this.fullyConstrained ? Styles.FULLY_CONSTRAINED : null);
|
||||
if (customStyle !== null) {
|
||||
ctx.save();
|
||||
viewer.setStyle(this.marked, ctx);
|
||||
} else if (this.fullyConstrained) {
|
||||
ctx.save();
|
||||
viewer.setStyle(Styles.FULLY_CONSTRAINED, ctx);
|
||||
viewer.setStyle(customStyle, ctx);
|
||||
}
|
||||
|
||||
this.drawImpl(ctx, scale, viewer);
|
||||
if (this.marked != null || this.fullyConstrained) ctx.restore();
|
||||
if (customStyle !== null) ctx.restore();
|
||||
}
|
||||
|
||||
copy() {
|
||||
|
|
|
|||
|
|
@ -15,13 +15,19 @@ export const Styles = {
|
|||
fillStyle : "#FF0000"
|
||||
},
|
||||
|
||||
MARK : {
|
||||
SELECTION : {
|
||||
lineWidth : 2,
|
||||
strokeStyle : "#ff0000",
|
||||
fillStyle : "#FF0000"
|
||||
},
|
||||
|
||||
SNAP : {
|
||||
HIGHLIGHT : {
|
||||
lineWidth : 2,
|
||||
strokeStyle : "#f1ff3a",
|
||||
fillStyle : "#f1ff3a"
|
||||
},
|
||||
|
||||
TOOL_HELPER : {
|
||||
lineWidth : 2,
|
||||
strokeStyle : "#00FF00",
|
||||
fillStyle : "#00FF00"
|
||||
|
|
|
|||
|
|
@ -105,7 +105,6 @@ export class FilletTool extends Tool {
|
|||
const point2 = candi[1];
|
||||
this.breakLinkAndMakeFillet(point1, point2)
|
||||
}
|
||||
|
||||
breakLinkAndMakeFillet(point1, point2) {
|
||||
const pm = this.viewer.parametricManager;
|
||||
let coi = null;
|
||||
|
|
@ -168,7 +167,7 @@ export class FilletTool extends Tool {
|
|||
}
|
||||
var candi = this.getCandidate(e);
|
||||
if (candi != null) {
|
||||
this.viewer.mark(candi[0], Styles.SNAP);
|
||||
this.lastCandidate.addStyle(Styles.SNAP);
|
||||
needRefresh = true;
|
||||
}
|
||||
if (needRefresh) {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import * as draw_utils from './shapes/draw-utils';
|
|||
import {Matrix3} from '../math/l3space';
|
||||
import sketcherStreams from './sketcherStreams';
|
||||
|
||||
|
||||
class Viewer {
|
||||
|
||||
constructor(canvas, IO) {
|
||||
|
|
@ -80,8 +79,10 @@ class Viewer {
|
|||
this.translate = {x: 0.0, y: 0.0};
|
||||
this.scale = 1.0;
|
||||
|
||||
this.selected = [];
|
||||
this.snapped = null;
|
||||
this.captured = {
|
||||
};
|
||||
Object.keys(CAPTURES).forEach(key => this.captured[key] = []);
|
||||
|
||||
|
||||
this.historyManager = new HistoryManager(this);
|
||||
this.transformation = null;
|
||||
|
|
@ -89,6 +90,14 @@ class Viewer {
|
|||
this.refresh();
|
||||
}
|
||||
|
||||
get selected() {
|
||||
return this.captured.selection;
|
||||
}
|
||||
|
||||
get snapped() {
|
||||
return this.captured.tool[0] || null;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
window.removeEventListener('resize', this.onWindowResize, false);
|
||||
this.canvas = null;
|
||||
|
|
@ -280,19 +289,15 @@ class Viewer {
|
|||
|
||||
snap(x, y, excl) {
|
||||
this.cleanSnap();
|
||||
var snapTo = this.search(x, y, 20 / this.scale, true, true, excl);
|
||||
const snapTo = this.search(x, y, 20 / this.scale, true, true, excl);
|
||||
if (snapTo.length > 0) {
|
||||
this.snapped = snapTo[0];
|
||||
this.mark(this.snapped, Styles.SNAP);
|
||||
this.capture('tool', [snapTo[0]], true);
|
||||
}
|
||||
return this.snapped;
|
||||
};
|
||||
|
||||
cleanSnap() {
|
||||
if (this.snapped != null) {
|
||||
this.deselect(this.snapped);
|
||||
this.snapped = null;
|
||||
}
|
||||
this.withdrawAll('tool')
|
||||
};
|
||||
|
||||
showBounds(x1, y1, x2, y2, offset) {
|
||||
|
|
@ -328,7 +333,7 @@ class Viewer {
|
|||
};
|
||||
|
||||
_screenToModel(x, y) {
|
||||
var out = {x: 0, y: 0};
|
||||
const out = {x: 0, y: 0};
|
||||
this.screenToModel2(x, y, out);
|
||||
return out;
|
||||
};
|
||||
|
|
@ -365,29 +370,68 @@ class Viewer {
|
|||
};
|
||||
|
||||
select(objs, exclusive) {
|
||||
if (exclusive) this.deselectAll();
|
||||
for (var i = 0; i < objs.length; i++) {
|
||||
this.mark(objs[i]);
|
||||
this.capture('selection', objs, exclusive);
|
||||
this.streams.selection.next(this.selected);
|
||||
}
|
||||
|
||||
capture(type, objs, exclusive) {
|
||||
if (exclusive) this.withdrawAll(type);
|
||||
const captured = this.captured[type];
|
||||
for (let i = 0; i < objs.length; i++) {
|
||||
const obj = objs[i];
|
||||
if (captured.indexOf(obj) === -1) {
|
||||
captured.push(obj);
|
||||
obj.addMarker(CAPTURES[type]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
withdraw(type, obj) {
|
||||
let captured = this.captured[type];
|
||||
for (let i = 0; i < captured.length; i++) {
|
||||
if (obj === captured[i]) {
|
||||
captured.splice(i, 1)[0].removeMarker(CAPTURES[type]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
withdrawAll(type) {
|
||||
const captured = this.captured[type];
|
||||
for (let i = 0; i < captured.length; i++) {
|
||||
captured[i].removeMarker(CAPTURES[type]);
|
||||
}
|
||||
while (captured.length > 0) captured.pop();
|
||||
};
|
||||
|
||||
deselect(obj) {
|
||||
this.withdraw('selection', obj);
|
||||
this.streams.selection.next(this.selected);
|
||||
};
|
||||
|
||||
deselectAll() {
|
||||
this.withdrawAll('selection');
|
||||
this.streams.selection.next(this.selected);
|
||||
};
|
||||
|
||||
highlight(objs, exclusive) {
|
||||
this.capture('highlight', objs, exclusive);
|
||||
}
|
||||
|
||||
unHighlightAll(objs) {
|
||||
this.withdrawAll('highlight');
|
||||
}
|
||||
|
||||
unHighlight(objs) {
|
||||
this.withdrawAll('highlight', objs);
|
||||
}
|
||||
|
||||
pick(e) {
|
||||
var m = this.screenToModel(e);
|
||||
return this.search(m.x, m.y, 20 / this.scale, true, false, []);
|
||||
};
|
||||
|
||||
mark(obj, style) {
|
||||
if (style === undefined) {
|
||||
style = Styles.MARK;
|
||||
}
|
||||
obj.marked = style;
|
||||
|
||||
if (this.selected.indexOf(obj) == -1) {
|
||||
this.selected.push(obj);
|
||||
this.streams.selection.next(this.selected);
|
||||
}
|
||||
};
|
||||
|
||||
getActiveLayer() {
|
||||
var layer = this._activeLayer;
|
||||
if (layer == null || layer.readOnly) {
|
||||
|
|
@ -423,24 +467,6 @@ class Viewer {
|
|||
}
|
||||
};
|
||||
|
||||
deselect(obj) {
|
||||
for (var i = 0; i < this.selected.length; i++) {
|
||||
if (obj === this.selected[i]) {
|
||||
this.selected.splice(i, 1)[0].marked = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.streams.selection.next(this.selected);
|
||||
};
|
||||
|
||||
deselectAll() {
|
||||
for (var i = 0; i < this.selected.length; i++) {
|
||||
this.selected[i].marked = null;
|
||||
}
|
||||
while (this.selected.length > 0) this.selected.pop();
|
||||
this.streams.selection.next(this.selected);
|
||||
};
|
||||
|
||||
equalizeLinkedEndpoints() {
|
||||
const visited = new Set();
|
||||
|
||||
|
|
@ -484,11 +510,11 @@ class Viewer {
|
|||
}
|
||||
|
||||
static __SKETCH_DRAW_PIPELINE = [
|
||||
(obj) => !isEndPoint(obj) && obj.marked === null && isConstruction(obj),
|
||||
(obj) => !isEndPoint(obj) && obj.marked === null && !isConstruction(obj),
|
||||
(obj) => !isEndPoint(obj) && obj.marked !== null,
|
||||
(obj) => isEndPoint(obj) && obj.marked === null,
|
||||
(obj) => isEndPoint(obj) && obj.marked !== null
|
||||
(obj) => !isEndPoint(obj) && !obj.marked && isConstruction(obj),
|
||||
(obj) => !isEndPoint(obj) && !obj.marked && !isConstruction(obj),
|
||||
(obj) => !isEndPoint(obj) && obj.marked,
|
||||
(obj) => isEndPoint(obj) && !obj.marked,
|
||||
(obj) => isEndPoint(obj) && obj.marked
|
||||
];
|
||||
|
||||
static __SIMPLE_DRAW_PIPELINE = [
|
||||
|
|
@ -547,4 +573,20 @@ class Layer {
|
|||
}
|
||||
}
|
||||
|
||||
const CAPTURES = {
|
||||
tool: {
|
||||
...Styles.TOOL_HELPER,
|
||||
priority: 1
|
||||
},
|
||||
highlight: {
|
||||
...Styles.HIGHLIGHT,
|
||||
priority: 2
|
||||
|
||||
},
|
||||
selection: {
|
||||
...Styles.SELECTION,
|
||||
priority: 3
|
||||
},
|
||||
};
|
||||
|
||||
export {Viewer, Styles}
|
||||
|
|
@ -1,6 +1,4 @@
|
|||
body {
|
||||
.sans-serif;
|
||||
font-size: 11px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
|
@ -11,6 +9,10 @@ html, body {
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
.sans-serif {
|
||||
|
||||
}
|
||||
|
||||
.helvetica {
|
||||
font-family: 'Helvetica Neue Light', HelveticaNeue-Light, 'Helvetica Neue', Helvetica, sans-serif;
|
||||
}
|
||||
|
|
@ -19,10 +21,6 @@ html, body {
|
|||
font-family: Monaco, monospace;
|
||||
}
|
||||
|
||||
.sans-serif {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.logo {
|
||||
color: #bbb;
|
||||
font-size: 16px;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Web CAD / Part Designer</title>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300italic,400,400italic,700,700italic&subset=latin,latin-ext">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto+Condensed:300,300italic,400,400italic,700,700italic&subset=latin,latin-ext">
|
||||
<link rel="stylesheet" href="lib/font-awesome/css/font-awesome.min.css?modeler">
|
||||
<link rel="shortcut icon" href="img/cad/cube96.png" />
|
||||
<script src="lib/pnltri.js"></script>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>sketcher.js</title>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300italic,400,400italic,700,700italic&subset=latin,latin-ext">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto+Condensed:300,300italic,400,400italic,700,700italic&subset=latin,latin-ext">
|
||||
<link rel="shortcut icon" href="img/tgn.png" />
|
||||
<link rel="stylesheet" href="css/toolkit.css">
|
||||
<link rel="stylesheet" href="lib/font-awesome/css/font-awesome.min.css">
|
||||
|
|
@ -70,7 +72,7 @@
|
|||
<div id="viewer-container" style="background: #808080; overflow: hidden; height: 100%; position: relative">
|
||||
<div class="tool-hint" style="position: absolute; bottom: 5px; right: 5px;"></div>
|
||||
<canvas width="300" height="300" id="viewer"></canvas>
|
||||
<div id="react-controls" style="position: absolute; top: 0; right: 0; bottom: 0;"></div>
|
||||
<div id="react-controls" style="position: absolute; top: 0; right: 0; left: 0; height: 0;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -69,20 +69,33 @@ module.exports = {
|
|||
]
|
||||
},
|
||||
{
|
||||
test: /\.(less|css)$/,
|
||||
include: [MODULES, WEB_APP],
|
||||
use: [
|
||||
'style-loader',
|
||||
oneOf: [
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
getLocalIdent: (context, localIdentName, localName) => generateCSSScopedName(localName, context.resourcePath),
|
||||
modules: true,
|
||||
url: false
|
||||
}
|
||||
test: /\.(less|css)$/,
|
||||
include: [path.resolve(MODULES, 'ui/styles/init')],
|
||||
use: [
|
||||
'style-loader',
|
||||
'css-loader',
|
||||
'less-loader'
|
||||
]
|
||||
},
|
||||
'less-loader'
|
||||
]
|
||||
{
|
||||
test: /\.(less|css)$/,
|
||||
include: [MODULES, WEB_APP],
|
||||
use: [
|
||||
'style-loader',
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
getLocalIdent: (context, localIdentName, localName) => generateCSSScopedName(localName, context.resourcePath),
|
||||
modules: true,
|
||||
url: false
|
||||
}
|
||||
},
|
||||
'less-loader'
|
||||
]
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.html$/,
|
||||
|
|
|
|||
Loading…
Reference in a new issue