angular measure tool

This commit is contained in:
Val Erastov (xibyte) 2020-04-02 02:14:33 -07:00
parent b86933b32a
commit c082c13ed5
17 changed files with 653 additions and 64 deletions

View file

@ -1,6 +1,8 @@
import Vector from 'math/vector'; import Vector from 'math/vector';
import BBox from './bbox' import BBox from './bbox'
import * as vec from './vec'; import * as vec from './vec';
import {perp2d} from "./vec";
import {eqTol} from "../brep/geom/tolerance";
export const TOLERANCE = 1E-6; export const TOLERANCE = 1E-6;
export const TOLERANCE_SQ = TOLERANCE * TOLERANCE; export const TOLERANCE_SQ = TOLERANCE * TOLERANCE;
@ -284,6 +286,20 @@ export function pointToLineSignedDistance(ax, ay, bx, by, px, py) {
return vx * nx + vy * ny; return vx * nx + vy * ny;
} }
export function lineLineIntersection2d(p1, p2, v1, v2) {
// const n1 = perp2d(v1);
const n2 = perp2d(v2);
const cos = vec.dot(n2, v1);
if (eqTol(cos, 0)) {
return null;
}
const u1 = vec.dot(n2, vec.sub(p2, p1)) / cos;
// const u2 = vec.dot(n1, vec.sub(p1, p2)) / vec.dot(n1, v2);
return [p1[0] + v1[0] * u1, p1[1] + v1[1] * u1];
}
export const DEG_RAD = Math.PI / 180.0; export const DEG_RAD = Math.PI / 180.0;
export const sq = (a) => a * a; export const sq = (a) => a * a;

View file

@ -103,6 +103,10 @@ export function __normalize(v, out) {
return __div(v, mag, out) return __div(v, mag, out)
} }
export function cross2d(v1, v2) {
return v1[0] * v2[1] - v1[1] * v2[0];
}
export function _normalize(v) { export function _normalize(v) {
return __normalize(v, v); return __normalize(v, v);
} }
@ -155,6 +159,20 @@ export function distance(v1, v2) {
return Math.sqrt(distanceSq(v1, v2)); return Math.sqrt(distanceSq(v1, v2));
} }
export function perp2d(v) {
return __perp2d(v, []);
}
export function _perp2d(v) {
return __perp2d(v, v);
}
export function __perp2d([x, y], out) {
out[0] = -y;
out[1] = x;
return out;
}
export function normal3(ccwSequence) { export function normal3(ccwSequence) {
let a = ccwSequence[0]; let a = ccwSequence[0];
let b = ccwSequence[1]; let b = ccwSequence[1];

View file

@ -1,7 +1,12 @@
import {MirrorGeneratorIcon} from "../icons/generators/GeneratorIcons";
import {MirrorGeneratorSchema} from "../generators/mirrorGenerator";
import {AddCircleDimTool, AddFreeDimTool, AddHorizontalDimTool, AddVerticalDimTool} from "../tools/dim";
import { import {
AddAngleBetweenDimTool,
AddCircleDimTool,
AddFreeDimTool,
AddHorizontalDimTool,
AddVerticalDimTool
} from "../tools/dim";
import {
MeasureAngleBetweenAngle,
MeasureCircleToolIcon, MeasureCircleToolIcon,
MeasureFreeToolIcon, MeasureFreeToolIcon,
MeasureHorizontalToolIcon, MeasureHorizontalToolIcon,
@ -62,4 +67,17 @@ export default [
} }
}, },
{
id: 'MeasureAngleBetween',
shortName: 'Measure Angle Between',
kind: 'Tool',
description: 'Measure angle between',
icon: MeasureAngleBetweenAngle,
invoke: (ctx) => {
ctx.viewer.toolManager.takeControl(new AddAngleBetweenDimTool(ctx.viewer, ctx.viewer.dimLayer));
}
},
]; ];

View file

@ -10,7 +10,6 @@ import Field from "ui/components/controls/Field";
import Label from "../../../../modules/ui/components/controls/Label"; import Label from "../../../../modules/ui/components/controls/Label";
import {SketcherAppContext} from "./SketcherApp"; import {SketcherAppContext} from "./SketcherApp";
import {EMPTY_OBJECT} from "../../../../modules/gems/objects"; import {EMPTY_OBJECT} from "../../../../modules/gems/objects";
import identity from 'lodash';
export function ConstraintEditor() { export function ConstraintEditor() {
@ -19,7 +18,7 @@ export function ConstraintEditor() {
const [values, setValues] = useState(null); const [values, setValues] = useState(null);
useEffect(() => { useEffect(() => {
setValues(req && {...req.constraint.constants}) setValues(req && {...req.constraint.constants});
return () => { return () => {
if (req) { if (req) {
viewer.unHighlight(req.constraint.objects); viewer.unHighlight(req.constraint.objects);
@ -72,10 +71,10 @@ export function ConstraintEditor() {
.sort().map(name => { .sort().map(name => {
const def = constraint.schema.constants[name]; const def = constraint.schema.constants[name];
const presentation = def.presentation || EMPTY_OBJECT; const presentation = def.presentation || EMPTY_OBJECT;
const onChange = value => setValue(name, (presentation.transformIn||identity)(value));
const val = (presentation.transformOut||identity)(values[name]);
const type = presentation.type || def.type; const type = presentation.type || def.type;
const onChange = value => setValue(name, (presentation.transformIn||transformInByType(type))(value));
const val = (presentation.transformOut||transformOutByType(type))(values[name]);
return <Field key={presentation.label||name}> return <Field key={presentation.label||name}>
<Label>{name}</Label> <Label>{name}</Label>
{ {
@ -114,3 +113,20 @@ export function editConstraint(rqStream, constraint, onApply) {
} }
}); });
} }
const NO_TR = v => v;
function transformInByType(type) {
if ('boolean' === type) {
return value => value ? 'true' : 'false';
}
return NO_TR;
}
function transformOutByType(type) {
if ('boolean' === type) {
return value => value === 'true';
}
return NO_TR;
}

View file

@ -964,6 +964,8 @@ export class AlgNumConstraint {
val = expressionResolver(val); val = expressionResolver(val);
if (def.type === 'number') { if (def.type === 'number') {
val = parseFloat(val); val = parseFloat(val);
} else if (def.type === 'boolean') {
val = val === 'true' || val === true;
} }
if (def.transform) { if (def.transform) {
val = def.transform(val); val = def.transform(val);
@ -996,9 +998,9 @@ export class AlgNumConstraint {
this.constantKeys.map(name => { this.constantKeys.map(name => {
let val = this.schema.constants[name].initialValue(this.objects); let val = this.schema.constants[name].initialValue(this.objects);
if (typeof val === 'number') { if (typeof val === 'number') {
val = val.toFixed(2) + ''; val = val.toFixed(2);
} }
this.updateConstant(name, val); this.updateConstant(name, val + '');
}); });
} }
} }

View file

@ -4,6 +4,7 @@ import measureCircleContent from './measure-circle-tool.svg';
import measureFreeContent from './measure-free-tool.svg'; import measureFreeContent from './measure-free-tool.svg';
import measureHorizontalContent from './measure-horizontal-tool.svg'; import measureHorizontalContent from './measure-horizontal-tool.svg';
import measureVerticalContent from './measure-vertical-tool.svg'; import measureVerticalContent from './measure-vertical-tool.svg';
import measureAngleBetweenContent from './measure-angle-between.svg';
import rectangleContent from './rectangle-tool.svg'; import rectangleContent from './rectangle-tool.svg';
import bezierContent from './bezier-tool.svg'; import bezierContent from './bezier-tool.svg';
import ellipseContent from './ellipse-tool.svg'; import ellipseContent from './ellipse-tool.svg';
@ -34,6 +35,11 @@ export function MeasureVerticalToolIcon(props) {
return <SvgIcon content={measureVerticalContent} {...props}/> return <SvgIcon content={measureVerticalContent} {...props}/>
} }
export function MeasureAngleBetweenAngle(props) {
return <SvgIcon content={measureAngleBetweenContent} {...props}/>
}
export function RectangleToolIcon(props) { export function RectangleToolIcon(props) {
return <SvgIcon content={rectangleContent} {...props}/> return <SvgIcon content={rectangleContent} {...props}/>

View file

@ -0,0 +1,26 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="64"
height="64"
viewBox="0 0 64 64"
stroke="black"
fill="gray">
<g>
<path
class="annotation"
fill="none"
d="M 19 26 A 25 25 0 0 1 40 45"
/>
<polygon class="annotation arrow" points="0,0 -7,-4 -7,4"
transform="translate(19 26) rotate(195 0 0)" />
<polygon class="annotation arrow" points="0,0 -7,-4 -7,4"
transform="translate(40 45) rotate(70 0 0)" />
<path class="line" d="M 10 55 L 23 10" />
<path class="line" d="M 10 55 L 55 40" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 583 B

View file

@ -8,7 +8,7 @@ import {Circle} from './shapes/circle'
import {Ellipse} from './shapes/ellipse' import {Ellipse} from './shapes/ellipse'
import {EllipticalArc} from './shapes/elliptical-arc' import {EllipticalArc} from './shapes/elliptical-arc'
import {BezierCurve} from './shapes/bezier-curve' import {BezierCurve} from './shapes/bezier-curve'
import {DiameterDimension, Dimension, HDimension, VDimension} from './shapes/dim' import {AngleBetweenDimension, DiameterDimension, Dimension, HDimension, VDimension} from './shapes/dim'
import {Constraints} from './parametric' import {Constraints} from './parametric'
import Vector from 'math/vector'; import Vector from 'math/vector';
import exportTextData from 'gems/exportTextData'; import exportTextData from 'gems/exportTextData';
@ -78,31 +78,32 @@ IO.prototype._loadSketch = function(sketch) {
} }
} }
} }
var layer = viewer.createLayer(name, Styles.DEFAULT); let layer = viewer.createLayer(name, Styles.DEFAULT);
viewer.layers.push(layer); viewer.layers.push(layer);
return layer; return layer;
} }
const version = sketch.version || 1; const version = sketch.version || 1;
var T = Types; let T = Types;
var maxEdge = 0; let maxEdge = 0;
var sketchLayers = sketch.layers; let sketchLayers = sketch.layers;
var boundary = sketch.boundary; let boundary = sketch.boundary;
var boundaryNeedsUpdate = !(boundary === undefined || boundary == null); let boundaryNeedsUpdate = !(boundary === undefined || boundary == null);
const dimensions = [];
if (sketchLayers !== undefined) { if (sketchLayers !== undefined) {
for (var l = 0; l < sketchLayers.length; ++l) { for (let l = 0; l < sketchLayers.length; ++l) {
var ioLayer = sketchLayers[l]; let ioLayer = sketchLayers[l];
var layerName = ioLayer.name; let layerName = ioLayer.name;
var boundaryProcessing = layerName === IO.BOUNDARY_LAYER_NAME && boundaryNeedsUpdate; let boundaryProcessing = layerName === IO.BOUNDARY_LAYER_NAME && boundaryNeedsUpdate;
var layer = getLayer(this.viewer, layerName); let layer = getLayer(this.viewer, layerName);
// if (!!ioLayer.style) layer.style = ioLayer.style; // if (!!ioLayer.style) layer.style = ioLayer.style;
layer.readOnly = !!ioLayer.readOnly; layer.readOnly = !!ioLayer.readOnly;
var layerData = ioLayer.data; let layerData = ioLayer.data;
for (let i = 0; i < layerData.length; ++i) { for (let i = 0; i < layerData.length; ++i) {
var obj = layerData[i]; let obj = layerData[i];
var skobj = null; let skobj = null;
var _class = obj._class; let _class = obj._class;
var aux = !!obj.aux; let aux = !!obj.aux;
var role = obj.role; let role = obj.role;
//support legacy format //support legacy format
if (!role) { if (!role) {
@ -150,17 +151,8 @@ IO.prototype._loadSketch = function(sketch) {
const cp1 = endPoint(obj.cp1); const cp1 = endPoint(obj.cp1);
const cp2 = endPoint(obj.cp2); const cp2 = endPoint(obj.cp2);
skobj = new BezierCurve(a, b, cp1, cp2); skobj = new BezierCurve(a, b, cp1, cp2);
} else if (_class === T.HDIM) { } else {
skobj = new HDimension(obj.a, obj.b); dimensions.push(obj);
obj.offset !== undefined && (skobj.offset = obj.offset);
} else if (_class === T.VDIM) {
skobj = new VDimension(obj.a, obj.b);
obj.offset !== undefined && (skobj.offset = obj.offset);
} else if (_class === T.DIM) {
skobj = new Dimension(obj.a, obj.b);
obj.offset !== undefined && (skobj.offset = obj.offset);
} else if (_class === T.DDIM) {
skobj = new DiameterDimension(obj.obj);
} }
if (skobj != null) { if (skobj != null) {
skobj.role = role; skobj.role = role;
@ -192,14 +184,27 @@ IO.prototype._loadSketch = function(sketch) {
} }
} }
for (let i = 0; i < this.viewer.dimLayer.objects.length; ++i) { for (let obj of dimensions) {
obj = this.viewer.dimLayer.objects[i]; let _class = obj._class;
if (obj._class === T.DIM || obj._class === T.HDIM || obj._class === T.VDIM) { let skobj = null;
obj.a = index[obj.a]; if (_class === T.HDIM) {
obj.b = index[obj.b]; skobj = new HDimension(index[obj.a], index[obj.b]);
} else if (obj._class === T.DDIM) { obj.offset !== undefined && (skobj.offset = obj.offset);
obj.obj = index[obj.obj]; } else if (_class === T.VDIM) {
skobj = new VDimension(index[obj.a], index[obj.b]);
obj.offset !== undefined && (skobj.offset = obj.offset);
} else if (_class === T.DIM) {
skobj = new Dimension(index[obj.a], index[obj.b]);
obj.offset !== undefined && (skobj.offset = obj.offset);
} else if (_class === T.DDIM) {
skobj = new DiameterDimension(index[obj.obj]);
skobj.angle = obj.angle;
} else if (_class === T.ANGLE_BW) {
skobj = new AngleBetweenDimension(index[obj.a], index[obj.b]);
skobj.offset = obj.offset;
} }
this.viewer.dimLayer.add(skobj);
index[obj.id] = skobj;
} }
if (boundaryNeedsUpdate) { if (boundaryNeedsUpdate) {
@ -391,6 +396,11 @@ IO.prototype._serializeSketch = function(metadata) {
to.offset = obj.offset; to.offset = obj.offset;
} else if (obj._class === T.DDIM) { } else if (obj._class === T.DDIM) {
to.obj = obj.obj.id; to.obj = obj.obj.id;
to.angle = obj.obj.angle;
} else if (obj._class === T.ANGLE_BW) {
to.a = obj.a.id;
to.b = obj.b.id;
to.offset = obj.offset;
} }
const children = nonPointChildren(obj).map(c => c.id); const children = nonPointChildren(obj).map(c => c.id);
if (children.length !== 0) { if (children.length !== 0) {

View file

@ -1,5 +1,6 @@
import * as math from '../../math/math' import * as math from '../../math/math'
import {pointToLineSignedDistance} from '../../math/math' import * as vec from '../../math/vec'
import {DEG_RAD, lineLineIntersection2d, makeAngle0_360, pointToLineSignedDistance} from '../../math/math'
import Vector from 'math/vector'; import Vector from 'math/vector';
import {SketchObject} from './sketch-object' import {SketchObject} from './sketch-object'
import {Styles} from "../styles"; import {Styles} from "../styles";
@ -12,6 +13,8 @@ const ARROW_H_PX = 4;
const ARROW_TO_TEXT_PAD_PX = 2; const ARROW_TO_TEXT_PAD_PX = 2;
const TEXT_H_OFFSET = 3; const TEXT_H_OFFSET = 3;
const OUTER_ARROW_TO_TEXT_PAD_PX = 6; const OUTER_ARROW_TO_TEXT_PAD_PX = 6;
const EXT_LINEAR_WIDTH_PX = 7;
const EXT_ANGULAR_WIDTH_PX = 10;
function drawArrow(ctx, x, y, nx, ny, arrowW, arrowH) { function drawArrow(ctx, x, y, nx, ny, arrowW, arrowH) {
ctx.beginPath(); ctx.beginPath();
@ -21,6 +24,14 @@ function drawArrow(ctx, x, y, nx, ny, arrowW, arrowH) {
ctx.fill(); ctx.fill();
} }
function drawArrowForArc(ctx, px, py, x, y, nx, ny, arrowW, arrowH) {
ctx.beginPath();
ctx.moveTo(x, y, 0);
ctx.lineTo(px + nx * arrowH, py + ny * arrowH );
ctx.lineTo(px - nx * arrowH, py - ny * arrowH);
ctx.fill();
}
function drawExtensionLine(ctx, x, y, nx, ny, width, tip, arrowW) { function drawExtensionLine(ctx, x, y, nx, ny, width, tip, arrowW) {
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(x + ny * arrowW, y + -nx * arrowW); ctx.moveTo(x + ny * arrowW, y + -nx * arrowW);
@ -119,7 +130,7 @@ class LinearDimension extends SketchObject {
function drawRef(start, x, y) { function drawRef(start, x, y) {
var vec = new Vector(x - start.x, y - start.y); var vec = new Vector(x - start.x, y - start.y);
vec._normalize(); vec._normalize();
vec._multiply(7 * unscale); vec._multiply(EXT_LINEAR_WIDTH_PX * unscale);
ctx.moveTo(start.x, start.y ); ctx.moveTo(start.x, start.y );
ctx.lineTo(x, y); ctx.lineTo(x, y);
@ -375,9 +386,337 @@ export class DiameterDimension extends SketchObject {
} }
DiameterDimension.prototype._class = 'TCAD.TWO.DiameterDimension'; DiameterDimension.prototype._class = 'TCAD.TWO.DiameterDimension';
export class AngleBetweenDimension extends SketchObject {
function getTextOff(scale) { constructor(a, b) {
return 3 * scale; super();
this.a = a;
this.b = b;
this.offset = 20;
this.pickA = [];
this.pickB = [];
this.textHelper = new TextHelper();
this.configuration = [this.a.a, this.a.b, this.b.a, this.b.b];
this.pickInfo = [];
} }
visitParams(callback) {
}
getReferencePoint() {
return this.a;
}
translateImpl(dx, dy) {
const [_ax, _ay] = this.pickA;
const [_bx, _by] = this.pickB;
let _vx = - (_by - _ay);
let _vy = _bx - _ax;
const d = math.distance(_ax, _ay, _bx, _by);
//normalize
let _vxn = _vx / d;
let _vyn = _vy / d;
this.offset += (dx * _vxn + dy * _vyn);
}
drawImpl(ctx, scale, viewer) {
const marked = this.markers.length !== 0;
if (marked) {
ctx.save();
viewer.setStyle(Styles.HIGHLIGHT, ctx);
}
this.drawDimension(ctx, scale, viewer)
if (marked) {
ctx.restore();
}
}
drawDimension(ctx, scale, viewer) {
const unscale = viewer.unscale;
let off = this.offset;
const MIN_OFFSET_PX = 20;
if (off * scale < MIN_OFFSET_PX) {
off = MIN_OFFSET_PX * unscale;
}
const textOff = unscale * TEXT_H_OFFSET;
let [aa, ab, ba, bb] = this.configuration;
let aAng = makeAngle0_360(Math.atan2(ab.y - aa.y, ab.x - aa.x));
let bAng = makeAngle0_360(Math.atan2(bb.y - ba.y, bb.x - ba.x));
let ang = makeAngle0_360(bAng - aAng);
if (ang > Math.PI) {
this.configuration.reverse();
[aa, ab, ba, bb] = this.configuration;
aAng = makeAngle0_360(Math.atan2(ab.y - aa.y, ab.x - aa.x));
bAng = makeAngle0_360(Math.atan2(bb.y - ba.y, bb.x - ba.x));
ang = makeAngle0_360(bAng - aAng);
}
// this.a.syncGeometry();
// this.b.syncGeometry && this.b.syncGeometry();
let avx = Math.cos(aAng);
let avy = Math.sin(aAng);
let bvx = Math.cos(bAng);
let bvy = Math.sin(bAng);
this.center = findCenter(aa, ab, ba, bb, avx, avy, bvx, bvy);
if (!this.center) {
return;
}
const [cx, cy] = this.center;
// if (distanceSquared(aa.x, aa.y, cx, cy) > distanceSquared(ab.x, ab.y, cx, cy)) {
// aAng = makeAngle0_360(aAng + Math.PI);
// avx *= -1;
// avy *= -1;
// }
// if (distanceSquared(ba.x, ba.y, cx, cy) > distanceSquared(bb.x, bb.y, cx, cy)) {
// bAng = makeAngle0_360(bAng + Math.PI);
// bvx *= -1;
// bvy *= -1;
//
// }
const halfAng = 0.5 * ang;
let _ax = cx + off * avx;
let _ay = cy + off * avy;
let _bx = cx + off * bvx;
let _by = cy + off * bvy;
const _vxn = Math.cos(aAng + halfAng);
const _vyn = Math.sin(aAng + halfAng);
const mx = cx + off * _vxn;
const my = cy + off * _vyn;
const arrowWpx = ARROW_W_PX;
const arrowW = arrowWpx * unscale;
const arrowH = ARROW_H_PX * unscale;
const txt = (1 / DEG_RAD * ang).toFixed(2) + '°';
this.textHelper.prepare(txt, ctx, viewer);
let sinPhi = arrowW / off;
const cosPhi = Math.sqrt(1 - sinPhi * sinPhi);
if (cosPhi !== cosPhi) {
return;
}
let arrLxV = avx * cosPhi - avy * sinPhi;
let arrLyV = avx * sinPhi + avy * cosPhi;
let arrLx = cx + off*(arrLxV);
let arrLy = cy + off*(arrLyV);
sinPhi *= -1;
let arrRxV = bvx * cosPhi - bvy * sinPhi;
let arrRyV = bvx * sinPhi + bvy * cosPhi;
let arrRx = cx + off*(arrRxV);
let arrRy = cy + off*(arrRyV);
const availableArea = math.distance(arrLx, arrLy, arrRx, arrRy);
const modelTextWidth = this.textHelper.modelTextWidth;
const innerMode = modelTextWidth <= availableArea;
let tx, ty;
if (innerMode) {
ctx.beginPath();
ctx.arc(cx, cy, off, Math.atan2(arrLyV, arrLxV), Math.atan2(arrRyV, arrRxV));
ctx.stroke();
drawArrowForArc(ctx, arrLx, arrLy, _ax, _ay, -arrLxV, -arrLyV, arrowW, arrowH);
drawArrowForArc(ctx, arrRx, arrRy, _bx, _by, arrRxV, arrRyV, arrowW, arrowH);
const h = modelTextWidth/2;
tx = (mx + _vxn * textOff) + (- _vyn) * h;
ty = (my + _vyn * textOff) + ( _vxn) * h;
this.textHelper.draw(tx, ty, _vxn, _vyn, ctx, unscale, viewer, textOff, true);
} else {
ctx.beginPath();
ctx.arc(cx, cy, off, aAng, bAng);
ctx.stroke();
//sin is inverted by this time
arrLxV = avx * cosPhi - avy * sinPhi;
arrLyV = avx * sinPhi + avy * cosPhi;
arrLx = cx + off*(arrLxV);
arrLy = cy + off*(arrLyV);
sinPhi *= -1;
arrRxV = bvx * cosPhi - bvy * sinPhi;
arrRyV = bvx * sinPhi + bvy * cosPhi;
arrRx = cx + off*(arrRxV);
arrRy = cy + off*(arrRyV);
drawArrowForArc(ctx, arrLx, arrLy, _ax, _ay, -arrLxV, -arrLyV, arrowW, arrowH);
drawArrowForArc(ctx, arrRx, arrRy, _bx, _by, arrRxV, arrRyV, arrowW, arrowH);
const longExt = modelTextWidth + 2 * OUTER_ARROW_TO_TEXT_PAD_PX * unscale;
const shortExt = OUTER_ARROW_TO_TEXT_PAD_PX * unscale;
ctx.beginPath();
ctx.moveTo(arrLx, arrLy);
ctx.lineTo(arrLx + arrLyV * longExt, arrLy - arrLxV * longExt);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(arrRx, arrRy);
ctx.lineTo(arrRx - arrRyV * shortExt, arrRy + arrRxV * shortExt);
ctx.stroke();
tx = arrLx - ( -arrLyV) * OUTER_ARROW_TO_TEXT_PAD_PX * unscale + arrLxV * textOff;
ty = arrLy - ( arrLxV) * OUTER_ARROW_TO_TEXT_PAD_PX * unscale + arrLyV * textOff;
this.textHelper.draw(tx, ty, arrLxV, arrLyV, ctx, unscale, viewer, textOff, true);
}
this.setPickInfo(cx, cy, _ax, _ay, _bx, _by, off);
function drawRef(a, b, px, py, vx, vy) {
const abx = b.x - a.x;
const aby = b.y - a.y;
const apx = px - a.x;
const apy = py - a.y;
const dot = abx * apx + aby * apy;
if (dot < 0) {
ctx.save();
viewer.setStyle(Styles.CONSTRUCTION, ctx);
ctx.beginPath();
ctx.moveTo(a.x, a.y);
ctx.lineTo(px - vx * EXT_ANGULAR_WIDTH_PX * unscale, py - vy * EXT_ANGULAR_WIDTH_PX * unscale);
ctx.stroke();
ctx.restore();
} else if (apx * apx + apy * apy > abx * abx + aby * aby) {
ctx.save();
viewer.setStyle(Styles.CONSTRUCTION, ctx);
ctx.beginPath();
ctx.moveTo(b.x, b.y);
ctx.lineTo(px + vx * EXT_ANGULAR_WIDTH_PX * unscale, py + vy * EXT_ANGULAR_WIDTH_PX * unscale);
ctx.stroke();
ctx.restore();
}
}
drawRef(aa, ab, _ax, _ay, avx, avy);
drawRef(ba, bb, _bx, _by, bvx, bvy);
}
setPickInfo(cx, cy, ax, ay, bx, by, rad) {
for (let i = 0; i < arguments.length; ++i) {
this.pickInfo[i] = arguments[i];
}
}
normalDistance(aim, scale) {
const textDist = this.textHelper.normalDistance(aim);
if (textDist !== -1) {
return textDist;
}
if (this.pickInfo.length === 0) {
return;
}
const [cx, cy, ax, ay, bx, by, rad] = this.pickInfo;
function isPointInsideSector(x, y) {
const ca = [ax - cx, ay - cy];
const cb = [bx - cx, by - cy];
const ct = [x - cx, y - cy];
vec._normalize(ca);
vec._normalize(cb);
vec._normalize(ct);
const cosAB = vec.dot(ca, cb);
const cosAT = vec.dot(ca, ct);
const isInside = cosAT >= cosAB;
const abInverse = vec.cross2d(ca, cb) < 0;
const atInverse = vec.cross2d(ca, ct) < 0;
let result;
if (abInverse) {
result = !atInverse || !isInside;
} else {
result = !atInverse && isInside;
}
return result;
}
const isInsideSector = isPointInsideSector(aim.x, aim.y);
if (isInsideSector) {
return Math.abs(math.distance(aim.x, aim.y, cx, cy) - rad);
} else {
return Math.min(
math.distance(aim.x, aim.y, ax, ay),
math.distance(aim.x, aim.y, bx, by)
);
}
}
}
export function findCenter(aa, ab, ba, bb, avx, avy, bvx, bvy) {
let center = lineLineIntersection2d([aa.x, aa.y], [ba.x, ba.y], [avx, avy], [bvx, bvy]);
if (!center) {
let commonPt = null;
aa.visitLinked(p => {
if (ba === p || bb === p) {
commonPt = aa;
}
});
if (!commonPt) {
ab.visitLinked(p => {
if (ba === p || bb === p) {
commonPt = ab;
}
});
}
if (!commonPt) {
return null;
}
center = commonPt.toVectorArray();
}
return center;
}
AngleBetweenDimension.prototype._class = 'TCAD.TWO.AngleBetweenDimension';

View file

@ -10,5 +10,6 @@ export const SketchTypes = {
DIM : 'TCAD.TWO.Dimension', DIM : 'TCAD.TWO.Dimension',
HDIM : 'TCAD.TWO.HDimension', HDIM : 'TCAD.TWO.HDimension',
VDIM : 'TCAD.TWO.VDimension', VDIM : 'TCAD.TWO.VDimension',
DDIM : 'TCAD.TWO.DiameterDimension' DDIM : 'TCAD.TWO.DiameterDimension',
ANGLE_BW : 'TCAD.TWO.AngleBetweenDimension'
}; };

View file

@ -15,7 +15,7 @@ export class TextHelper {
this.txt = txt; this.txt = txt;
} }
draw(tx, ty, nx, ny, ctx, unscale, viewer, flipOffset) { draw(tx, ty, nx, ny, ctx, unscale, viewer, flipOffset, staticFlip = false) {
ctx.save(); ctx.save();
let rot = makeAngle0_360(Math.atan2(-nx, ny)); let rot = makeAngle0_360(Math.atan2(-nx, ny));
@ -31,10 +31,12 @@ export class TextHelper {
let dty = [modelTextHeight * nx, ny * modelTextHeight]; let dty = [modelTextHeight * nx, ny * modelTextHeight];
if (flip) { if (flip) {
const dx = ny * modelTextWidth - nx * 2 *flipOffset; tx += ny * modelTextWidth;
const dy = -nx * modelTextWidth - ny * 2*flipOffset; ty += -nx * modelTextWidth;
tx += dx; const k = staticFlip ? 1.5 : -1;
ty += dy; tx += k * nx * 2 *flipOffset;
ty += k * ny * 2 *flipOffset;
_negate(dtx); _negate(dtx);
_negate(dty); _negate(dty);
} }

View file

@ -50,11 +50,11 @@ export class EditCircleTool extends Tool {
this.circle = new Circle( this.circle = new Circle(
new EndPoint(p.x, p.y) new EndPoint(p.x, p.y)
); );
if (needSnap) this.viewer.parametricManager.coincidePoints(this.circle.c, p);
this.pointPicked(this.circle.c.x, this.circle.c.y); this.pointPicked(this.circle.c.x, this.circle.c.y);
this.sendHint('specify radius'); this.sendHint('specify radius');
this.viewer.activeLayer.add(this.circle); this.viewer.activeLayer.add(this.circle);
this.viewer.parametricManager.prepare([this.circle]); this.viewer.parametricManager.prepare([this.circle]);
if (needSnap) this.viewer.parametricManager.coincidePoints(this.circle.c, p);
this.viewer.refresh(); this.viewer.refresh();
} }

View file

@ -1,4 +1,4 @@
import {HDimension, VDimension, Dimension, DiameterDimension} from '../shapes/dim' import {AngleBetweenDimension, DiameterDimension, Dimension, findCenter, HDimension, VDimension,} from '../shapes/dim'
import Vector from 'math/vector'; import Vector from 'math/vector';
import {EndPoint} from '../shapes/point' import {EndPoint} from '../shapes/point'
import {Tool} from './tool' import {Tool} from './tool'
@ -6,6 +6,8 @@ import {DragTool} from "./drag";
import {isInstanceOf} from "../actions/matchUtils"; import {isInstanceOf} from "../actions/matchUtils";
import {Segment} from "../shapes/segment"; import {Segment} from "../shapes/segment";
import {DEFAULT_SEARCH_BUFFER} from "../viewer2d"; import {DEFAULT_SEARCH_BUFFER} from "../viewer2d";
import {distance} from "../../math/math";
import {_negate, cross2d} from "../../math/vec";
export class AddDimTool extends Tool { export class AddDimTool extends Tool {
@ -97,14 +99,14 @@ export class AddCircleDimTool extends Tool {
return o._class === 'TCAD.TWO.Circle' || o._class === 'TCAD.TWO.Arc'; return o._class === 'TCAD.TWO.Circle' || o._class === 'TCAD.TWO.Arc';
}); });
if (objects.length != 0) { if (objects.length !== 0) {
this.dim.obj = objects[0]; this.dim.obj = objects[0];
this.viewer.capture('tool', [this.dim.obj], true); this.viewer.capture('tool', [this.dim.obj], true);
} else { } else {
this.dim.obj = null; this.dim.obj = null;
this.viewer.withdrawAll('tool'); this.viewer.withdrawAll('tool');
} }
if (this.dim.obj != null) { if (this.dim.obj !== null) {
this.dim.angle = Math.atan2(p.y - this.dim.obj.c.y, p.x - this.dim.obj.c.x); this.dim.angle = Math.atan2(p.y - this.dim.obj.c.y, p.x - this.dim.obj.c.x);
} }
this.viewer.refresh(); this.viewer.refresh();
@ -122,3 +124,127 @@ export class AddCircleDimTool extends Tool {
} }
} }
export class AddAngleTool extends Tool {
constructor(name, viewer, layer, dimCreation) {
super(name, viewer);
this.layer = layer;
this.a = null;
this.dim = null;
this.dimCreation = dimCreation;
}
mousemove(e) {
const p = this.viewer.screenToModel(e);
const result = this.viewer.search(p.x, p.y, DEFAULT_SEARCH_BUFFER, true, false, []).filter(o => o._class === 'TCAD.TWO.Segment');
const [segment] = result;
if (this.dim) {
let [center, configuration] = this.classify(p.x, p.y);
if (configuration) {
this.dim.configuration = configuration;
}
if (!center) {
center = segment.a;
}
const [cx, cy] = center;
this.dim.offset = distance(cx, cy, p.x, p.y);
} else {
if (segment) {
this.viewer.capture('tool', [segment], true);
} else {
this.viewer.withdrawAll('tool');
}
}
this.viewer.refresh();
}
mouseup(e) {
const p = this.viewer.screenToModel(e);
const [segment] = this.viewer.captured.tool;
this.viewer.withdrawAll('tool');
if (this.a === null) {
if (!segment) {
this.viewer.toolManager.releaseControl();
this.viewer.refresh();
}
this.a = segment;
} else if (this.dim == null) {
if (!segment) {
this.viewer.toolManager.releaseControl();
this.viewer.refresh();
}
this.dim = this.dimCreation(this.a, segment);
let [center, configuration] = this.classify(p.x, p.y);
if (configuration) {
this.dim.configuration = configuration;
}
if (!center) {
center = segment.a;
}
const [cx, cy] = center;
this.dim.offset = distance(cx, cy, p.x, p.y);
this.layer.add(this.dim);
this.viewer.refresh();
} else {
this.viewer.toolManager.releaseControl();
this.viewer.refresh();
}
}
classify(px, py) {
const line1 = this.dim.a, line2 = this.dim.b;
const v1 = [line1.ny, - line1.nx];
const v2 = [line2.ny, - line2.nx];
const isec = findCenter(line1.a, line1.b, line2.a, line2.b, v1[0], v1[1], v2[0], v2[1]);
if (!isec) {
return [];
}
const [cx, cy] = isec;
const v = [px - cx, py - cy];
const insideSector = (v, v1, v2) => cross2d(v1, v) > 0 && cross2d(v2, v) < 0;
if (insideSector(v, v1, v2)) {
return [isec, [line1.a, line1.b, line2.a, line2.b]];
}
if (insideSector(v, v2, _negate(v1))) {
return [isec, [line2.a, line2.b, line1.b, line1.a]];
}
_negate(v1);
if (insideSector(v, _negate(v1), _negate(v2))) {
return [isec, [line1.b, line1.a, line2.b, line2.a]];
}
_negate(v1);
_negate(v2);
if (insideSector(v, _negate(v2), v1)) {
return [isec, [line2.b, line2.a, line1.a, line1.b]];
}
return [isec];
}
}
export class AddAngleBetweenDimTool extends AddAngleTool {
constructor(viewer, layer) {
super('angle between dimension', viewer, layer, (a, b) => new AngleBetweenDimension(a, b));
}
}

View file

@ -25,7 +25,7 @@ export class DragTool extends Tool {
this.obj.translate(dx, dy); this.obj.translate(dx, dy);
} }
// this.viewer.parametricManager.setConstantsFromGeometry(this.obj); // this.viewer.parametricManager.setConstantsFromGeometry(this.obj);
if (!Tool.dumbMode(e) || this.obj.constraints.length !== 0) { if (!Tool.dumbMode(e) && this.obj.constraints.length !== 0) {
// this.viewer.parametricManager.prepare(); // this.viewer.parametricManager.prepare();
this.viewer.parametricManager.solve(true); this.viewer.parametricManager.solve(true);
} }

View file

@ -1,9 +1,10 @@
import {Ellipse} from '../shapes/ellipse' import {Ellipse} from '../shapes/ellipse'
import {EllipticalArc} from '../shapes/elliptical-arc'
import {Circle} from '../shapes/circle' import {Circle} from '../shapes/circle'
import {EditCircleTool} from './circle' import {EditCircleTool} from './circle'
import {DragTool} from './drag' import {DragTool} from './drag'
import {EllipseTool, STATE_RADIUS} from './ellipse' import {EllipseTool, STATE_RADIUS} from './ellipse'
import {AngleBetweenDimension} from "../shapes/dim";
import {AddAngleBetweenDimTool} from "./dim";
export function GetShapeEditTool(viewer, obj, alternative) { export function GetShapeEditTool(viewer, obj, alternative) {
if (obj instanceof Circle && !alternative) { if (obj instanceof Circle && !alternative) {
@ -18,6 +19,11 @@ export function GetShapeEditTool(viewer, obj, alternative) {
tool.ellipse = obj; tool.ellipse = obj;
tool.state = STATE_RADIUS; tool.state = STATE_RADIUS;
return tool; return tool;
} else if (obj instanceof AngleBetweenDimension && !alternative) {
const tool = new AddAngleBetweenDimTool(viewer, viewer.dimLayer);
tool.a = obj.a;
tool.dim = obj;
return tool;
} else { } else {
return new DragTool(obj, viewer); return new DragTool(obj, viewer);
} }

View file

@ -64,7 +64,7 @@ export class Tool {
} }
static dumbMode(e) { static dumbMode(e) {
return e.ctrlKey || e.metaKey; return e.ctrlKey || e.metaKey || e.altKey;
} }
} }

View file

@ -240,6 +240,9 @@ class Viewer {
this.__prevStyle = null; this.__prevStyle = null;
this.interactiveScale = this.scale / this.retinaPxielRatio;
this.unscale = 1 / this.interactiveScale;
this.__drawWorkspace(ctx, this._workspace, Viewer.__SKETCH_DRAW_PIPELINE); this.__drawWorkspace(ctx, this._workspace, Viewer.__SKETCH_DRAW_PIPELINE);
this.__drawWorkspace(ctx, this._serviceWorkspace, Viewer.__SIMPLE_DRAW_PIPELINE); this.__drawWorkspace(ctx, this._serviceWorkspace, Viewer.__SIMPLE_DRAW_PIPELINE);
}; };