/* * Copyright (c) 2011 by RibbonSoft, GmbH. All rights reserved. * * This file is part of the QCAD project. * * Licensees holding valid QCAD Professional Edition licenses * may use this file in accordance with the QCAD License * Agreement provided with the Software. * * This file is provided AS IS with NO WARRANTY OF ANY KIND, * INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE. * * See http://www.ribbonsoft.com for further details. */ include("../Polyline.js"); /** * \class PolylineEquidistant * \brief Create equidistant polyline. * -# User chooses existing polyline. * -# Equidistant polyline is created. * \ingroup ecma_draw_polyline */ function PolylineEquidistant(guiAction) { Polyline.call(this, guiAction); this.polylineEntity = undefined; this.side = undefined; this.number = undefined; this.distance = undefined; this.useArcsAtCorners = undefined; this.setUiOptions("PolylineEquidistant.ui"); } PolylineEquidistant.prototype = new Polyline(); PolylineEquidistant.State = { ChoosingPolyline : 0 }; PolylineEquidistant.prototype.beginEvent = function() { Polyline.prototype.beginEvent.call(this); this.setState(PolylineEquidistant.State.ChoosingPolyline); }; PolylineEquidistant.prototype.setState = function(state) { Polyline.prototype.setState.call(this, state); this.setCrosshairCursor(); var appWin = RMainWindowQt.getMainWindow(); switch (this.state) { case PolylineEquidistant.State.ChoosingPolyline: this.getDocumentInterface().setClickMode(RAction.PickEntity); var trChoosePolyline = qsTr("Choose polyline"); this.setCommandPrompt(trChoosePolyline); this.setLeftMouseTip(trChoosePolyline); this.setRightMouseTip(EAction.trCancel); this.polylineEntity = undefined; break; } }; PolylineEquidistant.prototype.escapeEvent = function() { var di = this.getDocumentInterface(); switch (this.state) { case PolylineEquidistant.State.ChoosingPolyline: EAction.prototype.escapeEvent.call(this); break; } }; PolylineEquidistant.prototype.pickEntity = function(event, preview) { var di = this.getDocumentInterface(); var doc = this.getDocument(); var entityId = event.getEntityId(); var entity = doc.queryEntity(entityId); var pos = event.getModelPosition(); // no polyline entity next to cursor, return immediately: if (isNull(entity) || !isPolylineEntity(entity)) { if (!preview) { EAction.warnNotPolyline(); } this.polylineEntity = undefined; this.updatePreview(); return; } switch (this.state) { case PolylineEquidistant.State.ChoosingPolyline: this.polylineEntity = entity; var index = this.getClosestSegment(pos); if (!isNumber(index)) { this.polylineEntity = undefined; break; } var closestSegment = entity.getSegmentAt(index); if (!isLineShape(closestSegment) && !isArcShape(closestSegment)) { this.polylineEntity = undefined; if (!preview) { EAction.handleUserWarning(qsTr("Invalid segment.")); } break; } this.side = closestSegment.getSideOfPoint(pos); //this.pos = pos; if (preview) { this.updatePreview(); } else { this.applyOperation(); } break; } }; PolylineEquidistant.prototype.getOperation = function(preview) { if (isNull(this.polylineEntity) || !isNumber(this.number) || !isNumber(this.distance) || !isBoolean(this.useArcsAtCorners)) { return undefined; } var eq = PolylineEquidistant.getEquidistants( this.polylineEntity, this.number, this.distance, this.side, this.useArcsAtCorners); var op = new RAddObjectsOperation(); for (var i=0; iRSettings.getPreviewEntities()) { break; } } return op; }; PolylineEquidistant.prototype.getHighlightedEntities = function() { var ret = new Array(); if (isEntity(this.polylineEntity)) { ret.push(this.polylineEntity.getId()); } return ret; }; PolylineEquidistant.prototype.slotDistanceChanged = function(value) { this.distance = value; }; PolylineEquidistant.prototype.slotNumberChanged = function(value) { this.number = value; }; PolylineEquidistant.prototype.slotRoundCornersChanged = function(value) { this.useArcsAtCorners = value; }; /** * Creates a polyline that is equidistant to another polyline (offset). * * \param polylineEntity RPolylineEntity Base polyline entity. * \param number Number of equidistans to create. * \param distance Distance between base and first equidistant. * \param side RS.RightHand or RS.LeftHand * \param useArcsAtCorners Boolean true: round corners / false: sharp corners. */ PolylineEquidistant.getEquidistants = function(polylineEntity, number, distance, side, useArcsAtCorners) { var ni, vs, bulge; var shapes = polylineEntity.getShapes(); if (shapes.length!==1) { return undefined; } var polyline = shapes[0]; var startPoint = polyline.getStartPoint(); var endPoint = polyline.getEndPoint(); var isClosed = polyline.isClosed() || startPoint.getDistanceTo(endPoint)0 && isClosed) { firstIntersection = vs[0]; equidistant.appendVertex(firstIntersection); } else { equidistant.appendVertex(para[0].getStartPoint()); } first = false; lastPara = undefined; } } // extend equidistant... if (!isNull(para[1])) { var vs1 = para[1].getIntersectionPoints(para[0], true); var vs2 = []; if (!isNull(para[2]) && !isTriangle) { vs2 = para[2].getIntersectionPoints(para[0], true); } var closest1 = RVector.invalid; var closest2 = RVector.invalid; if (vs1.length!==0) { closest1 = equidistant.getEndPoint().getClosest(vs1); } if (vs2.length!==0) { closest2 = equidistant.getEndPoint().getClosest(vs2); } vs = new Array(); if (isValidVector(closest1)) { vs.push(closest1); } if (isValidVector(closest2)) { vs.push(closest2); } // ... to next intersection: if (vs.length>0) { var closestIdx = -1; var dist1 = undefined; var dist2 = undefined; if (isValidVector(closest1)) { dist1 = equidistant.getEndPoint().getDistanceTo(closest1); } if (isValidVector(closest2)) { dist2 = equidistant.getEndPoint().getDistanceTo(closest2); } var is = undefined; if (isNull(dist2) || dist1 RS.PointTolerance) { bulge = 0.0; if (isArcShape(para[0])) { para[0].setStartAngle(para[0].getCenter().getAngleTo(equidistant.getEndPoint())); para[0].setEndAngle(para[0].getCenter().getAngleTo(is)); bulge = para[0].getBulge(); } PolylineEquidistant.extendEquidistant(equidistant, is, bulge, isCorner && !useArcsAtCorners); } } // ... to end of entity and around corner (arc): else { bulge = 0.0; if (isArcShape(para[0])) { para[0].setStartAngle(para[0].getCenter().getAngleTo(equidistant.getEndPoint())); bulge = para[0].getBulge(); } PolylineEquidistant.extendEquidistant(equidistant, para[0].getEndPoint(), bulge, isCorner && !useArcsAtCorners); } } // para[1] is NULL, end equidistant: else { bulge = 0.0; if (isArcShape(para[0])) { para[0].setStartAngle(para[0].getCenter().getAngleTo(equidistant.getEndPoint())); if (isClosed && isValidVector(firstIntersection)) { para[0].setEndAngle(para[0].getCenter().getAngleTo(firstIntersection)); } bulge = para[0].getBulge(); } if (isClosed && isValidVector(firstIntersection)) { var lastVertex = firstIntersection; if (!useArcsAtCorners) { lastVertex = equidistant.getStartPoint(); } PolylineEquidistant.extendEquidistant(equidistant, lastVertex, bulge, isCorner && !useArcsAtCorners); } else { this.extendEquidistant(equidistant, para[0].getEndPoint(), bulge, isCorner && !useArcsAtCorners); } } } if (!dropped) { ori[0] = ori[1]; } else { ori[0] = ori[2]; index++; } } while(!isNull(ori[0])); ret.push(equidistant); } // TODO: cut equidistant where it intersects with itself return ret; }; /** * Prepares the given polyline segments for creating an equidistant. Arcs segments * with zero radius are added at the corners. These segments allow an easy * expansion for equidistant polylines. */ PolylineEquidistant.preparePolylineForEquidistant = function(segments, isClosed) { if (isNull(segments) || segments.length===0) { return undefined; } var ret = new Array(); var last; if (isClosed) { last = segments[segments.length-1]; } for (var i=0; i 1.0e-12) { var p = s.getPointsWithDistanceToEnd(s.getLength()/100.0, RS.FromStart)[0]; var side = last.getSideOfPoint(p); var rev; var deltaAngle; if (side===RS.RightHand) { deltaAngle = Math.PI/2; rev=true; } else { deltaAngle = -Math.PI/2; rev=false; } var a = new RArc(s.getStartPoint(), 0.0, RMath.getNormalizedAngle(lastDir+deltaAngle), RMath.getNormalizedAngle(thisDir+deltaAngle), rev); ret.push(a); } } last = s; ret.push(s); } return ret; }; /** * Finds the next entity in the given polyline that has a valid * parallel with the given parameters. * * \param segments Array of segments (RLine and RArc) of the original polyline. * \param side Side of equidistant (RS.LeftHand or RS.RightHand) * \param distance Distance of equidistant. * \param reveresed Look in reverse order. * * \return Index of next segment that can have a parallel. */ PolylineEquidistant.findSegmentWithParallel = function( segments, startIndex, side, distance, reversed) { if (isNull(segments) || segments.length==0 || startIndex<0 || startIndex>segments.length-1) { return undefined; } var ret; var para; var done = false; var i = startIndex; do { para = PolylineEquidistant.createEquidistantParallel( segments[i], side, distance); if (!isNull(para)) { ret = i; done = true; } if (!done) { if (!reversed) { i++; } else { i--; } } } while(!done && i>=0 && i1.0e6) { return; } if (!sharpCornerInsteadOfArc) { ed.appendVertex(pos); ed.setBulgeAt(ed.countVertices()-2, bulge); } else { // if we were to create an arc around the corner, it would look like this: var arc = RArc.createFrom2PBulge(ed.getEndPoint(), pos, bulge); // direction at endpoints (tangents): var direction1 = arc.getDirection1(); var direction2 = arc.getDirection2(); var vDirection1 = RVector.createPolar(arc.getRadius()*2, direction1); var vDirection2 = RVector.createPolar(arc.getRadius()*2, direction2); // create tangent lines: var line1 = new RLine(ed.getEndPoint(), ed.getEndPoint().operator_add(vDirection1)); var line2 = new RLine(pos, pos.operator_add(vDirection2)); // intersection of tangents: var sp = line1.getIntersectionPoints(line2, false); if (sp.length>0) { var lastSegment = ed.getSegmentAt(ed.countSegments()-1); if (!isNull(lastSegment) && isLineShape(lastSegment)) { ed.removeLastVertex(); } ed.appendVertex(sp[0]); } else { ed.appendVertex(pos); ed.setBulgeAt(ed.countVertices()-2, bulge); } } // for debugging: //this.getDocumentInterface().applyOperation(new RAddObjectOperation(new RPolylineEntity(this.getDocument(), new RPolylineData(ed)))); }; PolylineEquidistant.init = function(basePath) { var action = new RGuiAction(qsTr("O&ffset"), RMainWindowQt.getMainWindow()); action.setRequiresDocument(true); action.setScriptFile(basePath + "/PolylineEquidistant.js"); action.setIcon(basePath + "/PolylineEquidistant.svg"); action.setStatusTip(qsTr("Draw polyline offset")); action.setDefaultShortcut(new QKeySequence("o,q")); action.setDefaultCommands(["polylineequidistant", "polylineoffset", "oq"]); action.setSortOrder(800); EAction.addGuiActionTo(action, Polyline, true, true, true); };