mirror of
https://github.com/xibyte/jsketcher
synced 2025-12-06 08:25:19 +01:00
angular measure tool
This commit is contained in:
parent
b86933b32a
commit
c082c13ed5
17 changed files with 653 additions and 64 deletions
|
|
@ -1,6 +1,8 @@
|
|||
import Vector from 'math/vector';
|
||||
import BBox from './bbox'
|
||||
import * as vec from './vec';
|
||||
import {perp2d} from "./vec";
|
||||
import {eqTol} from "../brep/geom/tolerance";
|
||||
|
||||
export const TOLERANCE = 1E-6;
|
||||
export const TOLERANCE_SQ = TOLERANCE * TOLERANCE;
|
||||
|
|
@ -284,6 +286,20 @@ export function pointToLineSignedDistance(ax, ay, bx, by, px, py) {
|
|||
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 sq = (a) => a * a;
|
||||
|
|
|
|||
|
|
@ -103,6 +103,10 @@ export function __normalize(v, out) {
|
|||
return __div(v, mag, out)
|
||||
}
|
||||
|
||||
export function cross2d(v1, v2) {
|
||||
return v1[0] * v2[1] - v1[1] * v2[0];
|
||||
}
|
||||
|
||||
export function _normalize(v) {
|
||||
return __normalize(v, v);
|
||||
}
|
||||
|
|
@ -155,6 +159,20 @@ export function distance(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) {
|
||||
let a = ccwSequence[0];
|
||||
let b = ccwSequence[1];
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
import {MirrorGeneratorIcon} from "../icons/generators/GeneratorIcons";
|
||||
import {MirrorGeneratorSchema} from "../generators/mirrorGenerator";
|
||||
import {AddCircleDimTool, AddFreeDimTool, AddHorizontalDimTool, AddVerticalDimTool} from "../tools/dim";
|
||||
import {
|
||||
AddAngleBetweenDimTool,
|
||||
AddCircleDimTool,
|
||||
AddFreeDimTool,
|
||||
AddHorizontalDimTool,
|
||||
AddVerticalDimTool
|
||||
} from "../tools/dim";
|
||||
import {
|
||||
MeasureAngleBetweenAngle,
|
||||
MeasureCircleToolIcon,
|
||||
MeasureFreeToolIcon,
|
||||
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));
|
||||
}
|
||||
|
||||
},
|
||||
];
|
||||
|
|
@ -10,7 +10,6 @@ import Field from "ui/components/controls/Field";
|
|||
import Label from "../../../../modules/ui/components/controls/Label";
|
||||
import {SketcherAppContext} from "./SketcherApp";
|
||||
import {EMPTY_OBJECT} from "../../../../modules/gems/objects";
|
||||
import identity from 'lodash';
|
||||
|
||||
export function ConstraintEditor() {
|
||||
|
||||
|
|
@ -19,7 +18,7 @@ export function ConstraintEditor() {
|
|||
const [values, setValues] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
setValues(req && {...req.constraint.constants})
|
||||
setValues(req && {...req.constraint.constants});
|
||||
return () => {
|
||||
if (req) {
|
||||
viewer.unHighlight(req.constraint.objects);
|
||||
|
|
@ -72,10 +71,10 @@ export function ConstraintEditor() {
|
|||
.sort().map(name => {
|
||||
const def = constraint.schema.constants[name];
|
||||
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 onChange = value => setValue(name, (presentation.transformIn||transformInByType(type))(value));
|
||||
|
||||
const val = (presentation.transformOut||transformOutByType(type))(values[name]);
|
||||
return <Field key={presentation.label||name}>
|
||||
<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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -964,6 +964,8 @@ export class AlgNumConstraint {
|
|||
val = expressionResolver(val);
|
||||
if (def.type === 'number') {
|
||||
val = parseFloat(val);
|
||||
} else if (def.type === 'boolean') {
|
||||
val = val === 'true' || val === true;
|
||||
}
|
||||
if (def.transform) {
|
||||
val = def.transform(val);
|
||||
|
|
@ -996,9 +998,9 @@ export class AlgNumConstraint {
|
|||
this.constantKeys.map(name => {
|
||||
let val = this.schema.constants[name].initialValue(this.objects);
|
||||
if (typeof val === 'number') {
|
||||
val = val.toFixed(2) + '';
|
||||
val = val.toFixed(2);
|
||||
}
|
||||
this.updateConstant(name, val);
|
||||
this.updateConstant(name, val + '');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import measureCircleContent from './measure-circle-tool.svg';
|
|||
import measureFreeContent from './measure-free-tool.svg';
|
||||
import measureHorizontalContent from './measure-horizontal-tool.svg';
|
||||
import measureVerticalContent from './measure-vertical-tool.svg';
|
||||
import measureAngleBetweenContent from './measure-angle-between.svg';
|
||||
import rectangleContent from './rectangle-tool.svg';
|
||||
import bezierContent from './bezier-tool.svg';
|
||||
import ellipseContent from './ellipse-tool.svg';
|
||||
|
|
@ -34,6 +35,11 @@ export function MeasureVerticalToolIcon(props) {
|
|||
return <SvgIcon content={measureVerticalContent} {...props}/>
|
||||
}
|
||||
|
||||
export function MeasureAngleBetweenAngle(props) {
|
||||
|
||||
return <SvgIcon content={measureAngleBetweenContent} {...props}/>
|
||||
}
|
||||
|
||||
export function RectangleToolIcon(props) {
|
||||
|
||||
return <SvgIcon content={rectangleContent} {...props}/>
|
||||
|
|
|
|||
26
web/app/sketcher/icons/tools/measure-angle-between.svg
Normal file
26
web/app/sketcher/icons/tools/measure-angle-between.svg
Normal 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 |
|
|
@ -8,7 +8,7 @@ import {Circle} from './shapes/circle'
|
|||
import {Ellipse} from './shapes/ellipse'
|
||||
import {EllipticalArc} from './shapes/elliptical-arc'
|
||||
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 Vector from 'math/vector';
|
||||
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);
|
||||
return layer;
|
||||
}
|
||||
const version = sketch.version || 1;
|
||||
var T = Types;
|
||||
var maxEdge = 0;
|
||||
var sketchLayers = sketch.layers;
|
||||
var boundary = sketch.boundary;
|
||||
var boundaryNeedsUpdate = !(boundary === undefined || boundary == null);
|
||||
let T = Types;
|
||||
let maxEdge = 0;
|
||||
let sketchLayers = sketch.layers;
|
||||
let boundary = sketch.boundary;
|
||||
let boundaryNeedsUpdate = !(boundary === undefined || boundary == null);
|
||||
const dimensions = [];
|
||||
if (sketchLayers !== undefined) {
|
||||
for (var l = 0; l < sketchLayers.length; ++l) {
|
||||
var ioLayer = sketchLayers[l];
|
||||
var layerName = ioLayer.name;
|
||||
var boundaryProcessing = layerName === IO.BOUNDARY_LAYER_NAME && boundaryNeedsUpdate;
|
||||
var layer = getLayer(this.viewer, layerName);
|
||||
for (let l = 0; l < sketchLayers.length; ++l) {
|
||||
let ioLayer = sketchLayers[l];
|
||||
let layerName = ioLayer.name;
|
||||
let boundaryProcessing = layerName === IO.BOUNDARY_LAYER_NAME && boundaryNeedsUpdate;
|
||||
let layer = getLayer(this.viewer, layerName);
|
||||
// if (!!ioLayer.style) layer.style = ioLayer.style;
|
||||
layer.readOnly = !!ioLayer.readOnly;
|
||||
var layerData = ioLayer.data;
|
||||
let layerData = ioLayer.data;
|
||||
for (let i = 0; i < layerData.length; ++i) {
|
||||
var obj = layerData[i];
|
||||
var skobj = null;
|
||||
var _class = obj._class;
|
||||
var aux = !!obj.aux;
|
||||
var role = obj.role;
|
||||
let obj = layerData[i];
|
||||
let skobj = null;
|
||||
let _class = obj._class;
|
||||
let aux = !!obj.aux;
|
||||
let role = obj.role;
|
||||
|
||||
//support legacy format
|
||||
if (!role) {
|
||||
|
|
@ -150,17 +151,8 @@ IO.prototype._loadSketch = function(sketch) {
|
|||
const cp1 = endPoint(obj.cp1);
|
||||
const cp2 = endPoint(obj.cp2);
|
||||
skobj = new BezierCurve(a, b, cp1, cp2);
|
||||
} else if (_class === T.HDIM) {
|
||||
skobj = new HDimension(obj.a, obj.b);
|
||||
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);
|
||||
} else {
|
||||
dimensions.push(obj);
|
||||
}
|
||||
if (skobj != null) {
|
||||
skobj.role = role;
|
||||
|
|
@ -192,14 +184,27 @@ IO.prototype._loadSketch = function(sketch) {
|
|||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.viewer.dimLayer.objects.length; ++i) {
|
||||
obj = this.viewer.dimLayer.objects[i];
|
||||
if (obj._class === T.DIM || obj._class === T.HDIM || obj._class === T.VDIM) {
|
||||
obj.a = index[obj.a];
|
||||
obj.b = index[obj.b];
|
||||
} else if (obj._class === T.DDIM) {
|
||||
obj.obj = index[obj.obj];
|
||||
for (let obj of dimensions) {
|
||||
let _class = obj._class;
|
||||
let skobj = null;
|
||||
if (_class === T.HDIM) {
|
||||
skobj = new HDimension(index[obj.a], index[obj.b]);
|
||||
obj.offset !== undefined && (skobj.offset = obj.offset);
|
||||
} 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) {
|
||||
|
|
@ -391,6 +396,11 @@ IO.prototype._serializeSketch = function(metadata) {
|
|||
to.offset = obj.offset;
|
||||
} else if (obj._class === T.DDIM) {
|
||||
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);
|
||||
if (children.length !== 0) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
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 {SketchObject} from './sketch-object'
|
||||
import {Styles} from "../styles";
|
||||
|
|
@ -12,6 +13,8 @@ const ARROW_H_PX = 4;
|
|||
const ARROW_TO_TEXT_PAD_PX = 2;
|
||||
const TEXT_H_OFFSET = 3;
|
||||
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) {
|
||||
ctx.beginPath();
|
||||
|
|
@ -21,6 +24,14 @@ function drawArrow(ctx, x, y, nx, ny, arrowW, arrowH) {
|
|||
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) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x + ny * arrowW, y + -nx * arrowW);
|
||||
|
|
@ -119,7 +130,7 @@ class LinearDimension extends SketchObject {
|
|||
function drawRef(start, x, y) {
|
||||
var vec = new Vector(x - start.x, y - start.y);
|
||||
vec._normalize();
|
||||
vec._multiply(7 * unscale);
|
||||
vec._multiply(EXT_LINEAR_WIDTH_PX * unscale);
|
||||
|
||||
ctx.moveTo(start.x, start.y );
|
||||
ctx.lineTo(x, y);
|
||||
|
|
@ -375,9 +386,337 @@ export class DiameterDimension extends SketchObject {
|
|||
}
|
||||
DiameterDimension.prototype._class = 'TCAD.TWO.DiameterDimension';
|
||||
|
||||
export class AngleBetweenDimension extends SketchObject {
|
||||
|
||||
function getTextOff(scale) {
|
||||
return 3 * scale;
|
||||
constructor(a, b) {
|
||||
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';
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -10,5 +10,6 @@ export const SketchTypes = {
|
|||
DIM : 'TCAD.TWO.Dimension',
|
||||
HDIM : 'TCAD.TWO.HDimension',
|
||||
VDIM : 'TCAD.TWO.VDimension',
|
||||
DDIM : 'TCAD.TWO.DiameterDimension'
|
||||
DDIM : 'TCAD.TWO.DiameterDimension',
|
||||
ANGLE_BW : 'TCAD.TWO.AngleBetweenDimension'
|
||||
};
|
||||
|
|
@ -15,7 +15,7 @@ export class TextHelper {
|
|||
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();
|
||||
let rot = makeAngle0_360(Math.atan2(-nx, ny));
|
||||
|
|
@ -31,10 +31,12 @@ export class TextHelper {
|
|||
let dty = [modelTextHeight * nx, ny * modelTextHeight];
|
||||
|
||||
if (flip) {
|
||||
const dx = ny * modelTextWidth - nx * 2 *flipOffset;
|
||||
const dy = -nx * modelTextWidth - ny * 2*flipOffset;
|
||||
tx += dx;
|
||||
ty += dy;
|
||||
tx += ny * modelTextWidth;
|
||||
ty += -nx * modelTextWidth;
|
||||
const k = staticFlip ? 1.5 : -1;
|
||||
tx += k * nx * 2 *flipOffset;
|
||||
ty += k * ny * 2 *flipOffset;
|
||||
|
||||
_negate(dtx);
|
||||
_negate(dty);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,11 +50,11 @@ export class EditCircleTool extends Tool {
|
|||
this.circle = new Circle(
|
||||
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.sendHint('specify radius');
|
||||
this.viewer.activeLayer.add(this.circle);
|
||||
this.viewer.parametricManager.prepare([this.circle]);
|
||||
if (needSnap) this.viewer.parametricManager.coincidePoints(this.circle.c, p);
|
||||
this.viewer.refresh();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {EndPoint} from '../shapes/point'
|
||||
import {Tool} from './tool'
|
||||
|
|
@ -6,6 +6,8 @@ import {DragTool} from "./drag";
|
|||
import {isInstanceOf} from "../actions/matchUtils";
|
||||
import {Segment} from "../shapes/segment";
|
||||
import {DEFAULT_SEARCH_BUFFER} from "../viewer2d";
|
||||
import {distance} from "../../math/math";
|
||||
import {_negate, cross2d} from "../../math/vec";
|
||||
|
||||
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';
|
||||
});
|
||||
|
||||
if (objects.length != 0) {
|
||||
if (objects.length !== 0) {
|
||||
this.dim.obj = objects[0];
|
||||
this.viewer.capture('tool', [this.dim.obj], true);
|
||||
} else {
|
||||
this.dim.obj = null;
|
||||
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.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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ export class DragTool extends Tool {
|
|||
this.obj.translate(dx, dy);
|
||||
}
|
||||
// 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.solve(true);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import {Ellipse} from '../shapes/ellipse'
|
||||
import {EllipticalArc} from '../shapes/elliptical-arc'
|
||||
import {Circle} from '../shapes/circle'
|
||||
import {EditCircleTool} from './circle'
|
||||
import {DragTool} from './drag'
|
||||
import {EllipseTool, STATE_RADIUS} from './ellipse'
|
||||
import {AngleBetweenDimension} from "../shapes/dim";
|
||||
import {AddAngleBetweenDimTool} from "./dim";
|
||||
|
||||
export function GetShapeEditTool(viewer, obj, alternative) {
|
||||
if (obj instanceof Circle && !alternative) {
|
||||
|
|
@ -18,6 +19,11 @@ export function GetShapeEditTool(viewer, obj, alternative) {
|
|||
tool.ellipse = obj;
|
||||
tool.state = STATE_RADIUS;
|
||||
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 {
|
||||
return new DragTool(obj, viewer);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ export class Tool {
|
|||
}
|
||||
|
||||
static dumbMode(e) {
|
||||
return e.ctrlKey || e.metaKey;
|
||||
return e.ctrlKey || e.metaKey || e.altKey;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -240,6 +240,9 @@ class Viewer {
|
|||
|
||||
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._serviceWorkspace, Viewer.__SIMPLE_DRAW_PIPELINE);
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue