HatchFromSelection.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. /**
  2. * Copyright (c) 2011-2018 by Andrew Mustun. All rights reserved.
  3. *
  4. * This file is part of the QCAD project.
  5. *
  6. * QCAD is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * QCAD is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with QCAD.
  18. */
  19. /**
  20. * \defgroup ecma_draw_hatch Hatch Drawing Tool
  21. * \ingroup ecma_draw
  22. *
  23. * \brief This module contains the ECMAScript implementation of the hatch
  24. * drawing tool.
  25. */
  26. include("scripts/EAction.js");
  27. include("../HatchDialog.js");
  28. /**
  29. * \class Hatch
  30. * \brief Create hatch for selected boundary.
  31. * \ingroup ecma_draw_hatch
  32. */
  33. function HatchFromSelection(guiAction) {
  34. EAction.call(this, guiAction);
  35. }
  36. HatchFromSelection.prototype = new EAction();
  37. HatchFromSelection.tolerance = 0.001;
  38. HatchFromSelection.errorPoint = undefined;
  39. HatchFromSelection.prototype.beginEvent = function() {
  40. var i, entityId, entity, shape;
  41. EAction.prototype.beginEvent.call(this);
  42. var doc = this.getDocument();
  43. var di = this.getDocumentInterface();
  44. // analyze selection:
  45. var selectedIds = doc.querySelectedEntities();
  46. // no selection:
  47. if (selectedIds.length===0) {
  48. EAction.handleUserWarning(qsTr("No boundary selected."));
  49. this.terminate();
  50. return;
  51. }
  52. if (!HatchFromSelection.verifyBoundary(doc, selectedIds)) {
  53. this.terminate();
  54. return;
  55. }
  56. // collect hatch data (boundary elements ordered as loops):
  57. var hatchData = HatchFromSelection.createHatchData(doc, selectedIds);
  58. if (isNull(hatchData)) {
  59. this.updatePreview();
  60. di.keepPreview();
  61. EAction.handleUserWarning(
  62. qsTr("Loop not closed at point %1/%2.")
  63. .arg(isNull(HatchFromSelection.connectionPoint) ? "?" : HatchFromSelection.connectionPoint.x)
  64. .arg(isNull(HatchFromSelection.connectionPoint) ? "?" : HatchFromSelection.connectionPoint.y),
  65. true);
  66. this.terminate();
  67. return;
  68. }
  69. // show hatch dialog for hatch pattern parameters:
  70. var dialog = new HatchDialog(this);
  71. var hatchDataDlg = dialog.show();
  72. if (isNull(hatchDataDlg)) {
  73. this.terminate();
  74. return;
  75. }
  76. hatchData.setSolid(hatchDataDlg.isSolid());
  77. hatchData.setScale(hatchDataDlg.getScale());
  78. hatchData.setAngle(hatchDataDlg.getAngle());
  79. hatchData.setPatternName(hatchDataDlg.getPatternName());
  80. var hatch = new RHatchEntity(doc, hatchData);
  81. var op = new RAddObjectOperation(hatch, this.getToolTitle());
  82. di.applyOperation(op);
  83. this.terminate();
  84. };
  85. HatchFromSelection.prototype.getAuxPreview = function() {
  86. if (!isValidVector(HatchFromSelection.errorPoint)) {
  87. return undefined;
  88. }
  89. var ret = [];
  90. var view = EAction.getGraphicsView();
  91. if (isNull(view)) {
  92. return undefined;
  93. }
  94. var radius = view.mapDistanceFromView(10.0);
  95. ret.push(new RCircle(HatchFromSelection.errorPoint, radius));
  96. return ret;
  97. };
  98. /**
  99. * Verifies the entities entityIds as potential hatch boundary.
  100. */
  101. HatchFromSelection.verifyBoundary = function(doc, entityIds) {
  102. for (var i = 0; i < entityIds.length; i++) {
  103. var entityId = entityIds[i];
  104. var entity = doc.queryEntityDirect(entityId);
  105. if (!HatchFromSelection.verifyBoundaryEntity(doc, entity)) {
  106. return false;
  107. }
  108. }
  109. return true;
  110. };
  111. /**
  112. * Verifies the given entity as potential hatch boundary.
  113. */
  114. HatchFromSelection.verifyBoundaryEntity = function(doc, entity) {
  115. if (isBlockReferenceEntity(entity)) {
  116. var blockReferenceData = entity.getData();
  117. var ids = doc.queryBlockEntities(blockReferenceData.getReferencedBlockId());
  118. var ret = true;
  119. for (var i=0; i<ids.length; i++) {
  120. var bEntity = blockReferenceData.queryEntity(ids[i], true);
  121. if (isNull(bEntity)) {
  122. continue;
  123. }
  124. if (!bEntity.isVisible()) {
  125. continue;
  126. }
  127. ret = ret && HatchFromSelection.verifyBoundaryEntity(doc, bEntity);
  128. }
  129. return ret;
  130. }
  131. if (isLineEntity(entity) ||
  132. isArcEntity(entity) ||
  133. isCircleEntity(entity) ||
  134. isEllipseEntity(entity) ||
  135. isPolylineEntity(entity) ||
  136. isFaceEntity(entity) ||
  137. isSplineEntity(entity)) {
  138. return true;
  139. }
  140. if (!isNull(entity)) {
  141. EAction.handleUserWarning(
  142. qsTr("Each boundary entity must be a line, arc, " +
  143. "circle, ellipse arc, ellipse, polyline or spline. " +
  144. "Found:") + " " +
  145. entityTypeToString(entity.getType(), false));
  146. }
  147. return false;
  148. };
  149. /**
  150. * \return RHatchData object created from the given boundary entities entityIds or undefined.
  151. */
  152. HatchFromSelection.createHatchData = function(doc, entityIds, ignoreOpenLoops) {
  153. if (isNull(ignoreOpenLoops)) {
  154. ignoreOpenLoops = false;
  155. }
  156. var hatchData = new RHatchData();
  157. hatchData.setDocument(doc);
  158. doc.traversed = {};
  159. HatchFromSelection.errorPoint = undefined;
  160. for (var i=0; i<entityIds.length; i++) {
  161. var entityId = entityIds[i];
  162. if (doc.traversed[entityId]) {
  163. continue;
  164. }
  165. var entity = doc.queryEntityDirect(entityId);
  166. if (!HatchFromSelection.traverse(hatchData, doc, entity, entityIds)) {
  167. if (ignoreOpenLoops) {
  168. continue;
  169. }
  170. else {
  171. return undefined;
  172. }
  173. }
  174. }
  175. return hatchData;
  176. };
  177. /**
  178. * Traverses the given candidates recursively for connected entities.
  179. * Resulting loops are appended to the given hatch data.
  180. */
  181. HatchFromSelection.traverse = function(hatchData, docOrBlockRef, entity, candidateIds) {
  182. var i;
  183. // handle closed polyline loops:
  184. if (HatchFromSelection.isClosedPolyline(entity)) {
  185. if (entity.getLength()<RS.PointTolerance) {
  186. // zero length closed polyline: safe to ignore:
  187. return true;
  188. }
  189. hatchData.newLoop();
  190. var segments = entity.getExploded();
  191. for (i=0; i<segments.length; i++) {
  192. var segment = segments[i];
  193. if (RSettings.getQtVersion() >= 0x060000) {
  194. hatchData.addBoundary(segment.clone());
  195. }
  196. else {
  197. hatchData.addBoundary(segment);
  198. }
  199. }
  200. // TODO: add polyline as boundary:
  201. // hatchData.addBoundary(entity.getData().castToShape());
  202. docOrBlockRef.traversed[entity.getId()] = true;
  203. return true;
  204. }
  205. // handle circle, full ellipse and closed spline loops:
  206. if (HatchFromSelection.isClosedCurve(entity)) {
  207. hatchData.newLoop();
  208. if (RSettings.getQtVersion() >= 0x060000) {
  209. hatchData.addBoundary(entity.getData().castToShape().clone());
  210. }
  211. else {
  212. hatchData.addBoundary(entity.getData().castToShape());
  213. }
  214. docOrBlockRef.traversed[entity.getId()] = true;
  215. return true;
  216. }
  217. var document = EAction.getDocument();
  218. // handle block reference:
  219. if (isBlockReferenceEntity(entity)) {
  220. var blockReferenceData = entity.getData();
  221. blockReferenceData.traversed = {};
  222. var ids = document.queryBlockEntities(blockReferenceData.getReferencedBlockId());
  223. var ret = true;
  224. for (i=0; i<ids.length; i++) {
  225. if (blockReferenceData.traversed[ids[i]]) {
  226. continue;
  227. }
  228. var bEntity = blockReferenceData.queryEntity(ids[i], true);
  229. if (isNull(bEntity)) {
  230. continue;
  231. }
  232. if (!bEntity.isVisible()) {
  233. continue;
  234. }
  235. if (!HatchFromSelection.traverse(hatchData, blockReferenceData, bEntity, ids)) {
  236. ret = false;
  237. break;
  238. }
  239. }
  240. return ret;
  241. }
  242. var shape = entity.getData().castToShape();
  243. if (isNull(shape)) {
  244. return false;
  245. }
  246. shape = shape.clone();
  247. if (isFunction(shape.getLength)) {
  248. if (shape.getLength()<RS.PointTolerance) {
  249. // ignore zero length entity:
  250. return false;
  251. }
  252. }
  253. // connect 'loose' boundary elements into loops:
  254. hatchData.newLoop();
  255. docOrBlockRef.traversed[entity.getId()] = true;
  256. if (RSettings.getQtVersion() >= 0x060000) {
  257. hatchData.addBoundary(shape.clone());
  258. }
  259. else {
  260. hatchData.addBoundary(shape);
  261. }
  262. var currentShape = shape;
  263. var loopStartPoint = shape.getStartPoint();
  264. // find connected entities:
  265. var done2 = true;
  266. HatchFromSelection.connectionPoint = undefined;
  267. do {
  268. done2 = true;
  269. HatchFromSelection.connectionPoint = currentShape.getEndPoint();
  270. for (i = 0; i < candidateIds.length; i++) {
  271. var entityId = candidateIds[i];
  272. if (docOrBlockRef.traversed[entityId]) {
  273. continue;
  274. }
  275. if (isFunction(docOrBlockRef.queryEntityDirect)) {
  276. // query entity from document:
  277. entity = docOrBlockRef.queryEntityDirect(entityId);
  278. }
  279. else {
  280. // query entity from block reference:
  281. entity = docOrBlockRef.queryEntity(entityId, true);
  282. }
  283. if (isBlockReferenceEntity(entity)) {
  284. continue;
  285. }
  286. if (HatchFromSelection.isClosedCurve(entity) || HatchFromSelection.isClosedPolyline(entity)) {
  287. continue;
  288. }
  289. if (isFunction(entity.getLength)) {
  290. // ignore zero length entities:
  291. if (entity.getLength()<RS.PointTolerance) {
  292. docOrBlockRef.traversed[entityId] = true;
  293. continue;
  294. }
  295. }
  296. if (!isFunction(entity.getStartPoint)) {
  297. // ignore entities without start / end points (existing hatches, etc..):
  298. continue;
  299. }
  300. var sp = entity.getStartPoint();
  301. var ep = entity.getEndPoint();
  302. var spConnects = HatchFromSelection.connectionPoint.equalsFuzzy(sp, HatchFromSelection.tolerance);
  303. var epConnects = HatchFromSelection.connectionPoint.equalsFuzzy(ep, HatchFromSelection.tolerance);
  304. if (spConnects || epConnects) {
  305. docOrBlockRef.traversed[entityId] = true;
  306. shape = entity.getData().castToShape().clone();
  307. if (epConnects) {
  308. shape.reverse();
  309. }
  310. if (RSettings.getQtVersion() >= 0x060000) {
  311. hatchData.addBoundary(shape.clone());
  312. }
  313. else {
  314. hatchData.addBoundary(shape);
  315. }
  316. currentShape = shape;
  317. done2 = false;
  318. //qDebug("Hatch: next loop shape: ", shape, epConnects ? " (reversed)" : "");
  319. break;
  320. }
  321. //qDebug("gap sp: ", HatchFromSelection.connectionPoint.getDistanceTo(sp));
  322. //qDebug("gap ep: ", HatchFromSelection.connectionPoint.getDistanceTo(ep));
  323. }
  324. } while (!done2);
  325. if (!HatchFromSelection.connectionPoint.equalsFuzzy(loopStartPoint, HatchFromSelection.tolerance)) {
  326. HatchFromSelection.errorPoint = HatchFromSelection.connectionPoint;
  327. hatchData.cancelLoop();
  328. //qDebug("start does not connect to end by: ", HatchFromSelection.connectionPoint.getDistanceTo(loopStartPoint));
  329. return false;
  330. }
  331. return true;
  332. };
  333. HatchFromSelection.isClosedPolyline = function(entity) {
  334. return (isPolylineEntity(entity) && entity.isGeometricallyClosed()) || isFaceEntity(entity);
  335. };
  336. HatchFromSelection.isClosedCurve = function(entity) {
  337. return isCircleEntity(entity) ||
  338. (isEllipseEntity(entity) && entity.isFullEllipse()) ||
  339. (isSplineEntity(entity) && entity.isClosed());
  340. };