ToolTransform.py 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961
  1. # ##########################################################
  2. # FlatCAM: 2D Post-processing for Manufacturing #
  3. # File Author: Marius Adrian Stanciu (c) #
  4. # Date: 3/10/2019 #
  5. # MIT Licence #
  6. # ##########################################################
  7. from PyQt5 import QtWidgets
  8. from FlatCAMTool import FlatCAMTool
  9. from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, FCButton, OptionalInputSection, EvalEntry2
  10. from FlatCAMObj import FlatCAMCNCjob
  11. import gettext
  12. import FlatCAMTranslation as fcTranslate
  13. import builtins
  14. fcTranslate.apply_language('strings')
  15. if '_' not in builtins.__dict__:
  16. _ = gettext.gettext
  17. class ToolTransform(FlatCAMTool):
  18. toolName = _("Object Transform")
  19. rotateName = _("Rotate")
  20. skewName = _("Skew/Shear")
  21. scaleName = _("Scale")
  22. flipName = _("Mirror (Flip)")
  23. offsetName = _("Offset")
  24. bufferName = _("Buffer")
  25. def __init__(self, app):
  26. FlatCAMTool.__init__(self, app)
  27. self.decimals = self.app.decimals
  28. # ## Title
  29. title_label = QtWidgets.QLabel("%s" % self.toolName)
  30. title_label.setStyleSheet("""
  31. QLabel
  32. {
  33. font-size: 16px;
  34. font-weight: bold;
  35. }
  36. """)
  37. self.layout.addWidget(title_label)
  38. self.layout.addWidget(QtWidgets.QLabel(''))
  39. # ## Layout
  40. grid0 = QtWidgets.QGridLayout()
  41. self.layout.addLayout(grid0)
  42. grid0.setColumnStretch(0, 0)
  43. grid0.setColumnStretch(1, 1)
  44. grid0.setColumnStretch(2, 0)
  45. grid0.addWidget(QtWidgets.QLabel(''))
  46. # ## Rotate Title
  47. rotate_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.rotateName)
  48. grid0.addWidget(rotate_title_label, 0, 0, 1, 3)
  49. self.rotate_label = QtWidgets.QLabel('%s:' % _("Angle"))
  50. self.rotate_label.setToolTip(
  51. _("Angle for Rotation action, in degrees.\n"
  52. "Float number between -360 and 359.\n"
  53. "Positive numbers for CW motion.\n"
  54. "Negative numbers for CCW motion.")
  55. )
  56. self.rotate_entry = FCDoubleSpinner(callback=self.confirmation_message)
  57. self.rotate_entry.set_precision(self.decimals)
  58. self.rotate_entry.setSingleStep(45)
  59. self.rotate_entry.setWrapping(True)
  60. self.rotate_entry.set_range(-360, 360)
  61. # self.rotate_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
  62. self.rotate_button = FCButton()
  63. self.rotate_button.setToolTip(
  64. _("Rotate the selected object(s).\n"
  65. "The point of reference is the middle of\n"
  66. "the bounding box for all selected objects.")
  67. )
  68. self.rotate_button.setMinimumWidth(90)
  69. grid0.addWidget(self.rotate_label, 1, 0)
  70. grid0.addWidget(self.rotate_entry, 1, 1)
  71. grid0.addWidget(self.rotate_button, 1, 2)
  72. separator_line = QtWidgets.QFrame()
  73. separator_line.setFrameShape(QtWidgets.QFrame.HLine)
  74. separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
  75. grid0.addWidget(separator_line, 2, 0, 1, 3)
  76. # ## Skew Title
  77. skew_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.skewName)
  78. grid0.addWidget(skew_title_label, 3, 0, 1, 3)
  79. self.skewx_label = QtWidgets.QLabel('%s:' % _("X angle"))
  80. self.skewx_label.setToolTip(
  81. _("Angle for Skew action, in degrees.\n"
  82. "Float number between -360 and 360.")
  83. )
  84. self.skewx_entry = FCDoubleSpinner(callback=self.confirmation_message)
  85. # self.skewx_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
  86. self.skewx_entry.set_precision(self.decimals)
  87. self.skewx_entry.set_range(-360, 360)
  88. self.skewx_button = FCButton()
  89. self.skewx_button.setToolTip(
  90. _("Skew/shear the selected object(s).\n"
  91. "The point of reference is the middle of\n"
  92. "the bounding box for all selected objects."))
  93. self.skewx_button.setMinimumWidth(90)
  94. grid0.addWidget(self.skewx_label, 4, 0)
  95. grid0.addWidget(self.skewx_entry, 4, 1)
  96. grid0.addWidget(self.skewx_button, 4, 2)
  97. self.skewy_label = QtWidgets.QLabel('%s:' % _("Y angle"))
  98. self.skewy_label.setToolTip(
  99. _("Angle for Skew action, in degrees.\n"
  100. "Float number between -360 and 360.")
  101. )
  102. self.skewy_entry = FCDoubleSpinner(callback=self.confirmation_message)
  103. # self.skewy_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
  104. self.skewy_entry.set_precision(self.decimals)
  105. self.skewy_entry.set_range(-360, 360)
  106. self.skewy_button = FCButton()
  107. self.skewy_button.setToolTip(
  108. _("Skew/shear the selected object(s).\n"
  109. "The point of reference is the middle of\n"
  110. "the bounding box for all selected objects."))
  111. self.skewy_button.setMinimumWidth(90)
  112. grid0.addWidget(self.skewy_label, 5, 0)
  113. grid0.addWidget(self.skewy_entry, 5, 1)
  114. grid0.addWidget(self.skewy_button, 5, 2)
  115. separator_line = QtWidgets.QFrame()
  116. separator_line.setFrameShape(QtWidgets.QFrame.HLine)
  117. separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
  118. grid0.addWidget(separator_line, 6, 0, 1, 3)
  119. # ## Scale Title
  120. scale_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.scaleName)
  121. grid0.addWidget(scale_title_label, 7, 0, 1, 3)
  122. self.scalex_label = QtWidgets.QLabel('%s:' % _("X factor"))
  123. self.scalex_label.setToolTip(
  124. _("Factor for scaling on X axis.")
  125. )
  126. self.scalex_entry = FCDoubleSpinner(callback=self.confirmation_message)
  127. # self.scalex_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
  128. self.scalex_entry.set_precision(self.decimals)
  129. self.scalex_entry.setMinimum(-1e6)
  130. self.scalex_button = FCButton()
  131. self.scalex_button.setToolTip(
  132. _("Scale the selected object(s).\n"
  133. "The point of reference depends on \n"
  134. "the Scale reference checkbox state."))
  135. self.scalex_button.setMinimumWidth(90)
  136. grid0.addWidget(self.scalex_label, 8, 0)
  137. grid0.addWidget(self.scalex_entry, 8, 1)
  138. grid0.addWidget(self.scalex_button, 8, 2)
  139. self.scaley_label = QtWidgets.QLabel('%s:' % _("Y factor"))
  140. self.scaley_label.setToolTip(
  141. _("Factor for scaling on Y axis.")
  142. )
  143. self.scaley_entry = FCDoubleSpinner(callback=self.confirmation_message)
  144. # self.scaley_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
  145. self.scaley_entry.set_precision(self.decimals)
  146. self.scaley_entry.setMinimum(-1e6)
  147. self.scaley_button = FCButton()
  148. self.scaley_button.setToolTip(
  149. _("Scale the selected object(s).\n"
  150. "The point of reference depends on \n"
  151. "the Scale reference checkbox state."))
  152. self.scaley_button.setMinimumWidth(90)
  153. grid0.addWidget(self.scaley_label, 9, 0)
  154. grid0.addWidget(self.scaley_entry, 9, 1)
  155. grid0.addWidget(self.scaley_button, 9, 2)
  156. self.scale_link_cb = FCCheckBox()
  157. self.scale_link_cb.setText(_("Link"))
  158. self.scale_link_cb.setToolTip(
  159. _("Scale the selected object(s)\n"
  160. "using the Scale_X factor for both axis.")
  161. )
  162. self.scale_zero_ref_cb = FCCheckBox()
  163. self.scale_zero_ref_cb.setText('%s' % _("Scale Reference"))
  164. self.scale_zero_ref_cb.setToolTip(
  165. _("Scale the selected object(s)\n"
  166. "using the origin reference when checked,\n"
  167. "and the center of the biggest bounding box\n"
  168. "of the selected objects when unchecked."))
  169. self.ois_scale = OptionalInputSection(self.scale_link_cb, [self.scaley_entry, self.scaley_button], logic=False)
  170. grid0.addWidget(self.scale_link_cb, 10, 0)
  171. grid0.addWidget(self.scale_zero_ref_cb, 10, 1, 1, 2)
  172. separator_line = QtWidgets.QFrame()
  173. separator_line.setFrameShape(QtWidgets.QFrame.HLine)
  174. separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
  175. grid0.addWidget(separator_line, 11, 0, 1, 3)
  176. # ## Offset Title
  177. offset_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.offsetName)
  178. grid0.addWidget(offset_title_label, 12, 0, 1, 3)
  179. self.offx_label = QtWidgets.QLabel('%s:' % _("X val"))
  180. self.offx_label.setToolTip(
  181. _("Distance to offset on X axis. In current units.")
  182. )
  183. self.offx_entry = FCDoubleSpinner(callback=self.confirmation_message)
  184. # self.offx_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
  185. self.offx_entry.set_precision(self.decimals)
  186. self.offx_entry.setMinimum(-1e6)
  187. self.offx_button = FCButton()
  188. self.offx_button.setToolTip(
  189. _("Offset the selected object(s).\n"
  190. "The point of reference is the middle of\n"
  191. "the bounding box for all selected objects.\n"))
  192. self.offx_button.setMinimumWidth(90)
  193. grid0.addWidget(self.offx_label, 13, 0)
  194. grid0.addWidget(self.offx_entry, 13, 1)
  195. grid0.addWidget(self.offx_button, 13, 2)
  196. self.offy_label = QtWidgets.QLabel('%s:' % _("Y val"))
  197. self.offy_label.setToolTip(
  198. _("Distance to offset on Y axis. In current units.")
  199. )
  200. self.offy_entry = FCDoubleSpinner(callback=self.confirmation_message)
  201. # self.offy_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
  202. self.offy_entry.set_precision(self.decimals)
  203. self.offy_entry.setMinimum(-1e6)
  204. self.offy_button = FCButton()
  205. self.offy_button.setToolTip(
  206. _("Offset the selected object(s).\n"
  207. "The point of reference is the middle of\n"
  208. "the bounding box for all selected objects.\n"))
  209. self.offy_button.setMinimumWidth(90)
  210. grid0.addWidget(self.offy_label, 14, 0)
  211. grid0.addWidget(self.offy_entry, 14, 1)
  212. grid0.addWidget(self.offy_button, 14, 2)
  213. separator_line = QtWidgets.QFrame()
  214. separator_line.setFrameShape(QtWidgets.QFrame.HLine)
  215. separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
  216. grid0.addWidget(separator_line, 15, 0, 1, 3)
  217. # ## Flip Title
  218. flip_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.flipName)
  219. grid0.addWidget(flip_title_label, 16, 0, 1, 3)
  220. self.flipx_button = FCButton()
  221. self.flipx_button.setToolTip(
  222. _("Flip the selected object(s) over the X axis.")
  223. )
  224. self.flipy_button = FCButton()
  225. self.flipy_button.setToolTip(
  226. _("Flip the selected object(s) over the X axis.")
  227. )
  228. hlay0 = QtWidgets.QHBoxLayout()
  229. grid0.addLayout(hlay0, 17, 0, 1, 3)
  230. hlay0.addWidget(self.flipx_button)
  231. hlay0.addWidget(self.flipy_button)
  232. self.flip_ref_cb = FCCheckBox()
  233. self.flip_ref_cb.setText('%s' % _("Mirror Reference"))
  234. self.flip_ref_cb.setToolTip(
  235. _("Flip the selected object(s)\n"
  236. "around the point in Point Entry Field.\n"
  237. "\n"
  238. "The point coordinates can be captured by\n"
  239. "left click on canvas together with pressing\n"
  240. "SHIFT key. \n"
  241. "Then click Add button to insert coordinates.\n"
  242. "Or enter the coords in format (x, y) in the\n"
  243. "Point Entry field and click Flip on X(Y)"))
  244. grid0.addWidget(self.flip_ref_cb, 18, 0, 1, 3)
  245. self.flip_ref_label = QtWidgets.QLabel('%s:' % _("Ref. Point"))
  246. self.flip_ref_label.setToolTip(
  247. _("Coordinates in format (x, y) used as reference for mirroring.\n"
  248. "The 'x' in (x, y) will be used when using Flip on X and\n"
  249. "the 'y' in (x, y) will be used when using Flip on Y.")
  250. )
  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(70)
  254. self.flip_ref_button = FCButton()
  255. self.flip_ref_button.setToolTip(
  256. _("The point coordinates can be captured by\n"
  257. "left click on canvas together with pressing\n"
  258. "SHIFT key. Then click Add button to insert."))
  259. self.ois_flip = OptionalInputSection(self.flip_ref_cb, [self.flip_ref_entry, self.flip_ref_button], logic=True)
  260. hlay1 = QtWidgets.QHBoxLayout()
  261. grid0.addLayout(hlay1, 19, 0, 1, 3)
  262. hlay1.addWidget(self.flip_ref_label)
  263. hlay1.addWidget(self.flip_ref_entry)
  264. grid0.addWidget(self.flip_ref_button, 20, 0, 1, 3)
  265. separator_line = QtWidgets.QFrame()
  266. separator_line.setFrameShape(QtWidgets.QFrame.HLine)
  267. separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
  268. grid0.addWidget(separator_line, 21, 0, 1, 3)
  269. # ## Buffer Title
  270. buffer_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.bufferName)
  271. grid0.addWidget(buffer_title_label, 22, 0, 1, 3)
  272. self.buffer_label = QtWidgets.QLabel('%s:' % _("Distance"))
  273. self.buffer_label.setToolTip(
  274. _("A positive value will create the effect of dilation,\n"
  275. "while a negative value will create the effect of erosion.\n"
  276. "Each geometry element of the object will be increased\n"
  277. "or decreased with the 'distance'.")
  278. )
  279. self.buffer_entry = FCDoubleSpinner(callback=self.confirmation_message)
  280. self.buffer_entry.set_precision(self.decimals)
  281. self.buffer_entry.setSingleStep(0.1)
  282. self.buffer_entry.setWrapping(True)
  283. self.buffer_entry.set_range(-9999.9999, 9999.9999)
  284. self.buffer_button = FCButton()
  285. self.buffer_button.setToolTip(
  286. _("Create the buffer effect on each geometry,\n"
  287. "element from the selected object, using the distance.")
  288. )
  289. self.buffer_button.setMinimumWidth(90)
  290. grid0.addWidget(self.buffer_label, 23, 0)
  291. grid0.addWidget(self.buffer_entry, 23, 1)
  292. grid0.addWidget(self.buffer_button, 23, 2)
  293. self.buffer_factor_label = QtWidgets.QLabel('%s:' % _("Value"))
  294. self.buffer_factor_label.setToolTip(
  295. _("A positive value will create the effect of dilation,\n"
  296. "while a negative value will create the effect of erosion.\n"
  297. "Each geometry element of the object will be increased\n"
  298. "or decreased to fit the 'Value'. Value is a percentage\n"
  299. "of the initial dimension.")
  300. )
  301. self.buffer_factor_entry = FCDoubleSpinner(callback=self.confirmation_message, suffix='%')
  302. self.buffer_factor_entry.set_range(-100.0000, 1000.0000)
  303. self.buffer_factor_entry.set_precision(self.decimals)
  304. self.buffer_factor_entry.setWrapping(True)
  305. self.buffer_factor_entry.setSingleStep(1)
  306. self.buffer_factor_button = FCButton()
  307. self.buffer_factor_button.setToolTip(
  308. _("Create the buffer effect on each geometry,\n"
  309. "element from the selected object, using the factor.")
  310. )
  311. self.buffer_factor_button.setMinimumWidth(90)
  312. grid0.addWidget(self.buffer_factor_label, 24, 0)
  313. grid0.addWidget(self.buffer_factor_entry, 24, 1)
  314. grid0.addWidget(self.buffer_factor_button, 24, 2)
  315. self.buffer_rounded_cb = FCCheckBox('%s' % _("Rounded"))
  316. self.buffer_rounded_cb.setToolTip(
  317. _("If checked then the buffer will surround the buffered shape,\n"
  318. "every corner will be rounded.\n"
  319. "If not checked then the buffer will follow the exact geometry\n"
  320. "of the buffered shape.")
  321. )
  322. grid0.addWidget(self.buffer_rounded_cb, 25, 0, 1, 3)
  323. grid0.addWidget(QtWidgets.QLabel(''), 26, 0, 1, 3)
  324. self.layout.addStretch()
  325. # ## Reset Tool
  326. self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
  327. self.reset_button.setToolTip(
  328. _("Will reset the tool parameters.")
  329. )
  330. self.reset_button.setStyleSheet("""
  331. QPushButton
  332. {
  333. font-weight: bold;
  334. }
  335. """)
  336. self.layout.addWidget(self.reset_button)
  337. # ## Signals
  338. self.rotate_button.clicked.connect(self.on_rotate)
  339. self.skewx_button.clicked.connect(self.on_skewx)
  340. self.skewy_button.clicked.connect(self.on_skewy)
  341. self.scalex_button.clicked.connect(self.on_scalex)
  342. self.scaley_button.clicked.connect(self.on_scaley)
  343. self.offx_button.clicked.connect(self.on_offx)
  344. self.offy_button.clicked.connect(self.on_offy)
  345. self.flipx_button.clicked.connect(self.on_flipx)
  346. self.flipy_button.clicked.connect(self.on_flipy)
  347. self.flip_ref_button.clicked.connect(self.on_flip_add_coords)
  348. self.buffer_button.clicked.connect(self.on_buffer_by_distance)
  349. self.buffer_factor_button.clicked.connect(self.on_buffer_by_factor)
  350. self.reset_button.clicked.connect(self.set_tool_ui)
  351. # self.rotate_entry.returnPressed.connect(self.on_rotate)
  352. # self.skewx_entry.returnPressed.connect(self.on_skewx)
  353. # self.skewy_entry.returnPressed.connect(self.on_skewy)
  354. # self.scalex_entry.returnPressed.connect(self.on_scalex)
  355. # self.scaley_entry.returnPressed.connect(self.on_scaley)
  356. # self.offx_entry.returnPressed.connect(self.on_offx)
  357. # self.offy_entry.returnPressed.connect(self.on_offy)
  358. # self.buffer_entry.returnPressed.connect(self.on_buffer_by_distance)
  359. def run(self, toggle=True):
  360. self.app.report_usage("ToolTransform()")
  361. if toggle:
  362. # if the splitter is hidden, display it, else hide it but only if the current widget is the same
  363. if self.app.ui.splitter.sizes()[0] == 0:
  364. self.app.ui.splitter.setSizes([1, 1])
  365. else:
  366. try:
  367. if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
  368. # if tab is populated with the tool but it does not have the focus, focus on it
  369. if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
  370. # focus on Tool Tab
  371. self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
  372. else:
  373. self.app.ui.splitter.setSizes([0, 1])
  374. except AttributeError:
  375. pass
  376. else:
  377. if self.app.ui.splitter.sizes()[0] == 0:
  378. self.app.ui.splitter.setSizes([1, 1])
  379. FlatCAMTool.run(self)
  380. self.set_tool_ui()
  381. self.app.ui.notebook.setTabText(2, _("Transform Tool"))
  382. def install(self, icon=None, separator=None, **kwargs):
  383. FlatCAMTool.install(self, icon, separator, shortcut='Alt+T', **kwargs)
  384. def set_tool_ui(self):
  385. self.rotate_button.set_value(_("Rotate"))
  386. self.skewx_button.set_value(_("Skew X"))
  387. self.skewy_button.set_value(_("Skew Y"))
  388. self.scalex_button.set_value(_("Scale X"))
  389. self.scaley_button.set_value(_("Scale Y"))
  390. self.scale_link_cb.set_value(True)
  391. self.scale_zero_ref_cb.set_value(True)
  392. self.offx_button.set_value(_("Offset X"))
  393. self.offy_button.set_value(_("Offset Y"))
  394. self.flipx_button.set_value(_("Flip on X"))
  395. self.flipy_button.set_value(_("Flip on Y"))
  396. self.flip_ref_cb.set_value(True)
  397. self.flip_ref_button.set_value(_("Add"))
  398. self.buffer_button.set_value(_("Buffer D"))
  399. self.buffer_factor_button.set_value(_("Buffer F"))
  400. # ## Initialize form
  401. if self.app.defaults["tools_transform_rotate"]:
  402. self.rotate_entry.set_value(self.app.defaults["tools_transform_rotate"])
  403. else:
  404. self.rotate_entry.set_value(0.0)
  405. if self.app.defaults["tools_transform_skew_x"]:
  406. self.skewx_entry.set_value(self.app.defaults["tools_transform_skew_x"])
  407. else:
  408. self.skewx_entry.set_value(0.0)
  409. if self.app.defaults["tools_transform_skew_y"]:
  410. self.skewy_entry.set_value(self.app.defaults["tools_transform_skew_y"])
  411. else:
  412. self.skewy_entry.set_value(0.0)
  413. if self.app.defaults["tools_transform_scale_x"]:
  414. self.scalex_entry.set_value(self.app.defaults["tools_transform_scale_x"])
  415. else:
  416. self.scalex_entry.set_value(1.0)
  417. if self.app.defaults["tools_transform_scale_y"]:
  418. self.scaley_entry.set_value(self.app.defaults["tools_transform_scale_y"])
  419. else:
  420. self.scaley_entry.set_value(1.0)
  421. if self.app.defaults["tools_transform_scale_link"]:
  422. self.scale_link_cb.set_value(self.app.defaults["tools_transform_scale_link"])
  423. else:
  424. self.scale_link_cb.set_value(True)
  425. if self.app.defaults["tools_transform_scale_reference"]:
  426. self.scale_zero_ref_cb.set_value(self.app.defaults["tools_transform_scale_reference"])
  427. else:
  428. self.scale_zero_ref_cb.set_value(True)
  429. if self.app.defaults["tools_transform_offset_x"]:
  430. self.offx_entry.set_value(self.app.defaults["tools_transform_offset_x"])
  431. else:
  432. self.offx_entry.set_value(0.0)
  433. if self.app.defaults["tools_transform_offset_y"]:
  434. self.offy_entry.set_value(self.app.defaults["tools_transform_offset_y"])
  435. else:
  436. self.offy_entry.set_value(0.0)
  437. if self.app.defaults["tools_transform_mirror_reference"]:
  438. self.flip_ref_cb.set_value(self.app.defaults["tools_transform_mirror_reference"])
  439. else:
  440. self.flip_ref_cb.set_value(False)
  441. if self.app.defaults["tools_transform_mirror_point"]:
  442. self.flip_ref_entry.set_value(self.app.defaults["tools_transform_mirror_point"])
  443. else:
  444. self.flip_ref_entry.set_value((0, 0))
  445. if self.app.defaults["tools_transform_buffer_dis"]:
  446. self.buffer_entry.set_value(self.app.defaults["tools_transform_buffer_dis"])
  447. else:
  448. self.buffer_entry.set_value(0.0)
  449. if self.app.defaults["tools_transform_buffer_factor"]:
  450. self.buffer_factor_entry.set_value(self.app.defaults["tools_transform_buffer_factor"])
  451. else:
  452. self.buffer_factor_entry.set_value(100.0)
  453. if self.app.defaults["tools_transform_buffer_corner"]:
  454. self.buffer_rounded_cb.set_value(self.app.defaults["tools_transform_buffer_corner"])
  455. else:
  456. self.buffer_rounded_cb.set_value(True)
  457. def on_rotate(self):
  458. value = float(self.rotate_entry.get_value())
  459. if value == 0:
  460. self.app.inform.emit('[WARNING_NOTCL] %s' %
  461. _("Rotate transformation can not be done for a value of 0."))
  462. self.app.worker_task.emit({'fcn': self.on_rotate_action, 'params': [value]})
  463. return
  464. def on_flipx(self):
  465. axis = 'Y'
  466. self.app.worker_task.emit({'fcn': self.on_flip, 'params': [axis]})
  467. return
  468. def on_flipy(self):
  469. axis = 'X'
  470. self.app.worker_task.emit({'fcn': self.on_flip, 'params': [axis]})
  471. return
  472. def on_flip_add_coords(self):
  473. val = self.app.clipboard.text()
  474. self.flip_ref_entry.set_value(val)
  475. def on_skewx(self):
  476. value = float(self.skewx_entry.get_value())
  477. axis = 'X'
  478. self.app.worker_task.emit({'fcn': self.on_skew, 'params': [axis, value]})
  479. return
  480. def on_skewy(self):
  481. value = float(self.skewy_entry.get_value())
  482. axis = 'Y'
  483. self.app.worker_task.emit({'fcn': self.on_skew, 'params': [axis, value]})
  484. return
  485. def on_scalex(self):
  486. xvalue = float(self.scalex_entry.get_value())
  487. if xvalue == 0 or xvalue == 1:
  488. self.app.inform.emit('[WARNING_NOTCL] %s' %
  489. _("Scale transformation can not be done for a factor of 0 or 1."))
  490. return
  491. if self.scale_link_cb.get_value():
  492. yvalue = xvalue
  493. else:
  494. yvalue = 1
  495. axis = 'X'
  496. point = (0, 0)
  497. if self.scale_zero_ref_cb.get_value():
  498. self.app.worker_task.emit({'fcn': self.on_scale, 'params': [axis, xvalue, yvalue, point]})
  499. else:
  500. self.app.worker_task.emit({'fcn': self.on_scale, 'params': [axis, xvalue, yvalue]})
  501. return
  502. def on_scaley(self):
  503. xvalue = 1
  504. yvalue = float(self.scaley_entry.get_value())
  505. if yvalue == 0 or yvalue == 1:
  506. self.app.inform.emit('[WARNING_NOTCL] %s' %
  507. _("Scale transformation can not be done for a factor of 0 or 1."))
  508. return
  509. axis = 'Y'
  510. point = (0, 0)
  511. if self.scale_zero_ref_cb.get_value():
  512. self.app.worker_task.emit({'fcn': self.on_scale, 'params': [axis, xvalue, yvalue, point]})
  513. else:
  514. self.app.worker_task.emit({'fcn': self.on_scale, 'params': [axis, xvalue, yvalue]})
  515. return
  516. def on_offx(self):
  517. value = float(self.offx_entry.get_value())
  518. if value == 0:
  519. self.app.inform.emit('[WARNING_NOTCL] %s' % _("Offset transformation can not be done for a value of 0."))
  520. return
  521. axis = 'X'
  522. self.app.worker_task.emit({'fcn': self.on_offset, 'params': [axis, value]})
  523. return
  524. def on_offy(self):
  525. value = float(self.offy_entry.get_value())
  526. if value == 0:
  527. self.app.inform.emit('[WARNING_NOTCL] %s' % _("Offset transformation can not be done for a value of 0."))
  528. return
  529. axis = 'Y'
  530. self.app.worker_task.emit({'fcn': self.on_offset, 'params': [axis, value]})
  531. return
  532. def on_buffer_by_distance(self):
  533. value = self.buffer_entry.get_value()
  534. join = 1 if self.buffer_rounded_cb.get_value() else 2
  535. self.app.worker_task.emit({'fcn': self.on_buffer_action, 'params': [value, join]})
  536. return
  537. def on_buffer_by_factor(self):
  538. value = self.buffer_factor_entry.get_value() / 100.0
  539. join = 1 if self.buffer_rounded_cb.get_value() else 2
  540. # tell the buffer method to use the factor
  541. factor = True
  542. self.app.worker_task.emit({'fcn': self.on_buffer_action, 'params': [value, join, factor]})
  543. return
  544. def on_rotate_action(self, num):
  545. obj_list = self.app.collection.get_selected()
  546. xminlist = []
  547. yminlist = []
  548. xmaxlist = []
  549. ymaxlist = []
  550. if not obj_list:
  551. self.app.inform.emit('[WARNING_NOTCL] %s' % _("No object selected. Please Select an object to rotate!"))
  552. return
  553. else:
  554. with self.app.proc_container.new(_("Appying Rotate")):
  555. try:
  556. # first get a bounding box to fit all
  557. for obj in obj_list:
  558. if isinstance(obj, FlatCAMCNCjob):
  559. pass
  560. else:
  561. xmin, ymin, xmax, ymax = obj.bounds()
  562. xminlist.append(xmin)
  563. yminlist.append(ymin)
  564. xmaxlist.append(xmax)
  565. ymaxlist.append(ymax)
  566. # get the minimum x,y and maximum x,y for all objects selected
  567. xminimal = min(xminlist)
  568. yminimal = min(yminlist)
  569. xmaximal = max(xmaxlist)
  570. ymaximal = max(ymaxlist)
  571. px = 0.5 * (xminimal + xmaximal)
  572. py = 0.5 * (yminimal + ymaximal)
  573. for sel_obj in obj_list:
  574. if isinstance(sel_obj, FlatCAMCNCjob):
  575. self.app.inform.emit(_("CNCJob objects can't be rotated."))
  576. else:
  577. sel_obj.rotate(-num, point=(px, py))
  578. self.app.object_changed.emit(sel_obj)
  579. # add information to the object that it was changed and how much
  580. sel_obj.options['rotate'] = num
  581. sel_obj.plot()
  582. self.app.inform.emit('[success] %s...' % _('Rotate done'))
  583. except Exception as e:
  584. self.app.inform.emit('[ERROR_NOTCL] %s %s, %s.' %
  585. (_("Due of"), str(e), _("action was not executed.")))
  586. return
  587. def on_flip(self, axis):
  588. obj_list = self.app.collection.get_selected()
  589. xminlist = []
  590. yminlist = []
  591. xmaxlist = []
  592. ymaxlist = []
  593. if not obj_list:
  594. self.app.inform.emit('[WARNING_NOTCL] %s!' %
  595. _("No object selected. Please Select an object to flip"))
  596. return
  597. else:
  598. with self.app.proc_container.new(_("Applying Flip")):
  599. try:
  600. # get mirroring coords from the point entry
  601. if self.flip_ref_cb.isChecked():
  602. px, py = eval('{}'.format(self.flip_ref_entry.text()))
  603. # get mirroing coords from the center of an all-enclosing bounding box
  604. else:
  605. # first get a bounding box to fit all
  606. for obj in obj_list:
  607. if isinstance(obj, FlatCAMCNCjob):
  608. pass
  609. else:
  610. xmin, ymin, xmax, ymax = obj.bounds()
  611. xminlist.append(xmin)
  612. yminlist.append(ymin)
  613. xmaxlist.append(xmax)
  614. ymaxlist.append(ymax)
  615. # get the minimum x,y and maximum x,y for all objects selected
  616. xminimal = min(xminlist)
  617. yminimal = min(yminlist)
  618. xmaximal = max(xmaxlist)
  619. ymaximal = max(ymaxlist)
  620. px = 0.5 * (xminimal + xmaximal)
  621. py = 0.5 * (yminimal + ymaximal)
  622. # execute mirroring
  623. for sel_obj in obj_list:
  624. if isinstance(sel_obj, FlatCAMCNCjob):
  625. self.app.inform.emit(_("CNCJob objects can't be mirrored/flipped."))
  626. else:
  627. if axis == 'X':
  628. sel_obj.mirror('X', (px, py))
  629. # add information to the object that it was changed and how much
  630. # the axis is reversed because of the reference
  631. if 'mirror_y' in sel_obj.options:
  632. sel_obj.options['mirror_y'] = not sel_obj.options['mirror_y']
  633. else:
  634. sel_obj.options['mirror_y'] = True
  635. self.app.inform.emit('[success] %s...' %
  636. _('Flip on the Y axis done'))
  637. elif axis == 'Y':
  638. sel_obj.mirror('Y', (px, py))
  639. # add information to the object that it was changed and how much
  640. # the axis is reversed because of the reference
  641. if 'mirror_x' in sel_obj.options:
  642. sel_obj.options['mirror_x'] = not sel_obj.options['mirror_x']
  643. else:
  644. sel_obj.options['mirror_x'] = True
  645. self.app.inform.emit('[success] %s...' % _('Flip on the X axis done'))
  646. self.app.object_changed.emit(sel_obj)
  647. sel_obj.plot()
  648. except Exception as e:
  649. self.app.inform.emit('[ERROR_NOTCL] %s %s, %s.' %
  650. (_("Due of"), str(e), _("action was not executed.")))
  651. return
  652. def on_skew(self, axis, num):
  653. obj_list = self.app.collection.get_selected()
  654. xminlist = []
  655. yminlist = []
  656. if num == 0 or num == 90 or num == 180:
  657. self.app.inform.emit('[WARNING_NOTCL] %s' %
  658. _("Skew transformation can not be done for 0, 90 and 180 degrees."))
  659. return
  660. if not obj_list:
  661. self.app.inform.emit('[WARNING_NOTCL] %s' %
  662. _("No object selected. Please Select an object to shear/skew!"))
  663. return
  664. else:
  665. with self.app.proc_container.new(_("Applying Skew")):
  666. try:
  667. # first get a bounding box to fit all
  668. for obj in obj_list:
  669. if isinstance(obj, FlatCAMCNCjob):
  670. pass
  671. else:
  672. xmin, ymin, xmax, ymax = obj.bounds()
  673. xminlist.append(xmin)
  674. yminlist.append(ymin)
  675. # get the minimum x,y and maximum x,y for all objects selected
  676. xminimal = min(xminlist)
  677. yminimal = min(yminlist)
  678. for sel_obj in obj_list:
  679. if isinstance(sel_obj, FlatCAMCNCjob):
  680. self.app.inform.emit(_("CNCJob objects can't be skewed."))
  681. else:
  682. if axis == 'X':
  683. sel_obj.skew(num, 0, point=(xminimal, yminimal))
  684. # add information to the object that it was changed and how much
  685. sel_obj.options['skew_x'] = num
  686. elif axis == 'Y':
  687. sel_obj.skew(0, num, point=(xminimal, yminimal))
  688. # add information to the object that it was changed and how much
  689. sel_obj.options['skew_y'] = num
  690. self.app.object_changed.emit(sel_obj)
  691. sel_obj.plot()
  692. self.app.inform.emit('[success] %s %s %s...' % (_('Skew on the'), str(axis), _("axis done")))
  693. except Exception as e:
  694. self.app.inform.emit('[ERROR_NOTCL] %s %s, %s.' %
  695. (_("Due of"), str(e), _("action was not executed.")))
  696. return
  697. def on_scale(self, axis, xfactor, yfactor, point=None):
  698. obj_list = self.app.collection.get_selected()
  699. xminlist = []
  700. yminlist = []
  701. xmaxlist = []
  702. ymaxlist = []
  703. if not obj_list:
  704. self.app.inform.emit('[WARNING_NOTCL] %s' %
  705. _("No object selected. Please Select an object to scale!"))
  706. return
  707. else:
  708. with self.app.proc_container.new(_("Applying Scale")):
  709. try:
  710. # first get a bounding box to fit all
  711. for obj in obj_list:
  712. if isinstance(obj, FlatCAMCNCjob):
  713. pass
  714. else:
  715. xmin, ymin, xmax, ymax = obj.bounds()
  716. xminlist.append(xmin)
  717. yminlist.append(ymin)
  718. xmaxlist.append(xmax)
  719. ymaxlist.append(ymax)
  720. # get the minimum x,y and maximum x,y for all objects selected
  721. xminimal = min(xminlist)
  722. yminimal = min(yminlist)
  723. xmaximal = max(xmaxlist)
  724. ymaximal = max(ymaxlist)
  725. if point is None:
  726. px = 0.5 * (xminimal + xmaximal)
  727. py = 0.5 * (yminimal + ymaximal)
  728. else:
  729. px = 0
  730. py = 0
  731. for sel_obj in obj_list:
  732. if isinstance(sel_obj, FlatCAMCNCjob):
  733. self.app.inform.emit(_("CNCJob objects can't be scaled."))
  734. else:
  735. sel_obj.scale(xfactor, yfactor, point=(px, py))
  736. # add information to the object that it was changed and how much
  737. sel_obj.options['scale_x'] = xfactor
  738. sel_obj.options['scale_y'] = yfactor
  739. self.app.object_changed.emit(sel_obj)
  740. sel_obj.plot()
  741. self.app.inform.emit('[success] %s %s %s...' %
  742. (_('Scale on the'), str(axis), _('axis done')))
  743. except Exception as e:
  744. self.app.inform.emit('[ERROR_NOTCL] %s %s, %s.' %
  745. (_("Due of"), str(e), _("action was not executed.")))
  746. return
  747. def on_offset(self, axis, num):
  748. obj_list = self.app.collection.get_selected()
  749. if not obj_list:
  750. self.app.inform.emit('[WARNING_NOTCL] %s' %
  751. _("No object selected. Please Select an object to offset!"))
  752. return
  753. else:
  754. with self.app.proc_container.new(_("Applying Offset")):
  755. try:
  756. for sel_obj in obj_list:
  757. if isinstance(sel_obj, FlatCAMCNCjob):
  758. self.app.inform.emit(_("CNCJob objects can't be offset."))
  759. else:
  760. if axis == 'X':
  761. sel_obj.offset((num, 0))
  762. # add information to the object that it was changed and how much
  763. sel_obj.options['offset_x'] = num
  764. elif axis == 'Y':
  765. sel_obj.offset((0, num))
  766. # add information to the object that it was changed and how much
  767. sel_obj.options['offset_y'] = num
  768. self.app.object_changed.emit(sel_obj)
  769. sel_obj.plot()
  770. self.app.inform.emit('[success] %s %s %s...' %
  771. (_('Offset on the'), str(axis), _('axis done')))
  772. except Exception as e:
  773. self.app.inform.emit('[ERROR_NOTCL] %s %s, %s.' %
  774. (_("Due of"), str(e), _("action was not executed.")))
  775. return
  776. def on_buffer_action(self, value, join, factor=None):
  777. obj_list = self.app.collection.get_selected()
  778. if not obj_list:
  779. self.app.inform.emit('[WARNING_NOTCL] %s' % _("No object selected. Please Select an object to buffer!"))
  780. return
  781. else:
  782. with self.app.proc_container.new(_("Applying Buffer")):
  783. try:
  784. for sel_obj in obj_list:
  785. if isinstance(sel_obj, FlatCAMCNCjob):
  786. self.app.inform.emit(_("CNCJob objects can't be buffered."))
  787. elif sel_obj.kind.lower() == 'gerber':
  788. sel_obj.buffer(value, join, factor)
  789. sel_obj.source_file = self.app.export_gerber(obj_name=sel_obj.options['name'],
  790. filename=None, local_use=sel_obj,
  791. use_thread=False)
  792. elif sel_obj.kind.lower() == 'excellon':
  793. sel_obj.buffer(value, join, factor)
  794. sel_obj.source_file = self.app.export_excellon(obj_name=sel_obj.options['name'],
  795. filename=None, local_use=sel_obj,
  796. use_thread=False)
  797. elif sel_obj.kind.lower() == 'geometry':
  798. sel_obj.buffer(value, join, factor)
  799. self.app.object_changed.emit(sel_obj)
  800. sel_obj.plot()
  801. self.app.inform.emit('[success] %s...' % _('Buffer done'))
  802. except Exception as e:
  803. self.app.log.debug("ToolTransform.on_buffer_action() --> %s" % str(e))
  804. self.app.inform.emit('[ERROR_NOTCL] %s %s, %s.' %
  805. (_("Due of"), str(e), _("action was not executed.")))
  806. return
  807. # end of file