ToolTransform.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756
  1. from PyQt5 import QtGui, QtCore, QtWidgets
  2. from PyQt5.QtCore import Qt
  3. from GUIElements import FCEntry, FCButton, OptionalInputSection
  4. from FlatCAMTool import FlatCAMTool
  5. from FlatCAMObj import *
  6. class ToolTransform(FlatCAMTool):
  7. toolName = "Object Transform"
  8. rotateName = "Rotate"
  9. skewName = "Skew/Shear"
  10. scaleName = "Scale"
  11. flipName = "Mirror (Flip)"
  12. offsetName = "Offset"
  13. def __init__(self, app):
  14. FlatCAMTool.__init__(self, app)
  15. self.transform_lay = QtWidgets.QVBoxLayout()
  16. self.layout.addLayout(self.transform_lay)
  17. ## Title
  18. title_label = QtWidgets.QLabel("<font size=4><b>%s</b></font><br>" % self.toolName)
  19. self.transform_lay.addWidget(title_label)
  20. self.empty_label = QtWidgets.QLabel("")
  21. self.empty_label.setFixedWidth(50)
  22. self.empty_label1 = QtWidgets.QLabel("")
  23. self.empty_label1.setFixedWidth(70)
  24. self.empty_label2 = QtWidgets.QLabel("")
  25. self.empty_label2.setFixedWidth(70)
  26. self.empty_label3 = QtWidgets.QLabel("")
  27. self.empty_label3.setFixedWidth(70)
  28. self.empty_label4 = QtWidgets.QLabel("")
  29. self.empty_label4.setFixedWidth(70)
  30. self.transform_lay.addWidget(self.empty_label)
  31. ## Rotate Title
  32. rotate_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.rotateName)
  33. self.transform_lay.addWidget(rotate_title_label)
  34. ## Layout
  35. form_layout = QtWidgets.QFormLayout()
  36. self.transform_lay.addLayout(form_layout)
  37. form_child = QtWidgets.QFormLayout()
  38. self.rotate_label = QtWidgets.QLabel("Angle:")
  39. self.rotate_label.setToolTip(
  40. "Angle for Rotation action, in degrees.\n"
  41. "Float number between -360 and 359.\n"
  42. "Positive numbers for CW motion.\n"
  43. "Negative numbers for CCW motion."
  44. )
  45. self.rotate_label.setFixedWidth(50)
  46. self.rotate_entry = FCEntry()
  47. self.rotate_entry.setFixedWidth(60)
  48. self.rotate_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
  49. self.rotate_button = FCButton()
  50. self.rotate_button.set_value("Rotate")
  51. self.rotate_button.setToolTip(
  52. "Rotate the selected object(s).\n"
  53. "The point of reference is the middle of\n"
  54. "the bounding box for all selected objects."
  55. )
  56. self.rotate_button.setFixedWidth(60)
  57. form_child.addRow(self.rotate_entry, self.rotate_button)
  58. form_layout.addRow(self.rotate_label, form_child)
  59. self.transform_lay.addWidget(self.empty_label1)
  60. ## Skew Title
  61. skew_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.skewName)
  62. self.transform_lay.addWidget(skew_title_label)
  63. ## Form Layout
  64. form1_layout = QtWidgets.QFormLayout()
  65. self.transform_lay.addLayout(form1_layout)
  66. form1_child_1 = QtWidgets.QFormLayout()
  67. form1_child_2 = QtWidgets.QFormLayout()
  68. self.skewx_label = QtWidgets.QLabel("Angle X:")
  69. self.skewx_label.setToolTip(
  70. "Angle for Skew action, in degrees.\n"
  71. "Float number between -360 and 359."
  72. )
  73. self.skewx_label.setFixedWidth(50)
  74. self.skewx_entry = FCEntry()
  75. self.skewx_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
  76. self.skewx_entry.setFixedWidth(60)
  77. self.skewx_button = FCButton()
  78. self.skewx_button.set_value("Skew X")
  79. self.skewx_button.setToolTip(
  80. "Skew/shear the selected object(s).\n"
  81. "The point of reference is the middle of\n"
  82. "the bounding box for all selected objects.")
  83. self.skewx_button.setFixedWidth(60)
  84. self.skewy_label = QtWidgets.QLabel("Angle Y:")
  85. self.skewy_label.setToolTip(
  86. "Angle for Skew action, in degrees.\n"
  87. "Float number between -360 and 359."
  88. )
  89. self.skewy_label.setFixedWidth(50)
  90. self.skewy_entry = FCEntry()
  91. self.skewy_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
  92. self.skewy_entry.setFixedWidth(60)
  93. self.skewy_button = FCButton()
  94. self.skewy_button.set_value("Skew Y")
  95. self.skewy_button.setToolTip(
  96. "Skew/shear the selected object(s).\n"
  97. "The point of reference is the middle of\n"
  98. "the bounding box for all selected objects.")
  99. self.skewy_button.setFixedWidth(60)
  100. form1_child_1.addRow(self.skewx_entry, self.skewx_button)
  101. form1_child_2.addRow(self.skewy_entry, self.skewy_button)
  102. form1_layout.addRow(self.skewx_label, form1_child_1)
  103. form1_layout.addRow(self.skewy_label, form1_child_2)
  104. self.transform_lay.addWidget(self.empty_label2)
  105. ## Scale Title
  106. scale_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.scaleName)
  107. self.transform_lay.addWidget(scale_title_label)
  108. ## Form Layout
  109. form2_layout = QtWidgets.QFormLayout()
  110. self.transform_lay.addLayout(form2_layout)
  111. form2_child_1 = QtWidgets.QFormLayout()
  112. form2_child_2 = QtWidgets.QFormLayout()
  113. self.scalex_label = QtWidgets.QLabel("Factor X:")
  114. self.scalex_label.setToolTip(
  115. "Factor for Scale action over X axis."
  116. )
  117. self.scalex_label.setFixedWidth(50)
  118. self.scalex_entry = FCEntry()
  119. self.scalex_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
  120. self.scalex_entry.setFixedWidth(60)
  121. self.scalex_button = FCButton()
  122. self.scalex_button.set_value("Scale X")
  123. self.scalex_button.setToolTip(
  124. "Scale the selected object(s).\n"
  125. "The point of reference depends on \n"
  126. "the Scale reference checkbox state.")
  127. self.scalex_button.setFixedWidth(60)
  128. self.scaley_label = QtWidgets.QLabel("Factor Y:")
  129. self.scaley_label.setToolTip(
  130. "Factor for Scale action over Y axis."
  131. )
  132. self.scaley_label.setFixedWidth(50)
  133. self.scaley_entry = FCEntry()
  134. self.scaley_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
  135. self.scaley_entry.setFixedWidth(60)
  136. self.scaley_button = FCButton()
  137. self.scaley_button.set_value("Scale Y")
  138. self.scaley_button.setToolTip(
  139. "Scale the selected object(s).\n"
  140. "The point of reference depends on \n"
  141. "the Scale reference checkbox state.")
  142. self.scaley_button.setFixedWidth(60)
  143. self.scale_link_cb = FCCheckBox()
  144. self.scale_link_cb.set_value(True)
  145. self.scale_link_cb.setText("Link")
  146. self.scale_link_cb.setToolTip(
  147. "Scale the selected object(s)\n"
  148. "using the Scale Factor X for both axis.")
  149. self.scale_link_cb.setFixedWidth(50)
  150. self.scale_zero_ref_cb = FCCheckBox()
  151. self.scale_zero_ref_cb.set_value(True)
  152. self.scale_zero_ref_cb.setText("Scale Reference")
  153. self.scale_zero_ref_cb.setToolTip(
  154. "Scale the selected object(s)\n"
  155. "using the origin reference when checked,\n"
  156. "and the center of the biggest bounding box\n"
  157. "of the selected objects when unchecked.")
  158. form2_child_1.addRow(self.scalex_entry, self.scalex_button)
  159. form2_child_2.addRow(self.scaley_entry, self.scaley_button)
  160. form2_layout.addRow(self.scalex_label, form2_child_1)
  161. form2_layout.addRow(self.scaley_label, form2_child_2)
  162. form2_layout.addRow(self.scale_link_cb, self.scale_zero_ref_cb)
  163. self.ois_scale = OptionalInputSection(self.scale_link_cb, [self.scaley_entry, self.scaley_button], logic=False)
  164. self.transform_lay.addWidget(self.empty_label3)
  165. ## Offset Title
  166. offset_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.offsetName)
  167. self.transform_lay.addWidget(offset_title_label)
  168. ## Form Layout
  169. form3_layout = QtWidgets.QFormLayout()
  170. self.transform_lay.addLayout(form3_layout)
  171. form3_child_1 = QtWidgets.QFormLayout()
  172. form3_child_2 = QtWidgets.QFormLayout()
  173. self.offx_label = QtWidgets.QLabel("Value X:")
  174. self.offx_label.setToolTip(
  175. "Value for Offset action on X axis."
  176. )
  177. self.offx_label.setFixedWidth(50)
  178. self.offx_entry = FCEntry()
  179. self.offx_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
  180. self.offx_entry.setFixedWidth(60)
  181. self.offx_button = FCButton()
  182. self.offx_button.set_value("Offset X")
  183. self.offx_button.setToolTip(
  184. "Offset the selected object(s).\n"
  185. "The point of reference is the middle of\n"
  186. "the bounding box for all selected objects.\n")
  187. self.offx_button.setFixedWidth(60)
  188. self.offy_label = QtWidgets.QLabel("Value Y:")
  189. self.offy_label.setToolTip(
  190. "Value for Offset action on Y axis."
  191. )
  192. self.offy_label.setFixedWidth(50)
  193. self.offy_entry = FCEntry()
  194. self.offy_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
  195. self.offy_entry.setFixedWidth(60)
  196. self.offy_button = FCButton()
  197. self.offy_button.set_value("Offset Y")
  198. self.offy_button.setToolTip(
  199. "Offset the selected object(s).\n"
  200. "The point of reference is the middle of\n"
  201. "the bounding box for all selected objects.\n")
  202. self.offy_button.setFixedWidth(60)
  203. form3_child_1.addRow(self.offx_entry, self.offx_button)
  204. form3_child_2.addRow(self.offy_entry, self.offy_button)
  205. form3_layout.addRow(self.offx_label, form3_child_1)
  206. form3_layout.addRow(self.offy_label, form3_child_2)
  207. self.transform_lay.addWidget(self.empty_label4)
  208. ## Flip Title
  209. flip_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.flipName)
  210. self.transform_lay.addWidget(flip_title_label)
  211. ## Form Layout
  212. form4_layout = QtWidgets.QFormLayout()
  213. self.transform_lay.addLayout(form4_layout)
  214. form4_child = QtWidgets.QFormLayout()
  215. form4_child_1 = QtWidgets.QFormLayout()
  216. self.flipx_button = FCButton()
  217. self.flipx_button.set_value("Flip on X")
  218. self.flipx_button.setToolTip(
  219. "Flip the selected object(s) over the X axis.\n"
  220. "Does not create a new object.\n "
  221. )
  222. self.flipx_button.setFixedWidth(60)
  223. self.flipy_button = FCButton()
  224. self.flipy_button.set_value("Flip on Y")
  225. self.flipy_button.setToolTip(
  226. "Flip the selected object(s) over the X axis.\n"
  227. "Does not create a new object.\n "
  228. )
  229. self.flipy_button.setFixedWidth(60)
  230. self.flip_ref_cb = FCCheckBox()
  231. self.flip_ref_cb.set_value(True)
  232. self.flip_ref_cb.setText("Ref Pt")
  233. self.flip_ref_cb.setToolTip(
  234. "Flip the selected object(s)\n"
  235. "around the point in Point Entry Field.\n"
  236. "\n"
  237. "The point coordinates can be captured by\n"
  238. "left click on canvas together with pressing\n"
  239. "SHIFT key. \n"
  240. "Then click Add button to insert coordinates.\n"
  241. "Or enter the coords in format (x, y) in the\n"
  242. "Point Entry field and click Flip on X(Y)")
  243. self.flip_ref_cb.setFixedWidth(50)
  244. self.flip_ref_label = QtWidgets.QLabel("Point:")
  245. self.flip_ref_label.setToolTip(
  246. "Coordinates in format (x, y) used as reference for mirroring.\n"
  247. "The 'x' in (x, y) will be used when using Flip on X and\n"
  248. "the 'y' in (x, y) will be used when using Flip on Y and"
  249. )
  250. self.flip_ref_label.setFixedWidth(50)
  251. self.flip_ref_entry = EvalEntry2("(0, 0)")
  252. self.flip_ref_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
  253. self.flip_ref_entry.setFixedWidth(60)
  254. self.flip_ref_button = FCButton()
  255. self.flip_ref_button.set_value("Add")
  256. self.flip_ref_button.setToolTip(
  257. "The point coordinates can be captured by\n"
  258. "left click on canvas together with pressing\n"
  259. "SHIFT key. Then click Add button to insert.")
  260. self.flip_ref_button.setFixedWidth(60)
  261. form4_child.addRow(self.flipx_button, self.flipy_button)
  262. form4_child_1.addRow(self.flip_ref_entry, self.flip_ref_button)
  263. form4_layout.addRow(self.empty_label, form4_child)
  264. form4_layout.addRow(self.flip_ref_cb)
  265. form4_layout.addRow(self.flip_ref_label, form4_child_1)
  266. self.ois_flip = OptionalInputSection(self.flip_ref_cb,
  267. [self.flip_ref_entry, self.flip_ref_button], logic=True)
  268. self.transform_lay.addStretch()
  269. ## Signals
  270. self.rotate_button.clicked.connect(self.on_rotate)
  271. self.skewx_button.clicked.connect(self.on_skewx)
  272. self.skewy_button.clicked.connect(self.on_skewy)
  273. self.scalex_button.clicked.connect(self.on_scalex)
  274. self.scaley_button.clicked.connect(self.on_scaley)
  275. self.offx_button.clicked.connect(self.on_offx)
  276. self.offy_button.clicked.connect(self.on_offy)
  277. self.flipx_button.clicked.connect(self.on_flipx)
  278. self.flipy_button.clicked.connect(self.on_flipy)
  279. self.flip_ref_button.clicked.connect(self.on_flip_add_coords)
  280. self.rotate_entry.returnPressed.connect(self.on_rotate)
  281. self.skewx_entry.returnPressed.connect(self.on_skewx)
  282. self.skewy_entry.returnPressed.connect(self.on_skewy)
  283. self.scalex_entry.returnPressed.connect(self.on_scalex)
  284. self.scaley_entry.returnPressed.connect(self.on_scaley)
  285. self.offx_entry.returnPressed.connect(self.on_offx)
  286. self.offy_entry.returnPressed.connect(self.on_offy)
  287. ## Initialize form
  288. self.rotate_entry.set_value('0')
  289. self.skewx_entry.set_value('0')
  290. self.skewy_entry.set_value('0')
  291. self.scalex_entry.set_value('1')
  292. self.scaley_entry.set_value('1')
  293. self.offx_entry.set_value('0')
  294. self.offy_entry.set_value('0')
  295. self.flip_ref_cb.setChecked(False)
  296. def run(self):
  297. FlatCAMTool.run(self)
  298. self.app.ui.notebook.setTabText(2, "Transform Tool")
  299. def on_rotate(self):
  300. try:
  301. value = float(self.rotate_entry.get_value())
  302. except Exception as e:
  303. self.app.inform.emit("[error] Failed to rotate due of: %s" % str(e))
  304. return
  305. self.app.worker_task.emit({'fcn': self.on_rotate_action,
  306. 'params': [value]})
  307. # self.on_rotate_action(value)
  308. return
  309. def on_flipx(self):
  310. # self.on_flip("Y")
  311. axis = 'Y'
  312. self.app.worker_task.emit({'fcn': self.on_flip,
  313. 'params': [axis]})
  314. return
  315. def on_flipy(self):
  316. # self.on_flip("X")
  317. axis = 'X'
  318. self.app.worker_task.emit({'fcn': self.on_flip,
  319. 'params': [axis]})
  320. return
  321. def on_flip_add_coords(self):
  322. val = self.app.defaults["global_point_clipboard_format"] % (self.app.pos[0], self.app.pos[1])
  323. self.flip_ref_entry.set_value(val)
  324. def on_skewx(self):
  325. try:
  326. value = float(self.skewx_entry.get_value())
  327. except:
  328. self.app.inform.emit("[warning_notcl] No value for Skew!")
  329. return
  330. # self.on_skew("X", value)
  331. axis = 'X'
  332. self.app.worker_task.emit({'fcn': self.on_skew,
  333. 'params': [axis, value]})
  334. return
  335. def on_skewy(self):
  336. try:
  337. value = float(self.skewy_entry.get_value())
  338. except:
  339. self.app.inform.emit("[warning_notcl] No value for Skew!")
  340. return
  341. # self.on_skew("Y", value)
  342. axis = 'Y'
  343. self.app.worker_task.emit({'fcn': self.on_skew,
  344. 'params': [axis, value]})
  345. return
  346. def on_scalex(self):
  347. try:
  348. xvalue = float(self.scalex_entry.get_value())
  349. except:
  350. self.app.inform.emit("[warning_notcl] No value for Scale!")
  351. return
  352. # scaling to zero has no sense so we remove it, because scaling with 1 does nothing
  353. if xvalue == 0:
  354. xvalue = 1
  355. if self.scale_link_cb.get_value():
  356. yvalue = xvalue
  357. else:
  358. yvalue = 1
  359. axis = 'X'
  360. point = (0, 0)
  361. if self.scale_zero_ref_cb.get_value():
  362. self.app.worker_task.emit({'fcn': self.on_scale,
  363. 'params': [axis, xvalue, yvalue, point]})
  364. # self.on_scale("X", xvalue, yvalue, point=(0,0))
  365. else:
  366. # self.on_scale("X", xvalue, yvalue)
  367. self.app.worker_task.emit({'fcn': self.on_scale,
  368. 'params': [axis, xvalue, yvalue]})
  369. return
  370. def on_scaley(self):
  371. xvalue = 1
  372. try:
  373. yvalue = float(self.scaley_entry.get_value())
  374. except:
  375. self.app.inform.emit("[warning_notcl] No value for Scale!")
  376. return
  377. # scaling to zero has no sense so we remove it, because scaling with 1 does nothing
  378. if yvalue == 0:
  379. yvalue = 1
  380. axis = 'Y'
  381. point = (0, 0)
  382. if self.scale_zero_ref_cb.get_value():
  383. self.app.worker_task.emit({'fcn': self.on_scale,
  384. 'params': [axis, xvalue, yvalue, point]})
  385. # self.on_scale("Y", xvalue, yvalue, point=(0,0))
  386. else:
  387. # self.on_scale("Y", xvalue, yvalue)
  388. self.app.worker_task.emit({'fcn': self.on_scale,
  389. 'params': [axis, xvalue, yvalue]})
  390. return
  391. def on_offx(self):
  392. try:
  393. value = float(self.offx_entry.get_value())
  394. except:
  395. self.app.inform.emit("[warning_notcl] No value for Offset!")
  396. return
  397. # self.on_offset("X", value)
  398. axis = 'X'
  399. self.app.worker_task.emit({'fcn': self.on_offset,
  400. 'params': [axis, value]})
  401. return
  402. def on_offy(self):
  403. try:
  404. value = float(self.offy_entry.get_value())
  405. except:
  406. self.app.inform.emit("[warning_notcl] No value for Offset!")
  407. return
  408. # self.on_offset("Y", value)
  409. axis = 'Y'
  410. self.app.worker_task.emit({'fcn': self.on_offset,
  411. 'params': [axis, value]})
  412. return
  413. def on_rotate_action(self, num):
  414. obj_list = self.app.collection.get_selected()
  415. xminlist = []
  416. yminlist = []
  417. xmaxlist = []
  418. ymaxlist = []
  419. if not obj_list:
  420. self.app.inform.emit("[warning_notcl] No object selected. Please Select an object to rotate!")
  421. return
  422. else:
  423. with self.app.proc_container.new("Appying Rotate"):
  424. try:
  425. # first get a bounding box to fit all
  426. for obj in obj_list:
  427. if isinstance(obj, FlatCAMCNCjob):
  428. pass
  429. else:
  430. xmin, ymin, xmax, ymax = obj.bounds()
  431. xminlist.append(xmin)
  432. yminlist.append(ymin)
  433. xmaxlist.append(xmax)
  434. ymaxlist.append(ymax)
  435. # get the minimum x,y and maximum x,y for all objects selected
  436. xminimal = min(xminlist)
  437. yminimal = min(yminlist)
  438. xmaximal = max(xmaxlist)
  439. ymaximal = max(ymaxlist)
  440. self.app.progress.emit(20)
  441. for sel_obj in obj_list:
  442. px = 0.5 * (xminimal + xmaximal)
  443. py = 0.5 * (yminimal + ymaximal)
  444. if isinstance(sel_obj, FlatCAMCNCjob):
  445. self.app.inform.emit("CNCJob objects can't be rotated.")
  446. else:
  447. sel_obj.rotate(-num, point=(px, py))
  448. sel_obj.plot()
  449. self.app.object_changed.emit(sel_obj)
  450. # add information to the object that it was changed and how much
  451. sel_obj.options['rotate'] = num
  452. self.app.inform.emit('Object(s) were rotated ...')
  453. self.app.progress.emit(100)
  454. except Exception as e:
  455. self.app.inform.emit("[error_notcl] Due of %s, rotation movement was not executed." % str(e))
  456. return
  457. def on_flip(self, axis):
  458. obj_list = self.app.collection.get_selected()
  459. xminlist = []
  460. yminlist = []
  461. xmaxlist = []
  462. ymaxlist = []
  463. if not obj_list:
  464. self.app.inform.emit("[warning_notcl] No object selected. Please Select an object to flip!")
  465. return
  466. else:
  467. with self.app.proc_container.new("Applying Flip"):
  468. try:
  469. # get mirroring coords from the point entry
  470. if self.flip_ref_cb.isChecked():
  471. px, py = eval('{}'.format(self.flip_ref_entry.text()))
  472. # get mirroing coords from the center of an all-enclosing bounding box
  473. else:
  474. # first get a bounding box to fit all
  475. for obj in obj_list:
  476. if isinstance(obj, FlatCAMCNCjob):
  477. pass
  478. else:
  479. xmin, ymin, xmax, ymax = obj.bounds()
  480. xminlist.append(xmin)
  481. yminlist.append(ymin)
  482. xmaxlist.append(xmax)
  483. ymaxlist.append(ymax)
  484. # get the minimum x,y and maximum x,y for all objects selected
  485. xminimal = min(xminlist)
  486. yminimal = min(yminlist)
  487. xmaximal = max(xmaxlist)
  488. ymaximal = max(ymaxlist)
  489. px = 0.5 * (xminimal + xmaximal)
  490. py = 0.5 * (yminimal + ymaximal)
  491. self.app.progress.emit(20)
  492. # execute mirroring
  493. for obj in obj_list:
  494. if isinstance(obj, FlatCAMCNCjob):
  495. self.app.inform.emit("CNCJob objects can't be mirrored/flipped.")
  496. else:
  497. if axis is 'X':
  498. obj.mirror('X', (px, py))
  499. # add information to the object that it was changed and how much
  500. # the axis is reversed because of the reference
  501. if 'mirror_y' in obj.options:
  502. obj.options['mirror_y'] = not obj.options['mirror_y']
  503. else:
  504. obj.options['mirror_y'] = True
  505. obj.plot()
  506. self.app.inform.emit('Flipped on the Y axis ...')
  507. elif axis is 'Y':
  508. obj.mirror('Y', (px, py))
  509. # add information to the object that it was changed and how much
  510. # the axis is reversed because of the reference
  511. if 'mirror_x' in obj.options:
  512. obj.options['mirror_x'] = not obj.options['mirror_x']
  513. else:
  514. obj.options['mirror_x'] = True
  515. obj.plot()
  516. self.app.inform.emit('Flipped on the X axis ...')
  517. self.app.object_changed.emit(obj)
  518. self.app.progress.emit(100)
  519. except Exception as e:
  520. self.app.inform.emit("[error_notcl] Due of %s, Flip action was not executed." % str(e))
  521. return
  522. def on_skew(self, axis, num):
  523. obj_list = self.app.collection.get_selected()
  524. xminlist = []
  525. yminlist = []
  526. if not obj_list:
  527. self.app.inform.emit("[warning_notcl] No object selected. Please Select an object to shear/skew!")
  528. return
  529. else:
  530. with self.app.proc_container.new("Applying Skew"):
  531. try:
  532. # first get a bounding box to fit all
  533. for obj in obj_list:
  534. if isinstance(obj, FlatCAMCNCjob):
  535. pass
  536. else:
  537. xmin, ymin, xmax, ymax = obj.bounds()
  538. xminlist.append(xmin)
  539. yminlist.append(ymin)
  540. # get the minimum x,y and maximum x,y for all objects selected
  541. xminimal = min(xminlist)
  542. yminimal = min(yminlist)
  543. self.app.progress.emit(20)
  544. for obj in obj_list:
  545. if isinstance(obj, FlatCAMCNCjob):
  546. self.app.inform.emit("CNCJob objects can't be skewed.")
  547. else:
  548. if axis is 'X':
  549. obj.skew(num, 0, point=(xminimal, yminimal))
  550. # add information to the object that it was changed and how much
  551. obj.options['skew_x'] = num
  552. elif axis is 'Y':
  553. obj.skew(0, num, point=(xminimal, yminimal))
  554. # add information to the object that it was changed and how much
  555. obj.options['skew_y'] = num
  556. obj.plot()
  557. self.app.object_changed.emit(obj)
  558. self.app.inform.emit('Object(s) were skewed on %s axis ...' % str(axis))
  559. self.app.progress.emit(100)
  560. except Exception as e:
  561. self.app.inform.emit("[error_notcl] Due of %s, Skew action was not executed." % str(e))
  562. return
  563. def on_scale(self, axis, xfactor, yfactor, point=None):
  564. obj_list = self.app.collection.get_selected()
  565. xminlist = []
  566. yminlist = []
  567. xmaxlist = []
  568. ymaxlist = []
  569. if not obj_list:
  570. self.app.inform.emit("[warning_notcl] No object selected. Please Select an object to scale!")
  571. return
  572. else:
  573. with self.app.proc_container.new("Applying Scale"):
  574. try:
  575. # first get a bounding box to fit all
  576. for obj in obj_list:
  577. if isinstance(obj, FlatCAMCNCjob):
  578. pass
  579. else:
  580. xmin, ymin, xmax, ymax = obj.bounds()
  581. xminlist.append(xmin)
  582. yminlist.append(ymin)
  583. xmaxlist.append(xmax)
  584. ymaxlist.append(ymax)
  585. # get the minimum x,y and maximum x,y for all objects selected
  586. xminimal = min(xminlist)
  587. yminimal = min(yminlist)
  588. xmaximal = max(xmaxlist)
  589. ymaximal = max(ymaxlist)
  590. self.app.progress.emit(20)
  591. if point is None:
  592. px = 0.5 * (xminimal + xmaximal)
  593. py = 0.5 * (yminimal + ymaximal)
  594. else:
  595. px = 0
  596. py = 0
  597. for obj in obj_list:
  598. if isinstance(obj, FlatCAMCNCjob):
  599. self.app.inform.emit("CNCJob objects can't be scaled.")
  600. else:
  601. obj.scale(xfactor, yfactor, point=(px, py))
  602. # add information to the object that it was changed and how much
  603. obj.options['scale_x'] = xfactor
  604. obj.options['scale_y'] = yfactor
  605. obj.plot()
  606. self.app.object_changed.emit(obj)
  607. self.app.inform.emit('Object(s) were scaled on %s axis ...' % str(axis))
  608. self.app.progress.emit(100)
  609. except Exception as e:
  610. self.app.inform.emit("[error_notcl] Due of %s, Scale action was not executed." % str(e))
  611. return
  612. def on_offset(self, axis, num):
  613. obj_list = self.app.collection.get_selected()
  614. xminlist = []
  615. yminlist = []
  616. if not obj_list:
  617. self.app.inform.emit("[warning_notcl] No object selected. Please Select an object to offset!")
  618. return
  619. else:
  620. with self.app.proc_container.new("Applying Offset"):
  621. try:
  622. # first get a bounding box to fit all
  623. for obj in obj_list:
  624. if isinstance(obj, FlatCAMCNCjob):
  625. pass
  626. else:
  627. xmin, ymin, xmax, ymax = obj.bounds()
  628. xminlist.append(xmin)
  629. yminlist.append(ymin)
  630. # get the minimum x,y and maximum x,y for all objects selected
  631. xminimal = min(xminlist)
  632. yminimal = min(yminlist)
  633. self.app.progress.emit(20)
  634. for obj in obj_list:
  635. if isinstance(obj, FlatCAMCNCjob):
  636. self.app.inform.emit("CNCJob objects can't be offseted.")
  637. else:
  638. if axis is 'X':
  639. obj.offset((num, 0))
  640. # add information to the object that it was changed and how much
  641. obj.options['offset_x'] = num
  642. elif axis is 'Y':
  643. obj.offset((0, num))
  644. # add information to the object that it was changed and how much
  645. obj.options['offset_y'] = num
  646. obj.plot()
  647. self.app.object_changed.emit(obj)
  648. self.app.inform.emit('Object(s) were offseted on %s axis ...' % str(axis))
  649. self.app.progress.emit(100)
  650. except Exception as e:
  651. self.app.inform.emit("[error_notcl] Due of %s, Offset action was not executed." % str(e))
  652. return
  653. # end of file