Trim.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  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. include("scripts/Modify/Modify.js");
  20. include("scripts/ShapeAlgorithms.js");
  21. /**
  22. * \class Trim
  23. * \brief Trims an entity to another entity or trims both entities
  24. * if \c this.trimBoth is true.
  25. * \ingroup ecma_modify
  26. */
  27. function Trim(guiAction) {
  28. Modify.call(this, guiAction);
  29. this.limitingEntity = undefined;
  30. this.limitingClickPos = undefined;
  31. this.trimEntity = undefined;
  32. this.trimClickPos = undefined;
  33. this.trimBoth = false;
  34. this.chooseTrimEntityFirst = false;
  35. }
  36. Trim.prototype = new Modify();
  37. Trim.includeBasePath = includeBasePath;
  38. Trim.State = {
  39. ChoosingLimitingEntity : 0,
  40. ChoosingTrimEntity : 1
  41. };
  42. Trim.prototype.beginEvent = function() {
  43. Modify.prototype.beginEvent.call(this);
  44. if (this.chooseTrimEntityFirst) {
  45. this.setState(Trim.State.ChoosingTrimEntity);
  46. }
  47. else {
  48. this.setState(Trim.State.ChoosingLimitingEntity);
  49. }
  50. };
  51. Trim.prototype.setState = function(state) {
  52. Modify.prototype.setState.call(this, state);
  53. this.getDocumentInterface().setClickMode(RAction.PickEntity);
  54. this.setCrosshairCursor();
  55. var appWin = RMainWindowQt.getMainWindow();
  56. switch (this.state) {
  57. case Trim.State.ChoosingLimitingEntity:
  58. this.limitingEntity = undefined;
  59. this.limitingClickPos = undefined;
  60. if (!this.chooseTrimEntityFirst) {
  61. this.trimEntity = undefined;
  62. this.trimClickPos = undefined;
  63. }
  64. var trLimitingEntity = qsTr("Choose limiting entity");
  65. this.setCommandPrompt(trLimitingEntity);
  66. this.setLeftMouseTip(trLimitingEntity);
  67. this.setRightMouseTip(EAction.trCancel);
  68. break;
  69. case Trim.State.ChoosingTrimEntity:
  70. this.trimEntity = undefined;
  71. this.trimClickPos = undefined;
  72. if (this.chooseTrimEntityFirst) {
  73. this.limitingEntity = undefined;
  74. this.limitingClickPos = undefined;
  75. }
  76. var trTrimEntity = qsTr("Choose entity to trim");
  77. this.setCommandPrompt(trTrimEntity);
  78. this.setLeftMouseTip(trTrimEntity);
  79. this.setRightMouseTip(EAction.trBack);
  80. break;
  81. }
  82. };
  83. Trim.prototype.escapeEvent = function() {
  84. switch (this.state) {
  85. case Trim.State.ChoosingLimitingEntity:
  86. if (this.chooseTrimEntityFirst) {
  87. this.setState(Trim.State.ChoosingTrimEntity);
  88. }
  89. else {
  90. Modify.prototype.escapeEvent.call(this);
  91. }
  92. break;
  93. case Trim.State.ChoosingTrimEntity:
  94. if (this.chooseTrimEntityFirst) {
  95. Modify.prototype.escapeEvent.call(this);
  96. }
  97. else {
  98. this.setState(Trim.State.ChoosingLimitingEntity);
  99. }
  100. break;
  101. }
  102. };
  103. Trim.prototype.pickEntity = function(event, preview) {
  104. var di = this.getDocumentInterface();
  105. var doc = this.getDocument();
  106. var entityId = this.getEntityId(event, preview);
  107. var entity = doc.queryEntity(entityId);
  108. var pos = event.getModelPosition();
  109. if (!this.isEntitySnappable(entity)) {
  110. // entity not on a snappable layer:
  111. return;
  112. }
  113. switch (this.state) {
  114. case Trim.State.ChoosingLimitingEntity:
  115. if (isNull(entity) || (this.trimBoth && !EAction.assertEditable(entity, preview))) {
  116. this.limitingEntity = undefined;
  117. if (preview) {
  118. this.updatePreview();
  119. }
  120. break;
  121. }
  122. var shape = entity.getClosestSimpleShape(pos);
  123. // unsupported entity chosen:
  124. if (!this.isSupportedLimitingShape(shape)) {
  125. if (preview) {
  126. this.updatePreview();
  127. }
  128. else {
  129. this.warnUnsupportedEntity();
  130. }
  131. break;
  132. }
  133. this.limitingEntity = entity;
  134. this.limitingClickPos = pos;
  135. if (preview) {
  136. this.updatePreview();
  137. }
  138. else {
  139. if (this.chooseTrimEntityFirst) {
  140. var op = this.getOperation(false);
  141. if (!isNull(op)) {
  142. di.applyOperation(op);
  143. }
  144. this.setState(Trim.State.ChoosingTrimEntity);
  145. }
  146. else {
  147. this.setState(Trim.State.ChoosingTrimEntity);
  148. }
  149. }
  150. break;
  151. case Trim.State.ChoosingTrimEntity:
  152. if (isNull(entity) || !EAction.assertEditable(entity, preview)) {
  153. this.trimEntity = undefined;
  154. if (preview) {
  155. this.updatePreview();
  156. }
  157. return;
  158. }
  159. // unsupported entity chosen:
  160. if (!Trim.isSupportedTrimEntity(entity)) {
  161. this.trimEntity = undefined;
  162. if (preview) {
  163. this.updatePreview();
  164. }
  165. else {
  166. this.warnUnsupportedEntity();
  167. }
  168. break;
  169. }
  170. this.trimEntity = entity;
  171. this.trimClickPos = event.getModelPosition();
  172. if (preview) {
  173. this.updatePreview();
  174. }
  175. else {
  176. if (this.chooseTrimEntityFirst) {
  177. this.setState(Trim.State.ChoosingLimitingEntity);
  178. }
  179. else {
  180. var op = this.getOperation(false);
  181. if (!isNull(op)) {
  182. di.applyOperation(op);
  183. }
  184. }
  185. // trimming both? start asking for next limiting entity
  186. // otherwise, trim more entities to same limiting entity
  187. if (this.trimBoth) {
  188. this.setState(Trim.State.ChoosingLimitingEntity);
  189. }
  190. }
  191. break;
  192. }
  193. };
  194. Trim.prototype.isSupportedLimitingShape = function(shape) {
  195. return isLineBasedShape(shape) ||
  196. isArcShape(shape) ||
  197. isCircleShape(shape) ||
  198. isEllipseShape(shape) ||
  199. (RSpline.hasProxy() && isSplineShape(shape)) ||
  200. (RPolyline.hasProxy() && isPolylineShape(shape));
  201. };
  202. Trim.isSupportedTrimEntity = function(entity) {
  203. return isLineBasedEntity(entity) ||
  204. isArcEntity(entity) ||
  205. isCircleEntity(entity) ||
  206. isEllipseEntity(entity) ||
  207. (RSpline.hasProxy() && isSplineEntity(entity)) ||
  208. (RPolyline.hasProxy() && isPolylineEntity(entity));
  209. };
  210. Trim.prototype.warnUnsupportedEntity = function() {
  211. if (RSpline.hasProxy() && RPolyline.hasProxy()) {
  212. EAction.warnNotLineArcCircleEllipseSplinePolyline();
  213. }
  214. else {
  215. EAction.warnNotLineArcCircleEllipse();
  216. }
  217. };
  218. Trim.prototype.getOperation = function(preview) {
  219. if (!isEntity(this.trimEntity)) {
  220. return undefined;
  221. }
  222. var op = new RAddObjectsOperation();
  223. op.setText(this.getToolTitle());
  224. var success = Trim.trim(op, this.limitingEntity, this.limitingClickPos, this.trimEntity, this.trimClickPos, this.trimBoth, preview);
  225. if (!preview) {
  226. this.trimEntity = undefined;
  227. }
  228. if (!success && !preview) {
  229. EAction.handleUserWarning(qsTr("The two entities don't intersect, or are currently not supported for trimming."));
  230. }
  231. return op;
  232. };
  233. Trim.prototype.getHighlightedEntities = function() {
  234. var ret = [];
  235. if (isEntity(this.limitingEntity)) {
  236. ret.push(this.limitingEntity.getId());
  237. }
  238. if (isEntity(this.trimEntity)) {
  239. ret.push(this.trimEntity.getId());
  240. }
  241. return ret;
  242. };
  243. /**
  244. * Trims or extends the given trimEntity to the intersection point of the
  245. * trimEntity and the limitingShape.
  246. *
  247. * \param limitingEntity Entity to which the trim entity will be trimmed.
  248. * \param limitingClickPos Coordinate which defines the intersection to which the
  249. * trim entity will be trimmed.
  250. * \param trimEntity Entity which will be trimmed.
  251. * \param trimClickPos Coordinate which defines which endpoint of the
  252. * trim entity to trim.
  253. * \param trimBoth true: Trim both entities. false: trim trimEntity only.
  254. */
  255. Trim.trim = function(op, limitingEntity, limitingClickPos, trimEntity, trimClickPos, trimBoth, preview) {
  256. if (!isEntity(limitingEntity) || !isValidVector(limitingClickPos) ||
  257. !isEntity(trimEntity) || !isValidVector(trimClickPos) ||
  258. !isBoolean(trimBoth)) {
  259. return false;
  260. }
  261. var trimShape = trimEntity.castToShape();
  262. if (isNull(trimShape)) {
  263. return false;
  264. }
  265. var limitingShape = limitingEntity.castToShape();
  266. if (isNull(limitingShape)) {
  267. limitingShape = limitingEntity.getClosestSimpleShape(limitingClickPos);
  268. if (!isNull(limitingShape)) {
  269. limitingShape = getPtr(limitingShape);
  270. }
  271. }
  272. if (isNull(limitingShape)) {
  273. return false;
  274. }
  275. var samePolyline = (limitingEntity.getId()===trimEntity.getId() && isPolylineEntity(limitingEntity));
  276. if (samePolyline) {
  277. if (!trimBoth) {
  278. // TODO: fix trimming one segment within same polyline:
  279. return false;
  280. }
  281. }
  282. if (trimBoth && isClosedPolylineEntity(limitingEntity) && !samePolyline) {
  283. // no trimming between two different closed polylines:
  284. return false;
  285. }
  286. var newShapes = Trim.trimShapes(trimShape, trimClickPos, limitingShape, limitingClickPos, trimBoth, samePolyline);
  287. if (newShapes.length===0) {
  288. return false;
  289. }
  290. if (!modifyEntity(op, trimEntity.clone(), getPtr(newShapes[0]))) {
  291. if (!preview) {
  292. if (trimBoth) {
  293. EAction.handleUserWarning(qsTr("First entity cannot be trimmed."));
  294. }
  295. else {
  296. EAction.handleUserWarning(qsTr("Entity cannot be trimmed."));
  297. }
  298. }
  299. }
  300. if (newShapes.length===1) {
  301. // trimming was within same polyline:
  302. return true;
  303. }
  304. if (trimBoth) {
  305. if (!modifyEntity(op, limitingEntity.clone(), getPtr(newShapes[1]))) {
  306. if (!preview) {
  307. EAction.handleUserWarning(qsTr("Second entity cannot be trimmed."));
  308. }
  309. }
  310. }
  311. return true;
  312. };
  313. Trim.trimShapes = function(trimShape, trimClickPos, limitingShape, limitingClickPos, trimBoth, samePolyline) {
  314. return RShape.trim(trimShape, trimClickPos, limitingShape, limitingClickPos, trimBoth, samePolyline);
  315. };