PolylineBreakSymbol.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. /*
  2. * Copyright (c) 2021 by Pete Vickerstaff. With a LOT of help from CVH.
  3. * Based on ExLineDove.js/LineDove.js by Matthias Melcher. All rights reserved.
  4. * Also based on BreakOutManual.js by Andrew Mustun. All rights reserved.
  5. * Both still are or were part of the QCAD project.
  6. *
  7. * This file is a free extension of the QCAD project.
  8. * Permission to evaluate, copy, modify, and/or distribute this software
  9. * for any purpose with or without fee is hereby granted, provided
  10. * that the above copyright notice and this permission notice appear
  11. * in all copies.
  12. *
  13. * QCAD is free software: you can redistribute it and/or modify
  14. * it under the terms of the GNU General Public License as published by
  15. * the Free Software Foundation, either version 3 of the License, or
  16. * (at your option) any later version.
  17. *
  18. * QCAD is distributed in the hope that it will be useful,
  19. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  20. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  21. * GNU General Public License for more details.
  22. *
  23. * You should have received a copy of the GNU General Public License
  24. * along with QCAD.
  25. UPDATES
  26. v0 - 11/03/2021 - First upload
  27. v1 - 15/03/2021 - Inherit checkbox added in UI, toggles inherit properties from chosen entity.
  28. Spin box replaced by a drop down combo box in UI.
  29. Peak Height changed to Peak Ratio. breakHeight vars now breakRatio.
  30. Radio buttons replaced with Toolbar buttons in UI.
  31. Added status line comments for toolbar items in UI.
  32. v2 - 22/04/2021 - All lines 158 to 163 - .entity was .shape
  33. */
  34. // Base Class
  35. include("scripts/EAction.js");
  36. // ShapeAlgorithms.autoSplitManual for removing segment
  37. include("scripts/ShapeAlgorithms.js");
  38. /**
  39. * \class PolylineBreakSymbol
  40. * \brief break symbol between two points.
  41. * \ingroup ecma_misc_draw
  42. */
  43. function PolylineBreakSymbol(guiAction) {
  44. EAction.call(this, guiAction);
  45. // Default settings matching the UI:
  46. this.inherit = true; // inherit properties from chosen entity
  47. this.removeSegment = true; // Remove segment by Default
  48. this.breakRatio = 3; // Default height
  49. this.inclinedSegment = true; // Default first/last segment
  50. // Entity/Shape:
  51. this.entity = undefined; // Selected entity
  52. this.shape = undefined; // Shape derived from entity
  53. // Coordinates:
  54. this.point1 = undefined; // First pick-point
  55. this.point2 = undefined; // Second pick-point
  56. this.cutPoint1 = undefined; // First cut point on shape
  57. this.cutPoint2 = undefined; // Second cut point on shape
  58. // Toolbar widget:
  59. this.setUiOptions("PolylineBreakSymbol.ui");
  60. }
  61. PolylineBreakSymbol.prototype = new EAction();
  62. PolylineBreakSymbol.State = {
  63. SettingShape : 0,
  64. SettingStartpt : 1,
  65. SettingEndpt : 2
  66. };
  67. PolylineBreakSymbol.prototype.beginEvent = function() {
  68. EAction.prototype.beginEvent.call(this);
  69. this.setState(PolylineBreakSymbol.State.SettingShape);
  70. };
  71. PolylineBreakSymbol.prototype.escapeEvent = function() {
  72. switch (this.state) {
  73. case PolylineBreakSymbol.State.SettingShape:
  74. EAction.prototype.escapeEvent.call(this);
  75. return; // Stop action
  76. case PolylineBreakSymbol.State.SettingStartpt:
  77. this.setState(PolylineBreakSymbol.State.SettingShape);
  78. break; // Step back to SettingShape
  79. case PolylineBreakSymbol.State.SettingEndpt:
  80. this.setState(PolylineBreakSymbol.State.SettingStartpt);
  81. break; // Step back to SettingStartpt
  82. }
  83. };
  84. PolylineBreakSymbol.prototype.setState = function(state) {
  85. EAction.prototype.setState.call(this, state);
  86. var di = this.getDocumentInterface();
  87. switch (this.state) {
  88. case PolylineBreakSymbol.State.SettingShape:
  89. this.cutPoint1 = undefined; // Reset first cut point on shape
  90. this.cutPoint2 = undefined; // Reset second cut point on shape
  91. di.setClickMode(RAction.PickEntity);
  92. // Set prompt and button tips textual:
  93. var trFirstPoint;
  94. if (RSpline.hasProxy() && RPolyline.hasProxy()) { // With PRO resources
  95. trFirstPoint = qsTr("Choose line, arc, circle, ellipse, spline or polyline to break up");
  96. }
  97. else { // Without PRO resources
  98. trFirstPoint = qsTr("Choose line, arc, circle or ellipse to break up");
  99. }
  100. this.setCommandPrompt(trFirstPoint);
  101. this.setLeftMouseTip(trFirstPoint);
  102. this.setRightMouseTip(EAction.trCancel);
  103. break;
  104. case PolylineBreakSymbol.State.SettingStartpt:
  105. this.cutPoint2 = undefined; // Reset second cut point on shape
  106. di.setClickMode(RAction.PickCoordinate); // Missing, but functional coordinateEvent & +Preview
  107. // Set prompt and button tips textual:
  108. var trFirstPoint = qsTr("Pick the first point");
  109. if (this.removeSegment) trFirstPoint = qsTr("Pick the first break point");
  110. this.setCommandPrompt(trFirstPoint);
  111. this.setLeftMouseTip(trFirstPoint);
  112. this.setRightMouseTip(EAction.trCancel);
  113. break;
  114. case PolylineBreakSymbol.State.SettingEndpt:
  115. di.setClickMode(RAction.PickCoordinate); // Missing but functional coordinateEvent & +Preview
  116. // Set prompt and button tips textual:
  117. var trSecondPoint = qsTr("Pick the second point");
  118. if (this.removeSegment) trSecondPoint = qsTr("Pick the second break point");
  119. this.setCommandPrompt(trSecondPoint);
  120. this.setLeftMouseTip(trSecondPoint);
  121. this.setRightMouseTip(EAction.trCancel);
  122. break;
  123. }
  124. this.setCrosshairCursor();
  125. EAction.showSnapTools();
  126. };
  127. PolylineBreakSymbol.prototype.pickEntity = function(event, preview) {
  128. var di = this.getDocumentInterface();
  129. var doc = this.getDocument();
  130. var entityId = this.getEntityId(event, preview);
  131. var entity = doc.queryEntity(entityId);
  132. // Return nothing when not an entity:
  133. if (isNull(entity)) {
  134. return;
  135. }
  136. // Store entity & shape:
  137. this.entity = entity;
  138. this.shape = this.entity.castToShape();
  139. // Validation of entity: All lines 158 to 163 - .entity was .shape
  140. var cond = isLineBasedEntity(this.entity) ||
  141. isArcEntity(this.entity) ||
  142. isCircleEntity(this.entity) ||
  143. isEllipseEntity(this.entity) ||
  144. (RSpline.hasProxy() && isSplineEntity(this.entity)) ||
  145. (RPolyline.hasProxy() && isPolylineEntity(this.entity));
  146. if (!cond) { // When validation failed
  147. // Warn about when unsupported entity if not a preview:
  148. if (!preview) {
  149. if (RSpline.hasProxy() && RPolyline.hasProxy()) { // With PRO resources
  150. // Standard warning PRO
  151. EAction.warnNotLineArcCircleEllipseSplinePolyline();
  152. }
  153. else { // Without PRO resources
  154. // Standard warning non PRO
  155. EAction.warnNotLineArcCircleEllipse();
  156. }
  157. }
  158. // Return nothing when unsupported entity:
  159. return;
  160. }
  161. else { // When validation didn't fail
  162. di.highlightEntity(entity.getId());
  163. }
  164. if (!EAction.assertEditable(entity, preview)) {
  165. return;
  166. }
  167. if (!preview) { // When NOT previewing
  168. this.setState(PolylineBreakSymbol.State.SettingStartpt);
  169. }
  170. };
  171. PolylineBreakSymbol.prototype.coordinateEvent = function(event) {
  172. var di = this.getDocumentInterface();
  173. switch (this.state) {
  174. case PolylineBreakSymbol.State.SettingStartpt:
  175. this.point1 = event.getModelPosition();
  176. this.cutPoint1 = this.shape.getClosestPointOnShape(this.point1, true); // Limited =true
  177. if (!isValidVector(this.cutPoint1)) this.cutPoint1 = this.point1;
  178. di.setRelativeZero(this.cutPoint1);
  179. this.setState(PolylineBreakSymbol.State.SettingEndpt);
  180. break;
  181. case PolylineBreakSymbol.State.SettingEndpt:
  182. this.point2 = event.getModelPosition();
  183. this.cutPoint2 = this.shape.getClosestPointOnShape(this.point2, true); // Limited =true
  184. if (!isValidVector(this.cutPoint2)) this.cutPoint2 = this.point2;
  185. di.setRelativeZero(this.cutPoint2);
  186. // Get the Break symbol, apply if any:
  187. var op = this.getOperation(false);
  188. if (!isNull(op)) {
  189. di.applyOperation(op);
  190. }
  191. // Optionally get the Breakout operation, apply if any:
  192. if (this.removeSegment) {
  193. var op = this.getBreakOutOperation(false);
  194. if (!isNull(op)) {
  195. di.applyOperation(op);
  196. }
  197. }
  198. // Return to SettingShape state when done:
  199. this.setState(PolylineBreakSymbol.State.SettingShape)
  200. break;
  201. }
  202. };
  203. PolylineBreakSymbol.prototype.coordinateEventPreview = function(event) {
  204. var di = this.getDocumentInterface();
  205. switch (this.state) {
  206. case PolylineBreakSymbol.State.SettingStartpt:
  207. this.point1 = event.getModelPosition();
  208. this.cutPoint1 = this.shape.getClosestPointOnShape(this.point1, true); // Limited =true
  209. line = new RLine(this.point1, this.cutPoint1);
  210. di.addAuxShapeToPreview(line);
  211. this.updatePreview();
  212. break;
  213. case PolylineBreakSymbol.State.SettingEndpt:
  214. this.point2 = event.getModelPosition();
  215. this.cutPoint2 = this.shape.getClosestPointOnShape(this.point2, true); // Limited =true
  216. line = new RLine(this.point2, this.cutPoint2);
  217. di.addAuxShapeToPreview(line);
  218. this.updatePreview();
  219. break;
  220. }
  221. };
  222. PolylineBreakSymbol.prototype.getOperation = function(preview) { // Unused var preview =?OBSOLETE
  223. // Return none when no endpoints:
  224. if (!isValidVector(this.cutPoint1) || !isValidVector(this.cutPoint2)) {
  225. return undefined;
  226. }
  227. var factor = this.cutPoint1.getDistanceTo(this.cutPoint2) / 3;
  228. var scaleV = new RVector(factor, factor);
  229. var segvi = (this.inclinedSegment === true) ? 1 : 0;
  230. // Define SEED:
  231. var points = new Array(
  232. new RVector( 0, 0),
  233. new RVector( segvi, this.breakRatio),
  234. new RVector( 3-segvi, this.breakRatio * -1),
  235. new RVector( 3, 0)
  236. );
  237. // Initiate an operation:
  238. var op = new RAddObjectsOperation();
  239. op.setText(this.getToolTitle());
  240. // Make an oriented and scaled polyline and add to operation:
  241. var poly = new RPolyline();
  242. for (var i=0; i<3; ++i) {
  243. var line = new RLine(points[i],points[i+1]);
  244. line.scale(scaleV);
  245. line.move(this.cutPoint1);
  246. line.rotate(this.cutPoint1.getAngleTo(this.cutPoint2), this.cutPoint1);
  247. poly.appendShape(line);
  248. }
  249. var polylineEntity = new RPolylineEntity(this.getDocument(), new RPolylineData(poly))
  250. polylineEntity.copyAttributesFrom(getPtr(this.entity));
  251. var pinherit = (this.inherit === true) ? false : true; //reverse true/false
  252. op.addObject(polylineEntity, pinherit);
  253. // Return the operation:
  254. return op;
  255. };
  256. PolylineBreakSymbol.prototype.getBreakOutOperation = function(preview) { // Unused var preview =?OBSOLETE
  257. var segment;
  258. // Return the operations:
  259. if (!isValidVector(this.cutPoint1) || !isValidVector(this.cutPoint2)) {
  260. return undefined;
  261. }
  262. // Initiate an operation:
  263. var op = new RMixedOperation();
  264. op.setText(qsTr("Break Out Segment"));
  265. // ODDITY in BreakOutManual for Arcs | Open Polylines
  266. // Order cut-points on distance from start:
  267. if (isArcEntity(this.entity) ||
  268. (isPolylineEntity(this.entity) && !this.entity.isClosed())) {
  269. var cutDist1 = this.shape.getDistanceFromStart(this.cutPoint1);
  270. var cutDist2 = this.shape.getDistanceFromStart(this.cutPoint2);
  271. if (cutDist1 > cutDist2) {
  272. // Swap points:
  273. this.point2 = this.cutPoint1;
  274. this.cutPoint1 = this.cutPoint2;
  275. this.cutPoint2 = this.point2;
  276. }
  277. }
  278. // DIFFERECE with BreakOutManual for Circles
  279. // Order cut-points CW: (.getDistanceFromStart returns 1e+300)
  280. if (isCircleEntity(this.entity)) {
  281. var cutAngle1 = this.entity.getCenter().getAngleTo(this.cutPoint1);
  282. var cutAngle2 = this.entity.getCenter().getAngleTo(this.cutPoint2);
  283. if (cutAngle2 > cutAngle1) {
  284. // Swap points:
  285. this.point2 = this.cutPoint1;
  286. this.cutPoint1 = this.cutPoint2;
  287. this.cutPoint2 = this.point2;
  288. }
  289. }
  290. // Get 3 segments resulting from:
  291. // ShapeAlgorithms.autoSplitManual(shape, cutDist1, cutDist2, cutPos1, cutPos2, position, extend);
  292. // Returns an array with rest from entity start, rest from entity end and the segment in between.
  293. // Returns undefined when the segment length would be zero.
  294. var newSegments = ShapeAlgorithms.autoSplitManual(this.shape,
  295. undefined, // Is calculate from cut point
  296. undefined, // Is calculate from cut point
  297. this.cutPoint1,
  298. this.cutPoint2,
  299. RVector.getAverage(this.cutPoint1, this.cutPoint2),
  300. false); // NOT extending Arcs
  301. // Add the first segment with the same attributes as entity, if any:
  302. if (!isNull(newSegments[0])) {
  303. segment = shapeToEntity(this.entity.getDocument(), newSegments[0]);
  304. if (!isNull(segment)) {
  305. segment.copyAttributesFrom(getPtr(this.entity));
  306. op.addObject(segment, false); // NOTuseCurrentAttributes
  307. }
  308. }
  309. // Add the last segment with the same attributes as entity, if any:
  310. if (!isNull(newSegments[1])) {
  311. segment = shapeToEntity(this.entity.getDocument(), newSegments[1])
  312. if (!isNull(segment)) {
  313. segment.copyAttributesFrom(getPtr(this.entity));
  314. op.addObject(segment, false); // NOTuseCurrentAttributes
  315. }
  316. }
  317. // Delete the original entity:
  318. op.deleteObject(this.entity);
  319. // Return the operation:
  320. return op;
  321. };
  322. PolylineBreakSymbol.prototype.slotRemoveSegmentChanged = function(bool) {
  323. this.removeSegment = bool;
  324. };
  325. PolylineBreakSymbol.prototype.slotBreakRatioChanged = function(index) {
  326. this.breakRatio = index + 1;
  327. };
  328. PolylineBreakSymbol.prototype.slotIncSegChanged = function(bool) {
  329. this.inclinedSegment = bool;
  330. };
  331. PolylineBreakSymbol.prototype.slotInheritChanged = function(bool) {
  332. this.inherit = bool;
  333. };