object highlight mode, reuse styles

This commit is contained in:
Val Erastov (xibyte) 2020-03-09 23:46:16 -07:00
parent 279db19809
commit 547ec02b01
39 changed files with 409 additions and 243 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,7 @@
.root {
display: flex;
justify-content: flex-end;
& > * {
& > *:not(:first-child) {
margin-left: 5px;
}
}

View file

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

View file

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

View file

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

View file

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

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

View 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";

View file

@ -0,0 +1,7 @@
a {
color: @font-color;
text-decoration: underline;
&:hover {
color: @color-text-highlight
}
}

View file

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

View file

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

View file

@ -26,5 +26,7 @@
@color-text-highlight: #9cdaf7;
@font-size: 11px;
//@work-area-toolbar-bg-color: ;
//@work-area-toolbar-font-color: ;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,5 +1,8 @@
.root {
position: absolute;
top: 0;
right: 0;
margin: 5px;
padding: 3px 5px;
background-color: #000D;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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$/,