Scale.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. /**
  2. * Copyright (c) 2011-2021 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("../Transform.js");
  20. include("scripts/ShapeAlgorithms.js");
  21. /**
  22. * \class Scale
  23. * \brief Scales selected entities. Supports multiple copies.
  24. * \ingroup ecma_modify
  25. */
  26. function Scale(guiAction) {
  27. Transform.call(this, guiAction);
  28. this.focusPoint = undefined;
  29. this.referencePoint = undefined;
  30. this.targetPoint = undefined;
  31. this.factorX = undefined;
  32. this.factorY = undefined;
  33. this.factorByMouse = false;
  34. this.useDialog = RSettings.getBoolValue("Scale/UseDialog", true);
  35. if (!this.useDialog) {
  36. this.setUiOptions("Scale.ui");
  37. }
  38. }
  39. Scale.prototype = new Transform();
  40. Scale.includeBasePath = includeBasePath;
  41. Scale.getPreferencesCategory = function() {
  42. return [qsTr("Modify"), qsTr("Scale")];
  43. };
  44. Scale.State = {
  45. SettingFocusPoint : 0,
  46. SettingReferencePoint : 1,
  47. SettingTargetPoint : 2
  48. };
  49. Scale.prototype.beginEvent = function() {
  50. Transform.prototype.beginEvent.call(this);
  51. if (!this.verifySelection()) {
  52. return;
  53. }
  54. this.setState(Scale.State.SettingFocusPoint);
  55. };
  56. Scale.prototype.setState = function(state) {
  57. Transform.prototype.setState.call(this, state);
  58. this.getDocumentInterface().setClickMode(RAction.PickCoordinate);
  59. this.setCrosshairCursor();
  60. var appWin = RMainWindowQt.getMainWindow();
  61. switch (this.state) {
  62. case Scale.State.SettingFocusPoint:
  63. this.referencePoint = undefined;
  64. this.targetPoint = undefined;
  65. //this.factorX = undefined;
  66. //this.factorY = undefined;
  67. var trCenterPoint = qsTr("Focus point");
  68. this.setCommandPrompt(trCenterPoint);
  69. this.setLeftMouseTip(trCenterPoint);
  70. this.setRightMouseTip(EAction.trCancel);
  71. break;
  72. case Scale.State.SettingReferencePoint:
  73. this.referencePoint = undefined;
  74. this.targetPoint = undefined;
  75. this.factorX = undefined;
  76. this.factorY = undefined;
  77. var trReferencePoint = qsTr("Reference point");
  78. this.setCommandPrompt(trReferencePoint);
  79. this.setLeftMouseTip(trReferencePoint);
  80. this.setRightMouseTip(EAction.trBack);
  81. break;
  82. case Scale.State.SettingTargetPoint:
  83. this.targetPoint = undefined;
  84. this.factorX = undefined;
  85. this.factorY = undefined;
  86. var trTargetPoint = qsTr("Target point");
  87. this.setCommandPrompt(trTargetPoint);
  88. this.setLeftMouseTip(trTargetPoint);
  89. this.setRightMouseTip(EAction.trBack);
  90. break;
  91. }
  92. EAction.showSnapTools();
  93. };
  94. Scale.prototype.initUiOptions = function(resume, optionsToolBar) {
  95. EAction.prototype.initUiOptions.call(this, resume, optionsToolBar);
  96. this.initWidgets(optionsToolBar, "Sq");
  97. };
  98. Scale.prototype.escapeEvent = function() {
  99. switch (this.state) {
  100. case Scale.State.SettingFocusPoint:
  101. Transform.prototype.escapeEvent.call(this);
  102. break;
  103. case Scale.State.SettingReferencePoint:
  104. this.setState(Scale.State.SettingFocusPoint);
  105. break;
  106. case Scale.State.SettingTargetPoint:
  107. this.setState(Scale.State.SettingReferencePoint);
  108. break;
  109. }
  110. };
  111. Scale.prototype.pickCoordinate = function(event, preview) {
  112. var di = this.getDocumentInterface();
  113. var op;
  114. switch (this.state) {
  115. case Scale.State.SettingFocusPoint:
  116. this.focusPoint = event.getModelPosition();
  117. if (preview) {
  118. this.updatePreview();
  119. }
  120. else {
  121. if (this.useDialog) {
  122. this.setState(-1);
  123. if (!this.showDialog()) {
  124. // dialog canceled:
  125. this.terminate();
  126. return;
  127. }
  128. }
  129. // define factor with mouse:
  130. if (this.factorByMouse===true) {
  131. this.setState(Scale.State.SettingReferencePoint);
  132. }
  133. // factor defined in dialog:
  134. else {
  135. op = this.getOperation(false);
  136. if (!isNull(op)) {
  137. di.applyOperation(op);
  138. this.terminate();
  139. }
  140. }
  141. di.setRelativeZero(this.focusPoint);
  142. }
  143. break;
  144. case Scale.State.SettingReferencePoint:
  145. this.referencePoint = event.getModelPosition();
  146. if (preview) {
  147. this.updatePreview();
  148. }
  149. else {
  150. di.setRelativeZero(this.referencePoint);
  151. this.setState(Scale.State.SettingTargetPoint);
  152. }
  153. break;
  154. case Scale.State.SettingTargetPoint:
  155. this.targetPoint = event.getModelPosition();
  156. if (preview) {
  157. this.updatePreview();
  158. }
  159. else {
  160. op = this.getOperation(false);
  161. if (!isNull(op)) {
  162. di.applyOperation(op);
  163. di.setRelativeZero(this.targetPoint);
  164. this.setState(-1);
  165. this.terminate();
  166. }
  167. }
  168. break;
  169. }
  170. };
  171. /**
  172. * Shows the scaling dialog.
  173. *
  174. * \return True if the dialog was accepted, false if it was canceled.
  175. */
  176. Scale.prototype.showDialog = function() {
  177. var dialog = WidgetFactory.createDialog(Scale.includeBasePath, "ScaleDialog.ui");
  178. var widgets = getWidgets(dialog);
  179. this.initWidgets(dialog);
  180. WidgetFactory.restoreState(dialog);
  181. if (!dialog.exec()) {
  182. destr(dialog);
  183. EAction.activateMainWindow();
  184. return false;
  185. }
  186. if (widgets["DeleteOriginal"].checked===true) {
  187. this.copies = 0;
  188. }
  189. else if (widgets["KeepOriginal"].checked===true) {
  190. this.copies = 1;
  191. }
  192. else {
  193. this.copies = widgets["NumberOfCopies"].getValue();
  194. }
  195. if (widgets["FactorByMouse"].checked===true) {
  196. this.factorByMouse = true;
  197. this.factorX = undefined;
  198. this.factorY = undefined;
  199. }
  200. else {
  201. this.factorByMouse = false;
  202. this.factorX = widgets["FactorX"].getValue();
  203. if (widgets["KeepProportions"].checked===false) {
  204. this.factorY = widgets["FactorY"].getValue();
  205. }
  206. else {
  207. this.factorY = undefined;
  208. }
  209. }
  210. this.useCurrentAttributes = (widgets["UseCurrentAttributes"].checked===true);
  211. WidgetFactory.saveState(dialog);
  212. destr(dialog);
  213. EAction.activateMainWindow();
  214. return true;
  215. };
  216. Scale.prototype.initWidgets = function(widget, postfix) {
  217. if (isNull(postfix)) {
  218. postfix = "";
  219. }
  220. var widgets = getWidgets(widget);
  221. widgets["FactorX"].textChanged.connect(
  222. function(text) {
  223. if (widgets["KeepProportions"].checked===true) {
  224. widgets["FactorY"].text = text;
  225. }
  226. });
  227. widgets["KeepProportions"].toggled.connect(
  228. function(checked) {
  229. if (checked) {
  230. widgets["FactorY"].text = widgets["FactorX"].text;
  231. }
  232. if (checked) {
  233. widgets["KeepProportions"].icon = new QIcon(Scale.includeBasePath + "/KeepProportionsOn" + postfix + ".svg");
  234. }
  235. else {
  236. widgets["KeepProportions"].icon = new QIcon(Scale.includeBasePath + "/KeepProportionsOff" + postfix + ".svg");
  237. }
  238. });
  239. };
  240. Scale.prototype.getOperation = function(preview) {
  241. if (!isValidVector(this.focusPoint)) {
  242. return undefined;
  243. }
  244. if (this.factorByMouse===true) {
  245. if (!isValidVector(this.referencePoint) || !isValidVector(this.targetPoint)) {
  246. return undefined;
  247. }
  248. var s1, s2;
  249. s1 = this.focusPoint.getDistanceTo(this.referencePoint);
  250. if (s1<RS.PointTolerance) {
  251. return undefined;
  252. }
  253. s2 = this.focusPoint.getDistanceTo(this.targetPoint);
  254. if (s2<RS.PointTolerance) {
  255. return undefined;
  256. }
  257. this.factorX = s2/s1;
  258. this.factorY = undefined;
  259. }
  260. if (!isNumber(this.factorX)) {
  261. return undefined;
  262. }
  263. return Transform.prototype.getOperation.call(this, preview);
  264. };
  265. Scale.prototype.transformArc = function(shape, sv) {
  266. var s = shape;
  267. if (isFunction(shape.data)) {
  268. s = getPtr(shape);
  269. }
  270. if (isCircleShape(s)) {
  271. s = ShapeAlgorithms.circleToArc(s, 0.0);
  272. }
  273. return RShape.scaleArc(s, sv, this.focusPoint);
  274. };
  275. /**
  276. * Callback function for Transform.getOperation.
  277. */
  278. Scale.prototype.transform = function(entity, k, op, preview, flags) {
  279. // uniform scaling (supported by all entities):
  280. if (isNull(this.factorY) || RMath.fuzzyCompare(this.factorX, this.factorY)) {
  281. entity.scale(Math.pow(this.factorX, k), this.focusPoint);
  282. op.addObject(entity, flags);
  283. return;
  284. }
  285. this.preTransform(entity);
  286. var e;
  287. // non-uniform scaling of arc, circle or ellipse:
  288. var sv = new RVector(Math.pow(this.factorX, k), Math.pow(this.factorY, k));
  289. if (isArcEntity(entity) || isCircleEntity(entity) || isEllipseEntity(entity)) {
  290. var arc = entity.castToShape();
  291. var shape = this.transformArc(arc, sv);
  292. e = shapeToEntity(this.getDocument(), shape);
  293. if (!isNull(e)) {
  294. e.copyAttributesFrom(entity);
  295. e.setDrawOrder(entity.getDrawOrder());
  296. e.setSelected(true);
  297. }
  298. if (this.getCopies()===0) {
  299. op.deleteObject(entity);
  300. }
  301. if (!isNull(e)) {
  302. this.postTransform(e);
  303. op.addObject(e, flags);
  304. }
  305. return;
  306. }
  307. // non-uniform scaling of hatches:
  308. if (isHatchEntity(entity)) {
  309. //debugger;
  310. var data = entity.getData();
  311. var newHatchData = new RHatchData();
  312. newHatchData.setDocument(data.getDocument());
  313. newHatchData.copyAttributesFrom(data);
  314. //newHatchData.clearBoundary();
  315. for (var i=0; i<entity.getLoopCount(); i++) {
  316. newHatchData.newLoop();
  317. var shapes = entity.getLoopBoundary(i);
  318. for (var n=0; n<shapes.length; n++) {
  319. shape = shapes[n];
  320. if (isArcShape(shape) || isCircleShape(shape) || isEllipseShape(shape)) {
  321. var newShape = this.transformArc(shape, sv);
  322. if (RSettings.getQtVersion() >= 0x060000) {
  323. newHatchData.addBoundary(newShape.clone());
  324. }
  325. else {
  326. newHatchData.addBoundary(newShape);
  327. }
  328. }
  329. else {
  330. // line, spline:
  331. shape.scale(sv, this.focusPoint);
  332. if (RSettings.getQtVersion() >= 0x060000) {
  333. newHatchData.addBoundary(shape.clone());
  334. }
  335. else {
  336. newHatchData.addBoundary(shape);
  337. }
  338. }
  339. }
  340. }
  341. newHatchData.setSolid(entity.isSolid());
  342. newHatchData.setPatternName(entity.getPatternName());
  343. newHatchData.setScale(entity.getScale());
  344. newHatchData.setAngle(entity.getAngle());
  345. newHatchData.setOriginPoint(entity.getOriginPoint());
  346. entity.setData(newHatchData);
  347. this.postTransform(entity);
  348. op.addObject(entity, flags);
  349. return;
  350. }
  351. // non-uniform scaling of other entities:
  352. entity.scale(sv, this.focusPoint);
  353. this.postTransform(entity);
  354. op.addObject(entity, flags);
  355. };
  356. Scale.prototype.preTransform = function(entity) {
  357. return;
  358. };
  359. Scale.prototype.postTransform = function(entity) {
  360. return;
  361. };
  362. Scale.prototype.getAuxPreview = function() {
  363. if (!isValidVector(this.focusPoint)) {
  364. return undefined;
  365. }
  366. var ret = [];
  367. if (this.state==Scale.State.SettingReferencePoint || this.state==Scale.State.SettingTargetPoint) {
  368. if (isValidVector(this.referencePoint)) {
  369. ret.push(new RLine(this.focusPoint, this.referencePoint));
  370. }
  371. }
  372. if (this.state==Scale.State.SettingTargetPoint) {
  373. if (isValidVector(this.targetPoint)) {
  374. ret.push(new RLine(this.focusPoint, this.targetPoint));
  375. }
  376. }
  377. return ret;
  378. };
  379. Scale.prototype.slotFactorXChanged = function(v) {
  380. this.factorX = v;
  381. };
  382. Scale.prototype.slotFactorYChanged = function(v) {
  383. this.factorY = v;
  384. };
  385. Scale.prototype.slotFactorByMouseChanged = function(v) {
  386. this.factorByMouse = v;
  387. };