| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467 |
- # ##########################################################
- # FlatCAM: 2D Post-processing for Manufacturing #
- # File Author: Marius Adrian Stanciu (c) #
- # Date: 3/10/2019 #
- # MIT Licence #
- # ##########################################################
- from PyQt5 import QtWidgets, QtGui, QtCore
- from appTool import AppTool
- from appGUI.GUIElements import FCDoubleSpinner, FCCheckBox, RadioSet, FCComboBox, OptionalInputSection, FCButton, \
- FCLabel
- from shapely.geometry import box, MultiPolygon, Polygon, LineString, LinearRing, MultiLineString
- from shapely.ops import unary_union, linemerge
- import shapely.affinity as affinity
- from matplotlib.backend_bases import KeyEvent as mpl_key_event
- from numpy import Inf
- from copy import deepcopy
- import math
- import logging
- import gettext
- import sys
- import simplejson as json
- import appTranslation as fcTranslate
- import builtins
- fcTranslate.apply_language('strings')
- if '_' not in builtins.__dict__:
- _ = gettext.gettext
- log = logging.getLogger('base')
- settings = QtCore.QSettings("Open Source", "FlatCAM")
- if settings.contains("machinist"):
- machinist_setting = settings.value('machinist', type=int)
- else:
- machinist_setting = 0
- class CutOut(AppTool):
- def __init__(self, app):
- AppTool.__init__(self, app)
- self.app = app
- self.canvas = app.plotcanvas
- self.decimals = self.app.decimals
- # #############################################################################
- # ######################### Tool GUI ##########################################
- # #############################################################################
- self.ui = CutoutUI(layout=self.layout, app=self.app)
- self.toolName = self.ui.toolName
- self.cutting_gapsize = 0.0
- self.cutting_dia = 0.0
- # true if we want to repeat the gap without clicking again on the button
- self.repeat_gap = False
- self.flat_geometry = []
- # this is the Geometry object generated in this class to be used for adding manual gaps
- self.man_cutout_obj = None
- # if mouse is dragging set the object True
- self.mouse_is_dragging = False
- # if mouse events are bound to local methods
- self.mouse_events_connected = False
- # event handlers references
- self.kp = None
- self.mm = None
- self.mr = None
- # hold the mouse position here
- self.x_pos = None
- self.y_pos = None
- # store the default data for the resulting Geometry Object
- self.default_data = {}
- # store the current cursor type to be restored after manual geo
- self.old_cursor_type = self.app.defaults["global_cursor_type"]
- # store the current selection shape status to be restored after manual geo
- self.old_selection_state = self.app.defaults['global_selection_shape']
- # store original geometry for manual cutout
- self.manual_solid_geo = None
- # here will store the original geometry for manual cutout with mouse bytes
- self.mb_manual_solid_geo = None
- # here will store the geo rests when doing manual cutouts with mouse bites
- self.mb_manual_cuts = []
- # here store the tool data for the Cutout Tool
- self.cut_tool_dict = {}
- # Signals
- self.ui.ff_cutout_object_btn.clicked.connect(self.on_freeform_cutout)
- self.ui.rect_cutout_object_btn.clicked.connect(self.on_rectangular_cutout)
- # adding tools
- self.ui.add_newtool_button.clicked.connect(lambda: self.on_tool_add())
- self.ui.addtool_from_db_btn.clicked.connect(self.on_tool_add_from_db_clicked)
- self.ui.type_obj_radio.activated_custom.connect(self.on_type_obj_changed)
- self.ui.man_geo_creation_btn.clicked.connect(self.on_manual_geo)
- self.ui.man_gaps_creation_btn.clicked.connect(self.on_manual_gap_click)
- self.ui.reset_button.clicked.connect(self.set_tool_ui)
- def on_type_obj_changed(self, val):
- obj_type = {'grb': 0, 'geo': 2}[val]
- self.ui.obj_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
- self.ui.obj_combo.setCurrentIndex(0)
- self.ui.obj_combo.obj_type = {"grb": "Gerber", "geo": "Geometry"}[val]
- if val == 'grb':
- self.ui.convex_box_label.setDisabled(False)
- self.ui.convex_box_cb.setDisabled(False)
- else:
- self.ui.convex_box_label.setDisabled(True)
- self.ui.convex_box_cb.setDisabled(True)
- def run(self, toggle=True):
- self.app.defaults.report_usage("ToolCutOut()")
- if toggle:
- # if the splitter is hidden, display it, else hide it but only if the current widget is the same
- if self.app.ui.splitter.sizes()[0] == 0:
- self.app.ui.splitter.setSizes([1, 1])
- else:
- try:
- if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
- # if tab is populated with the tool but it does not have the focus, focus on it
- if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
- # focus on Tool Tab
- self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
- else:
- self.app.ui.splitter.setSizes([0, 1])
- except AttributeError:
- pass
- else:
- if self.app.ui.splitter.sizes()[0] == 0:
- self.app.ui.splitter.setSizes([1, 1])
- AppTool.run(self)
- self.set_tool_ui()
- self.app.ui.notebook.setTabText(2, _("Cutout Tool"))
- def install(self, icon=None, separator=None, **kwargs):
- AppTool.install(self, icon, separator, shortcut='Alt+X', **kwargs)
- def set_tool_ui(self):
- self.reset_fields()
- # use the current selected object and make it visible in the object combobox
- sel_list = self.app.collection.get_selected()
- if len(sel_list) == 1:
- active = self.app.collection.get_active()
- kind = active.kind
- if kind == 'gerber':
- self.ui.type_obj_radio.set_value('grb')
- else:
- self.ui.type_obj_radio.set_value('geo')
- # run those once so the obj_type attribute is updated for the FCComboboxes
- # so the last loaded object is displayed
- if kind == 'gerber':
- self.on_type_obj_changed(val='grb')
- else:
- self.on_type_obj_changed(val='geo')
- self.ui.obj_combo.set_value(active.options['name'])
- else:
- kind = 'gerber'
- self.ui.type_obj_radio.set_value('grb')
- # run those once so the obj_type attribute is updated for the FCComboboxes
- # so the last loaded object is displayed
- if kind == 'gerber':
- self.on_type_obj_changed(val='grb')
- else:
- self.on_type_obj_changed(val='geo')
- self.ui.dia.set_value(float(self.app.defaults["tools_cutout_tooldia"]))
- self.default_data.update({
- "plot": True,
- "cutz": float(self.app.defaults["geometry_cutz"]),
- "multidepth": self.app.defaults["geometry_multidepth"],
- "depthperpass": float(self.app.defaults["geometry_depthperpass"]),
- "vtipdia": float(self.app.defaults["geometry_vtipdia"]),
- "vtipangle": float(self.app.defaults["geometry_vtipangle"]),
- "travelz": float(self.app.defaults["geometry_travelz"]),
- "feedrate": float(self.app.defaults["geometry_feedrate"]),
- "feedrate_z": float(self.app.defaults["geometry_feedrate_z"]),
- "feedrate_rapid": float(self.app.defaults["geometry_feedrate_rapid"]),
- "spindlespeed": self.app.defaults["geometry_spindlespeed"],
- "dwell": self.app.defaults["geometry_dwell"],
- "dwelltime": float(self.app.defaults["geometry_dwelltime"]),
- "spindledir": self.app.defaults["geometry_spindledir"],
- "ppname_g": self.app.defaults["geometry_ppname_g"],
- "extracut": self.app.defaults["geometry_extracut"],
- "extracut_length": float(self.app.defaults["geometry_extracut_length"]),
- "toolchange": self.app.defaults["geometry_toolchange"],
- "toolchangexy": self.app.defaults["geometry_toolchangexy"],
- "toolchangez": float(self.app.defaults["geometry_toolchangez"]),
- "startz": self.app.defaults["geometry_startz"],
- "endz": float(self.app.defaults["geometry_endz"]),
- "endxy": self.app.defaults["geometry_endxy"],
- "area_exclusion": self.app.defaults["geometry_area_exclusion"],
- "area_shape": self.app.defaults["geometry_area_shape"],
- "area_strategy": self.app.defaults["geometry_area_strategy"],
- "area_overz": float(self.app.defaults["geometry_area_overz"]),
- "optimization_type": self.app.defaults["geometry_optimization_type"],
- # Cutout
- "tools_cutout_tooldia": self.app.defaults["tools_cutout_tooldia"],
- "tools_cutout_kind": self.app.defaults["tools_cutout_kind"],
- "tools_cutout_margin": float(self.app.defaults["tools_cutout_margin"]),
- "tools_cutout_z": float(self.app.defaults["tools_cutout_z"]),
- "tools_cutout_depthperpass": float(self.app.defaults["tools_cutout_depthperpass"]),
- "tools_cutout_mdepth": self.app.defaults["tools_cutout_mdepth"],
- "tools_cutout_gapsize": float(self.app.defaults["tools_cutout_gapsize"]),
- "tools_cutout_gaps_ff": self.app.defaults["tools_cutout_gaps_ff"],
- "tools_cutout_convexshape": self.app.defaults["tools_cutout_convexshape"],
- "tools_cutout_big_cursor": self.app.defaults["tools_cutout_big_cursor"],
- "tools_cutout_gap_type": self.app.defaults["tools_cutout_gap_type"],
- "tools_cutout_gap_depth": float(self.app.defaults["tools_cutout_gap_depth"]),
- "tools_cutout_mb_dia": float(self.app.defaults["tools_cutout_mb_dia"]),
- "tools_cutout_mb_spacing": float(self.app.defaults["tools_cutout_mb_spacing"]),
- })
- tool_dia = float(self.app.defaults["tools_cutout_tooldia"])
- self.on_tool_add(custom_dia=tool_dia)
- def update_ui(self, tool_dict):
- self.ui.obj_kind_combo.set_value(self.default_data["tools_cutout_kind"])
- self.ui.big_cursor_cb.set_value(self.default_data['tools_cutout_big_cursor'])
- # Entries that may be updated from database
- self.ui.margin.set_value(float(tool_dict["tools_cutout_margin"]))
- self.ui.gapsize.set_value(float(tool_dict["tools_cutout_gapsize"]))
- self.ui.gaptype_radio.set_value(tool_dict["tools_cutout_gap_type"])
- self.ui.thin_depth_entry.set_value(float(tool_dict["tools_cutout_gap_depth"]))
- self.ui.mb_dia_entry.set_value(float(tool_dict["tools_cutout_mb_dia"]))
- self.ui.mb_spacing_entry.set_value(float(tool_dict["tools_cutout_mb_spacing"]))
- self.ui.convex_box_cb.set_value(tool_dict['tools_cutout_convexshape'])
- self.ui.gaps.set_value(tool_dict["tools_cutout_gaps_ff"])
- self.ui.cutz_entry.set_value(float(tool_dict["tools_cutout_z"]))
- self.ui.mpass_cb.set_value(float(tool_dict["tools_cutout_mdepth"]))
- self.ui.maxdepth_entry.set_value(float(tool_dict["tools_cutout_depthperpass"]))
- def on_tool_add(self, custom_dia=None):
- self.blockSignals(True)
- filename = self.app.tools_database_path()
- new_tools_dict = deepcopy(self.default_data)
- updated_tooldia = None
- # determine the new tool diameter
- if custom_dia is None:
- tool_dia = self.ui.dia.get_value()
- else:
- tool_dia = custom_dia
- if tool_dia is None or tool_dia == 0:
- self.app.inform.emit('[WARNING_NOTCL] %s' % _("Please enter a tool diameter with non-zero value, "
- "in Float format."))
- self.blockSignals(False)
- return
- truncated_tooldia = self.app.dec_format(tool_dia, self.decimals)
- # load the database tools from the file
- try:
- with open(filename) as f:
- tools = f.read()
- except IOError:
- self.app.log.error("Could not load tools DB file.")
- self.app.inform.emit('[ERROR] %s' % _("Could not load Tools DB file."))
- self.blockSignals(False)
- self.on_tool_default_add(dia=tool_dia)
- return
- try:
- # store here the tools from Tools Database when searching in Tools Database
- tools_db_dict = json.loads(tools)
- except Exception:
- e = sys.exc_info()[0]
- self.app.log.error(str(e))
- self.app.inform.emit('[ERROR] %s' % _("Failed to parse Tools DB file."))
- self.blockSignals(False)
- self.on_tool_default_add(dia=tool_dia)
- return
- tool_found = 0
- offset = 'Path'
- offset_val = 0.0
- typ = 'Rough'
- tool_type = 'V'
- # look in database tools
- for db_tool, db_tool_val in tools_db_dict.items():
- offset = db_tool_val['offset']
- offset_val = db_tool_val['offset_value']
- typ = db_tool_val['type']
- tool_type = db_tool_val['tool_type']
- db_tooldia = db_tool_val['tooldia']
- low_limit = float(db_tool_val['data']['tol_min'])
- high_limit = float(db_tool_val['data']['tol_max'])
- # we need only tool marked for Cutout Tool
- if db_tool_val['data']['tool_target'] != _('Cutout'):
- continue
- # if we find a tool with the same diameter in the Tools DB just update it's data
- if truncated_tooldia == db_tooldia:
- tool_found += 1
- for d in db_tool_val['data']:
- if d.find('tools_cutout') == 0:
- new_tools_dict[d] = db_tool_val['data'][d]
- elif d.find('tools_') == 0:
- # don't need data for other App Tools; this tests after 'tools_drill_'
- continue
- else:
- new_tools_dict[d] = db_tool_val['data'][d]
- # search for a tool that has a tolerance that the tool fits in
- elif high_limit >= truncated_tooldia >= low_limit:
- tool_found += 1
- updated_tooldia = db_tooldia
- for d in db_tool_val['data']:
- if d.find('tools_cutout') == 0:
- new_tools_dict[d] = db_tool_val['data'][d]
- elif d.find('tools_') == 0:
- # don't need data for other App Tools; this tests after 'tools_drill_'
- continue
- else:
- new_tools_dict[d] = db_tool_val['data'][d]
- # test we found a suitable tool in Tools Database or if multiple ones
- if tool_found == 0:
- self.app.inform.emit('[WARNING_NOTCL] %s' % _("Tool not in Tools Database. Adding a default tool."))
- self.on_tool_default_add()
- self.blockSignals(False)
- return
- if tool_found > 1:
- self.app.inform.emit(
- '[WARNING_NOTCL] %s' % _("Cancelled.\n"
- "Multiple tools for one tool diameter found in Tools Database."))
- self.blockSignals(False)
- return
- # FIXME when the Geometry UI milling functionality will be transferred in the Milling Tool this needs changes
- new_tools_dict["tools_cutout_z"] = deepcopy(new_tools_dict["cutz"])
- new_tools_dict["tools_cutout_mdepth"] = deepcopy(new_tools_dict["multidepth"])
- new_tools_dict["tools_cutout_depthperpass"] = deepcopy(new_tools_dict["depthperpass"])
- new_tdia = deepcopy(updated_tooldia) if updated_tooldia is not None else deepcopy(truncated_tooldia)
- self.cut_tool_dict.update({
- 'tooldia': new_tdia,
- 'offset': deepcopy(offset),
- 'offset_value': deepcopy(offset_val),
- 'type': deepcopy(typ),
- 'tool_type': deepcopy(tool_type),
- 'data': deepcopy(new_tools_dict),
- 'solid_geometry': []
- })
- self.update_ui(new_tools_dict)
- self.blockSignals(False)
- self.app.inform.emit('[success] %s' % _("Updated tool from Tools Database."))
- def on_tool_default_add(self, dia=None, muted=None):
- dia = dia if dia else str(self.app.defaults["tools_cutout_tooldia"])
- self.default_data.update({
- "plot": True,
- "cutz": float(self.app.defaults["geometry_cutz"]),
- "multidepth": self.app.defaults["geometry_multidepth"],
- "depthperpass": float(self.app.defaults["geometry_depthperpass"]),
- "vtipdia": float(self.app.defaults["geometry_vtipdia"]),
- "vtipangle": float(self.app.defaults["geometry_vtipangle"]),
- "travelz": float(self.app.defaults["geometry_travelz"]),
- "feedrate": float(self.app.defaults["geometry_feedrate"]),
- "feedrate_z": float(self.app.defaults["geometry_feedrate_z"]),
- "feedrate_rapid": float(self.app.defaults["geometry_feedrate_rapid"]),
- "spindlespeed": self.app.defaults["geometry_spindlespeed"],
- "dwell": self.app.defaults["geometry_dwell"],
- "dwelltime": float(self.app.defaults["geometry_dwelltime"]),
- "spindledir": self.app.defaults["geometry_spindledir"],
- "ppname_g": self.app.defaults["geometry_ppname_g"],
- "extracut": self.app.defaults["geometry_extracut"],
- "extracut_length": float(self.app.defaults["geometry_extracut_length"]),
- "toolchange": self.app.defaults["geometry_toolchange"],
- "toolchangexy": self.app.defaults["geometry_toolchangexy"],
- "toolchangez": float(self.app.defaults["geometry_toolchangez"]),
- "startz": self.app.defaults["geometry_startz"],
- "endz": float(self.app.defaults["geometry_endz"]),
- "endxy": self.app.defaults["geometry_endxy"],
- "area_exclusion": self.app.defaults["geometry_area_exclusion"],
- "area_shape": self.app.defaults["geometry_area_shape"],
- "area_strategy": self.app.defaults["geometry_area_strategy"],
- "area_overz": float(self.app.defaults["geometry_area_overz"]),
- "optimization_type": self.app.defaults["geometry_optimization_type"],
- # Cutout
- "tools_cutout_tooldia": self.app.defaults["tools_cutout_tooldia"],
- "tools_cutout_kind": self.app.defaults["tools_cutout_kind"],
- "tools_cutout_margin": float(self.app.defaults["tools_cutout_margin"]),
- "tools_cutout_z": float(self.app.defaults["tools_cutout_z"]),
- "tools_cutout_depthperpass": float(self.app.defaults["tools_cutout_depthperpass"]),
- "tools_cutout_mdepth": self.app.defaults["tools_cutout_mdepth"],
- "tools_cutout_gapsize": float(self.app.defaults["tools_cutout_gapsize"]),
- "tools_cutout_gaps_ff": self.app.defaults["tools_cutout_gaps_ff"],
- "tools_cutout_convexshape": self.app.defaults["tools_cutout_convexshape"],
- "tools_cutout_big_cursor": self.app.defaults["tools_cutout_big_cursor"],
- "tools_cutout_gap_type": self.app.defaults["tools_cutout_gap_type"],
- "tools_cutout_gap_depth": float(self.app.defaults["tools_cutout_gap_depth"]),
- "tools_cutout_mb_dia": float(self.app.defaults["tools_cutout_mb_dia"]),
- "tools_cutout_mb_spacing": float(self.app.defaults["tools_cutout_mb_spacing"]),
- })
- self.cut_tool_dict.update({
- 'tooldia': dia,
- 'offset': 'Path',
- 'offset_value': 0.0,
- 'type': 'Rough',
- 'tool_type': 'C1',
- 'data': deepcopy(self.default_data),
- 'solid_geometry': []
- })
- self.update_ui(self.default_data)
- if muted is None:
- self.app.inform.emit('[success] %s' % _("Default tool added."))
- def on_cutout_tool_add_from_db_executed(self, tool):
- """
- Here add the tool from DB in the selected geometry object
- :return:
- """
- if tool['data']['tool_target'] not in [0, 6]: # [General, Cutout Tool]
- for idx in range(self.app.ui.plot_tab_area.count()):
- if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"):
- wdg = self.app.ui.plot_tab_area.widget(idx)
- wdg.deleteLater()
- self.app.ui.plot_tab_area.removeTab(idx)
- self.app.inform.emit('[ERROR_NOTCL] %s' % _("Selected tool can't be used here. Pick another."))
- return
- tool_from_db = deepcopy(self.default_data)
- tool_from_db.update(tool)
- # FIXME when the Geometry UI milling functionality will be transferred in the Milling Tool this needs changes
- tool_from_db['data']["tools_cutout_tooldia"] = deepcopy(tool["tooldia"])
- tool_from_db['data']["tools_cutout_z"] = deepcopy(tool_from_db['data']["cutz"])
- tool_from_db['data']["tools_cutout_mdepth"] = deepcopy(tool_from_db['data']["multidepth"])
- tool_from_db['data']["tools_cutout_depthperpass"] = deepcopy(tool_from_db['data']["depthperpass"])
- self.cut_tool_dict.update(tool_from_db)
- self.cut_tool_dict['solid_geometry'] = []
- self.update_ui(tool_from_db['data'])
- self.ui.dia.set_value(float(tool_from_db['data']["tools_cutout_tooldia"]))
- for idx in range(self.app.ui.plot_tab_area.count()):
- if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"):
- wdg = self.app.ui.plot_tab_area.widget(idx)
- wdg.deleteLater()
- self.app.ui.plot_tab_area.removeTab(idx)
- self.app.inform.emit('[success] %s' % _("Tool updated from Tools Database."))
- def on_tool_from_db_inserted(self, tool):
- """
- Called from the Tools DB object through a App method when adding a tool from Tools Database
- :param tool: a dict with the tool data
- :return: None
- """
- tooldia = float(tool['tooldia'])
- truncated_tooldia = self.app.dec_format(tooldia, self.decimals)
- self.cutout_tools.update({
- 1: {
- 'tooldia': truncated_tooldia,
- 'offset': tool['offset'],
- 'offset_value': tool['offset_value'],
- 'type': tool['type'],
- 'tool_type': tool['tool_type'],
- 'data': deepcopy(tool['data']),
- 'solid_geometry': []
- }
- })
- self.cutout_tools[1]['data']['name'] = '_cutout'
- return 1
- def on_tool_add_from_db_clicked(self):
- """
- Called when the user wants to add a new tool from Tools Database. It will create the Tools Database object
- and display the Tools Database tab in the form needed for the Tool adding
- :return: None
- """
- # if the Tools Database is already opened focus on it
- for idx in range(self.app.ui.plot_tab_area.count()):
- if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"):
- self.app.ui.plot_tab_area.setCurrentWidget(self.app.tools_db_tab)
- break
- ret_val = self.app.on_tools_database(source='cutout')
- if ret_val == 'fail':
- return
- self.app.tools_db_tab.ok_to_add = True
- self.app.tools_db_tab.ui.buttons_frame.hide()
- self.app.tools_db_tab.ui.add_tool_from_db.show()
- self.app.tools_db_tab.ui.cancel_tool_from_db.show()
- def on_freeform_cutout(self):
- log.debug("Cutout.on_freeform_cutout() was launched ...")
- name = self.ui.obj_combo.currentText()
- # Get source object.
- try:
- cutout_obj = self.app.collection.get_by_name(str(name))
- except Exception as e:
- log.debug("CutOut.on_freeform_cutout() --> %s" % str(e))
- self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), name))
- return "Could not retrieve object: %s" % name
- if cutout_obj is None:
- self.app.inform.emit('[ERROR_NOTCL] %s' %
- _("There is no object selected for Cutout.\nSelect one and try again."))
- return
- dia = self.ui.dia.get_value()
- if 0 in {dia}:
- self.app.inform.emit('[WARNING_NOTCL] %s' %
- _("Tool Diameter is zero value. Change it to a positive real number."))
- return "Tool Diameter is zero value. Change it to a positive real number."
- try:
- kind = self.ui.obj_kind_combo.get_value()
- except ValueError:
- return
- margin = self.ui.margin.get_value()
- try:
- gaps = self.ui.gaps.get_value()
- except TypeError:
- self.app.inform.emit('[WARNING_NOTCL] %s' % _("Number of gaps value is missing. Add it and retry."))
- return
- if gaps not in ['None', 'LR', 'TB', '2LR', '2TB', '4', '8']:
- self.app.inform.emit('[WARNING_NOTCL] %s' %
- _("Gaps value can be only one of: 'None', 'lr', 'tb', '2lr', '2tb', 4 or 8.\n"
- "Fill in a correct value and retry. "))
- return
- # if cutout_obj.multigeo is True:
- # self.app.inform.emit('[ERROR] %s' % _("Cutout operation cannot be done on a multi-geo Geometry.\n"
- # "Optionally, this Multi-geo Geometry can be converted to "
- # "Single-geo Geometry,\n"
- # "and after that perform Cutout."))
- # return
- def cutout_handler(geom, gapsize):
- proc_geometry = []
- rest_geometry = []
- r_temp_geo = []
- initial_geo = deepcopy(geom)
- # Get min and max data for each object as we just cut rectangles across X or Y
- xxmin, yymin, xxmax, yymax = CutOut.recursive_bounds(geom)
- px = 0.5 * (xxmin + xxmax) + margin
- py = 0.5 * (yymin + yymax) + margin
- lenx = (xxmax - xxmin) + (margin * 2)
- leny = (yymax - yymin) + (margin * 2)
- if gaps == 'None':
- pass
- else:
- if gaps == '8' or gaps == '2LR':
- points = (
- xxmin - gapsize, # botleft_x
- py - gapsize + leny / 4, # botleft_y
- xxmax + gapsize, # topright_x
- py + gapsize + leny / 4 # topright_y
- )
- geom = self.subtract_poly_from_geo(geom, points)
- r_temp_geo.append(
- self.intersect_geo(initial_geo, box(points[0], points[1], points[2], points[3]))
- )
- points = (
- xxmin - gapsize,
- py - gapsize - leny / 4,
- xxmax + gapsize,
- py + gapsize - leny / 4
- )
- geom = self.subtract_poly_from_geo(geom, points)
- r_temp_geo.append(
- self.intersect_geo(initial_geo, box(points[0], points[1], points[2], points[3]))
- )
- if gaps == '8' or gaps == '2TB':
- points = (
- px - gapsize + lenx / 4,
- yymin - gapsize,
- px + gapsize + lenx / 4,
- yymax + gapsize
- )
- geom = self.subtract_poly_from_geo(geom, points)
- r_temp_geo.append(
- self.intersect_geo(initial_geo, box(points[0], points[1], points[2], points[3]))
- )
- points = (
- px - gapsize - lenx / 4,
- yymin - gapsize,
- px + gapsize - lenx / 4,
- yymax + gapsize
- )
- geom = self.subtract_poly_from_geo(geom, points)
- r_temp_geo.append(
- self.intersect_geo(initial_geo, box(points[0], points[1], points[2], points[3]))
- )
- if gaps == '4' or gaps == 'LR':
- points = (
- xxmin - gapsize,
- py - gapsize,
- xxmax + gapsize,
- py + gapsize
- )
- geom = self.subtract_poly_from_geo(geom, points)
- r_temp_geo.append(
- self.intersect_geo(initial_geo, box(points[0], points[1], points[2], points[3]))
- )
- if gaps == '4' or gaps == 'TB':
- points = (
- px - gapsize,
- yymin - gapsize,
- px + gapsize,
- yymax + gapsize
- )
- geom = self.subtract_poly_from_geo(geom, points)
- r_temp_geo.append(
- self.intersect_geo(initial_geo, box(points[0], points[1], points[2], points[3]))
- )
- try:
- for g in geom:
- if g and not g.is_empty:
- proc_geometry.append(g)
- except TypeError:
- if geom and not geom.is_empty:
- proc_geometry.append(geom)
- r_temp_geo = CutOut.flatten(r_temp_geo)
- for g in r_temp_geo:
- if g and not g.is_empty:
- rest_geometry.append(g)
- return proc_geometry, rest_geometry
- with self.app.proc_container.new("Generating Cutout ..."):
- outname = cutout_obj.options["name"] + "_cutout"
- self.app.collection.promise(outname)
- has_mouse_bites = True if self.ui.gaptype_radio.get_value() == 'mb' else False
- outname_exc = cutout_obj.options["name"] + "_mouse_bites"
- if has_mouse_bites is True:
- self.app.collection.promise(outname_exc)
- def job_thread(app_obj):
- solid_geo = []
- gaps_solid_geo = []
- mouse_bites_geo = []
- convex_box = self.ui.convex_box_cb.get_value()
- gapsize = self.ui.gapsize.get_value()
- gapsize = gapsize / 2 + (dia / 2)
- mb_dia = self.ui.mb_dia_entry.get_value()
- mb_buff_val = mb_dia / 2.0
- mb_spacing = self.ui.mb_spacing_entry.get_value()
- gap_type = self.ui.gaptype_radio.get_value()
- thin_entry = self.ui.thin_depth_entry.get_value()
- if cutout_obj.kind == 'gerber':
- if isinstance(cutout_obj.solid_geometry, list):
- cutout_obj.solid_geometry = MultiPolygon(cutout_obj.solid_geometry)
- try:
- if convex_box:
- object_geo = cutout_obj.solid_geometry.convex_hull
- else:
- object_geo = cutout_obj.solid_geometry
- except Exception as err:
- log.debug("CutOut.on_freeform_cutout().geo_init() --> %s" % str(err))
- object_geo = cutout_obj.solid_geometry
- else:
- if cutout_obj.multigeo is False:
- object_geo = cutout_obj.solid_geometry
- else:
- # first tool in the tools dict
- t_first = list(cutout_obj.tools.keys())[0]
- object_geo = cutout_obj.tools[t_first]['solid_geometry']
- if kind == 'single':
- object_geo = unary_union(object_geo)
- # for geo in object_geo:
- if cutout_obj.kind == 'gerber':
- if isinstance(object_geo, MultiPolygon):
- x0, y0, x1, y1 = object_geo.bounds
- object_geo = box(x0, y0, x1, y1)
- if margin >= 0:
- geo_buf = object_geo.buffer(margin + abs(dia / 2))
- else:
- geo_buf = object_geo.buffer(margin - abs(dia / 2))
- geo = geo_buf.exterior
- else:
- if isinstance(object_geo, MultiPolygon):
- x0, y0, x1, y1 = object_geo.bounds
- object_geo = box(x0, y0, x1, y1)
- geo_buf = object_geo.buffer(0)
- geo = geo_buf.exterior
- solid_geo, rest_geo = cutout_handler(geom=geo, gapsize=gapsize)
- if gap_type == 'bt' and thin_entry != 0:
- gaps_solid_geo = rest_geo
- else:
- try:
- __ = iter(object_geo)
- except TypeError:
- object_geo = [object_geo]
- for geom_struct in object_geo:
- if cutout_obj.kind == 'gerber':
- if margin >= 0:
- geom_struct = (geom_struct.buffer(margin + abs(dia / 2))).exterior
- else:
- geom_struct_buff = geom_struct.buffer(-margin + abs(dia / 2))
- geom_struct = geom_struct_buff.interiors
- c_geo, r_geo = cutout_handler(geom=geom_struct, gapsize=gapsize)
- solid_geo += c_geo
- if gap_type == 'bt' and thin_entry != 0:
- gaps_solid_geo += r_geo
- if not solid_geo:
- self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed."))
- return "fail"
- solid_geo = linemerge(solid_geo)
- if has_mouse_bites is True:
- gapsize -= dia / 2
- mb_object_geo = deepcopy(object_geo)
- if kind == 'single':
- mb_object_geo = unary_union(mb_object_geo)
- # for geo in object_geo:
- if cutout_obj.kind == 'gerber':
- if isinstance(mb_object_geo, MultiPolygon):
- x0, y0, x1, y1 = mb_object_geo.bounds
- mb_object_geo = box(x0, y0, x1, y1)
- if margin >= 0:
- geo_buf = mb_object_geo.buffer(margin + mb_buff_val)
- else:
- geo_buf = mb_object_geo.buffer(margin - mb_buff_val)
- mb_geo = geo_buf.exterior
- else:
- if isinstance(mb_object_geo, MultiPolygon):
- x0, y0, x1, y1 = mb_object_geo.bounds
- mb_object_geo = box(x0, y0, x1, y1)
- geo_buf = mb_object_geo.buffer(0)
- mb_geo = geo_buf.exterior
- __, rest_geo = cutout_handler(geom=mb_geo, gapsize=gapsize)
- mouse_bites_geo = rest_geo
- else:
- try:
- __ = iter(mb_object_geo)
- except TypeError:
- mb_object_geo = [mb_object_geo]
- for mb_geom_struct in mb_object_geo:
- if cutout_obj.kind == 'gerber':
- if margin >= 0:
- mb_geom_struct = mb_geom_struct.buffer(margin + mb_buff_val)
- mb_geom_struct = mb_geom_struct.exterior
- else:
- mb_geom_struct = mb_geom_struct.buffer(-margin + mb_buff_val)
- mb_geom_struct = mb_geom_struct.interiors
- __, mb_r_geo = cutout_handler(geom=mb_geom_struct, gapsize=gapsize)
- mouse_bites_geo += mb_r_geo
- # list of Shapely Points to mark the drill points centers
- holes = []
- for line in mouse_bites_geo:
- calc_len = 0
- while calc_len < line.length:
- holes.append(line.interpolate(calc_len))
- calc_len += mb_dia + mb_spacing
- def geo_init(geo_obj, app_object):
- geo_obj.multigeo = True
- geo_obj.solid_geometry = deepcopy(solid_geo)
- xmin, ymin, xmax, ymax = CutOut.recursive_bounds(geo_obj.solid_geometry)
- geo_obj.options['xmin'] = xmin
- geo_obj.options['ymin'] = ymin
- geo_obj.options['xmax'] = xmax
- geo_obj.options['ymax'] = ymax
- geo_obj.options['cnctooldia'] = str(dia)
- geo_obj.options['cutz'] = self.ui.cutz_entry.get_value()
- geo_obj.options['multidepth'] = self.ui.mpass_cb.get_value()
- geo_obj.options['depthperpass'] = self.ui.maxdepth_entry.get_value()
- geo_obj.tools[1] = deepcopy(self.cut_tool_dict)
- geo_obj.tools[1]['tooldia'] = str(dia)
- geo_obj.tools[1]['solid_geometry'] = geo_obj.solid_geometry
- geo_obj.tools[1]['data']['name'] = outname
- geo_obj.tools[1]['data']['cutz'] = self.ui.cutz_entry.get_value()
- geo_obj.tools[1]['data']['multidepth'] = self.ui.mpass_cb.get_value()
- geo_obj.tools[1]['data']['depthperpass'] = self.ui.maxdepth_entry.get_value()
- if not gaps_solid_geo:
- pass
- else:
- geo_obj.tools[9999] = deepcopy(self.cut_tool_dict)
- geo_obj.tools[9999]['tooldia'] = str(dia)
- geo_obj.tools[9999]['solid_geometry'] = gaps_solid_geo
- geo_obj.tools[9999]['data']['name'] = outname
- geo_obj.tools[9999]['data']['cutz'] = self.ui.thin_depth_entry.get_value()
- geo_obj.tools[9999]['data']['multidepth'] = self.ui.mpass_cb.get_value()
- geo_obj.tools[9999]['data']['depthperpass'] = self.ui.maxdepth_entry.get_value()
- # plot this tool in a different color
- geo_obj.tools[9999]['data']['override_color'] = "#29a3a3fa"
- def excellon_init(exc_obj, app_o):
- if not holes:
- return 'fail'
- tools = {
- 1: {
- "tooldia": mb_dia,
- "drills": holes,
- "solid_geometry": []
- }
- }
- exc_obj.tools = tools
- exc_obj.create_geometry()
- exc_obj.source_file = app_o.f_handlers.export_excellon(obj_name=exc_obj.options['name'],
- local_use=exc_obj, filename=None,
- use_thread=False)
- # calculate the bounds
- xmin, ymin, xmax, ymax = CutOut.recursive_bounds(exc_obj.solid_geometry)
- exc_obj.options['xmin'] = xmin
- exc_obj.options['ymin'] = ymin
- exc_obj.options['xmax'] = xmax
- exc_obj.options['ymax'] = ymax
- try:
- if self.ui.gaptype_radio.get_value() == 'mb':
- ret = app_obj.app_obj.new_object('excellon', outname_exc, excellon_init)
- if ret == 'fail':
- app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Mouse bites failed."))
- ret = app_obj.app_obj.new_object('geometry', outname, geo_init)
- if ret == 'fail':
- app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Failed."))
- return
- # cutout_obj.plot(plot_tool=1)
- app_obj.inform.emit('[success] %s' % _("Any-form Cutout operation finished."))
- # self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
- app_obj.should_we_save = True
- except Exception as ee:
- log.debug(str(ee))
- self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
- def on_rectangular_cutout(self):
- log.debug("Cutout.on_rectangular_cutout() was launched ...")
- name = self.ui.obj_combo.currentText()
- # Get source object.
- try:
- cutout_obj = self.app.collection.get_by_name(str(name))
- except Exception as e:
- log.debug("CutOut.on_rectangular_cutout() --> %s" % str(e))
- self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), name))
- return "Could not retrieve object: %s" % name
- if cutout_obj is None:
- self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), str(name)))
- dia = float(self.ui.dia.get_value())
- if 0 in {dia}:
- self.app.inform.emit('[ERROR_NOTCL] %s' %
- _("Tool Diameter is zero value. Change it to a positive real number."))
- return "Tool Diameter is zero value. Change it to a positive real number."
- try:
- kind = self.ui.obj_kind_combo.get_value()
- except ValueError:
- return
- margin = self.ui.margin.get_value()
- try:
- gaps = self.ui.gaps.get_value()
- except TypeError:
- self.app.inform.emit('[WARNING_NOTCL] %s' %
- _("Number of gaps value is missing. Add it and retry."))
- return
- if gaps not in ['None', 'LR', 'TB', '2LR', '2TB', '4', '8']:
- self.app.inform.emit('[WARNING_NOTCL] %s' % _("Gaps value can be only one of: "
- "'None', 'lr', 'tb', '2lr', '2tb', 4 or 8. "
- "Fill in a correct value and retry. "))
- return
- # if cutout_obj.multigeo is True:
- # self.app.inform.emit('[ERROR] %s' % _("Cutout operation cannot be done on a multi-geo Geometry.\n"
- # "Optionally, this Multi-geo Geometry can be converted to "
- # "Single-geo Geometry,\n"
- # "and after that perform Cutout."))
- # return
- def cutout_rect_handler(geom, gapsize, xmin, ymin, xmax, ymax):
- proc_geometry = []
- px = 0.5 * (xmin + xmax) + margin
- py = 0.5 * (ymin + ymax) + margin
- lenx = (xmax - xmin) + (margin * 2)
- leny = (ymax - ymin) + (margin * 2)
- if gaps == 'None':
- pass
- else:
- if gaps == '8' or gaps == '2LR':
- points = (
- xmin - gapsize, # botleft_x
- py - gapsize + leny / 4, # botleft_y
- xmax + gapsize, # topright_x
- py + gapsize + leny / 4 # topright_y
- )
- geom = self.subtract_poly_from_geo(geom, points)
- points = (
- xmin - gapsize,
- py - gapsize - leny / 4,
- xmax + gapsize,
- py + gapsize - leny / 4
- )
- geom = self.subtract_poly_from_geo(geom, points)
- if gaps == '8' or gaps == '2TB':
- points = (
- px - gapsize + lenx / 4,
- ymin - gapsize,
- px + gapsize + lenx / 4,
- ymax + gapsize
- )
- geom = self.subtract_poly_from_geo(geom, points)
- points = (
- px - gapsize - lenx / 4,
- ymin - gapsize,
- px + gapsize - lenx / 4,
- ymax + gapsize
- )
- geom = self.subtract_poly_from_geo(geom, points)
- if gaps == '4' or gaps == 'LR':
- points = (
- xmin - gapsize,
- py - gapsize,
- xmax + gapsize,
- py + gapsize
- )
- geom = self.subtract_poly_from_geo(geom, points)
- if gaps == '4' or gaps == 'TB':
- points = (
- px - gapsize,
- ymin - gapsize,
- px + gapsize,
- ymax + gapsize
- )
- geom = self.subtract_poly_from_geo(geom, points)
- try:
- for g in geom:
- proc_geometry.append(g)
- except TypeError:
- proc_geometry.append(geom)
- return proc_geometry
- with self.app.proc_container.new("Generating Cutout ..."):
- outname = cutout_obj.options["name"] + "_cutout"
- self.app.collection.promise(outname)
- has_mouse_bites = True if self.ui.gaptype_radio.get_value() == 'mb' else False
- outname_exc = cutout_obj.options["name"] + "_mouse_bites"
- if has_mouse_bites is True:
- self.app.collection.promise(outname_exc)
- def job_thread(app_obj):
- solid_geo = []
- gaps_solid_geo = []
- mouse_bites_geo = []
- gapsize = self.ui.gapsize.get_value()
- gapsize = gapsize / 2 + (dia / 2)
- mb_dia = self.ui.mb_dia_entry.get_value()
- mb_buff_val = mb_dia / 2.0
- mb_spacing = self.ui.mb_spacing_entry.get_value()
- gap_type = self.ui.gaptype_radio.get_value()
- thin_entry = self.ui.thin_depth_entry.get_value()
- if cutout_obj.multigeo is False:
- object_geo = cutout_obj.solid_geometry
- else:
- # first tool in the tools dict
- t_first = list(cutout_obj.tools.keys())[0]
- object_geo = cutout_obj.tools[t_first]['solid_geometry']
- if kind == 'single':
- # fuse the lines
- object_geo = unary_union(object_geo)
- xmin, ymin, xmax, ymax = object_geo.bounds
- geo = box(xmin, ymin, xmax, ymax)
- # if Gerber create a buffer at a distance
- # if Geometry then cut through the geometry
- if cutout_obj.kind == 'gerber':
- if margin >= 0:
- geo = geo.buffer(margin + abs(dia / 2))
- else:
- geo = geo.buffer(margin - abs(dia / 2))
- solid_geo = cutout_rect_handler(geo, gapsize, xmin, ymin, xmax, ymax)
- if gap_type == 'bt' and thin_entry != 0:
- gaps_solid_geo = self.subtract_geo(geo, deepcopy(solid_geo))
- else:
- if cutout_obj.kind == 'geometry':
- try:
- __ = iter(object_geo)
- except TypeError:
- object_geo = [object_geo]
- for geom_struct in object_geo:
- geom_struct = unary_union(geom_struct)
- xmin, ymin, xmax, ymax = geom_struct.bounds
- geom_struct = box(xmin, ymin, xmax, ymax)
- c_geo = cutout_rect_handler(geom_struct, gapsize, xmin, ymin, xmax, ymax)
- solid_geo += c_geo
- if gap_type == 'bt' and thin_entry != 0:
- try:
- gaps_solid_geo += self.subtract_geo(geom_struct, c_geo)
- except TypeError:
- gaps_solid_geo.append(self.subtract_geo(geom_struct, c_geo))
- elif cutout_obj.kind == 'gerber' and margin >= 0:
- try:
- __ = iter(object_geo)
- except TypeError:
- object_geo = [object_geo]
- for geom_struct in object_geo:
- geom_struct = unary_union(geom_struct)
- xmin, ymin, xmax, ymax = geom_struct.bounds
- geom_struct = box(xmin, ymin, xmax, ymax)
- geom_struct = geom_struct.buffer(margin + abs(dia / 2))
- c_geo = cutout_rect_handler(geom_struct, gapsize, xmin, ymin, xmax, ymax)
- solid_geo += c_geo
- if gap_type == 'bt' and thin_entry != 0:
- try:
- gaps_solid_geo += self.subtract_geo(geom_struct, c_geo)
- except TypeError:
- gaps_solid_geo.append(self.subtract_geo(geom_struct, c_geo))
- elif cutout_obj.kind == 'gerber' and margin < 0:
- app_obj.inform.emit(
- '[WARNING_NOTCL] %s' % _("Rectangular cutout with negative margin is not possible."))
- return "fail"
- if not solid_geo:
- app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Failed."))
- return "fail"
- solid_geo = linemerge(solid_geo)
- if has_mouse_bites is True:
- gapsize -= dia / 2
- mb_object_geo = deepcopy(object_geo)
- if kind == 'single':
- # fuse the lines
- mb_object_geo = unary_union(mb_object_geo)
- xmin, ymin, xmax, ymax = mb_object_geo.bounds
- mb_geo = box(xmin, ymin, xmax, ymax)
- # if Gerber create a buffer at a distance
- # if Geometry then cut through the geometry
- if cutout_obj.kind == 'gerber':
- if margin >= 0:
- mb_geo = mb_geo.buffer(margin + mb_buff_val)
- else:
- mb_geo = mb_geo.buffer(margin - mb_buff_val)
- else:
- mb_geo = mb_geo.buffer(0)
- mb_solid_geo = cutout_rect_handler(mb_geo, gapsize, xmin, ymin, xmax, ymax)
- mouse_bites_geo = self.subtract_geo(mb_geo, mb_solid_geo)
- else:
- if cutout_obj.kind == 'geometry':
- try:
- __ = iter(mb_object_geo)
- except TypeError:
- mb_object_geo = [mb_object_geo]
- for mb_geom_struct in mb_object_geo:
- mb_geom_struct = unary_union(mb_geom_struct)
- xmin, ymin, xmax, ymax = mb_geom_struct.bounds
- mb_geom_struct = box(xmin, ymin, xmax, ymax)
- c_geo = cutout_rect_handler(mb_geom_struct, gapsize, xmin, ymin, xmax, ymax)
- solid_geo += c_geo
- try:
- mouse_bites_geo += self.subtract_geo(mb_geom_struct, c_geo)
- except TypeError:
- mouse_bites_geo.append(self.subtract_geo(mb_geom_struct, c_geo))
- elif cutout_obj.kind == 'gerber' and margin >= 0:
- try:
- __ = iter(mb_object_geo)
- except TypeError:
- mb_object_geo = [mb_object_geo]
- for mb_geom_struct in mb_object_geo:
- mb_geom_struct = unary_union(mb_geom_struct)
- xmin, ymin, xmax, ymax = mb_geom_struct.bounds
- mb_geom_struct = box(xmin, ymin, xmax, ymax)
- mb_geom_struct = mb_geom_struct.buffer(margin + mb_buff_val)
- c_geo = cutout_rect_handler(mb_geom_struct, gapsize, xmin, ymin, xmax, ymax)
- solid_geo += c_geo
- try:
- mouse_bites_geo += self.subtract_geo(mb_geom_struct, c_geo)
- except TypeError:
- mouse_bites_geo.append(self.subtract_geo(mb_geom_struct, c_geo))
- elif cutout_obj.kind == 'gerber' and margin < 0:
- msg = '[WARNING_NOTCL] %s' % \
- _("Rectangular cutout with negative margin is not possible.")
- app_obj.inform.emit(msg)
- return "fail"
- # list of Shapely Points to mark the drill points centers
- holes = []
- for line in mouse_bites_geo:
- calc_len = 0
- while calc_len < line.length:
- holes.append(line.interpolate(calc_len))
- calc_len += mb_dia + mb_spacing
- def geo_init(geo_obj, application_obj):
- geo_obj.multigeo = True
- geo_obj.solid_geometry = deepcopy(solid_geo)
- geo_obj.options['xmin'] = xmin
- geo_obj.options['ymin'] = ymin
- geo_obj.options['xmax'] = xmax
- geo_obj.options['ymax'] = ymax
- geo_obj.options['cnctooldia'] = str(dia)
- geo_obj.options['cutz'] = self.ui.cutz_entry.get_value()
- geo_obj.options['multidepth'] = self.ui.mpass_cb.get_value()
- geo_obj.options['depthperpass'] = self.ui.maxdepth_entry.get_value()
- geo_obj.tools[1] = deepcopy(self.cut_tool_dict)
- geo_obj.tools[1]['tooldia'] = str(dia)
- geo_obj.tools[1]['solid_geometry'] = geo_obj.solid_geometry
- geo_obj.tools[1]['data']['name'] = outname
- geo_obj.tools[1]['data']['cutz'] = self.ui.cutz_entry.get_value()
- geo_obj.tools[1]['data']['multidepth'] = self.ui.mpass_cb.get_value()
- geo_obj.tools[1]['data']['depthperpass'] = self.ui.maxdepth_entry.get_value()
- if not gaps_solid_geo:
- pass
- else:
- geo_obj.tools[9999] = deepcopy(self.cut_tool_dict)
- geo_obj.tools[9999]['tooldia'] = str(dia)
- geo_obj.tools[9999]['solid_geometry'] = gaps_solid_geo
- geo_obj.tools[9999]['data']['name'] = outname
- geo_obj.tools[9999]['data']['cutz'] = self.ui.thin_depth_entry.get_value()
- geo_obj.tools[9999]['data']['multidepth'] = self.ui.mpass_cb.get_value()
- geo_obj.tools[9999]['data']['depthperpass'] = self.ui.maxdepth_entry.get_value()
- geo_obj.tools[9999]['data']['override_color'] = "#29a3a3fa"
- def excellon_init(exc_obj, app_o):
- if not holes:
- return 'fail'
- tools = {
- 1: {
- "tooldia": mb_dia,
- "drills": holes,
- "solid_geometry": []
- }
- }
- exc_obj.tools = tools
- exc_obj.create_geometry()
- exc_obj.source_file = app_o.f_handlers.export_excellon(obj_name=exc_obj.options['name'],
- local_use=exc_obj,
- filename=None,
- use_thread=False)
- # calculate the bounds
- e_xmin, e_ymin, e_xmax, e_ymax = CutOut.recursive_bounds(exc_obj.solid_geometry)
- exc_obj.options['xmin'] = e_xmin
- exc_obj.options['ymin'] = e_ymin
- exc_obj.options['xmax'] = e_xmax
- exc_obj.options['ymax'] = e_ymax
- try:
- if self.ui.gaptype_radio.get_value() == 'mb':
- ret = app_obj.app_obj.new_object('excellon', outname_exc, excellon_init)
- if ret == 'fail':
- app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Mouse bites failed."))
- ret = app_obj.app_obj.new_object('geometry', outname, geo_init)
- if ret == 'fail':
- app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Failed."))
- return
- # cutout_obj.plot(plot_tool=1)
- app_obj.inform.emit('[success] %s' % _("Rectangular CutOut operation finished."))
- # self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
- app_obj.should_we_save = True
- except Exception as ee:
- log.debug(str(ee))
- self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
- def on_manual_gap_click(self):
- name = self.ui.man_object_combo.currentText()
- # Get source object.
- try:
- self.man_cutout_obj = self.app.collection.get_by_name(str(name))
- except Exception as e:
- log.debug("CutOut.on_manual_cutout() --> %s" % str(e))
- self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), name))
- return
- if self.man_cutout_obj is None:
- self.app.inform.emit('[ERROR_NOTCL] %s: %s' %
- (_("Geometry object for manual cutout not found"), self.man_cutout_obj))
- return
- self.app.inform.emit(_("Click on the selected geometry object perimeter to create a bridge gap ..."))
- self.app.geo_editor.tool_shape.enabled = True
- self.manual_solid_geo = deepcopy(self.flatten(self.man_cutout_obj.solid_geometry))
- self.cutting_dia = self.ui.dia.get_value()
- if 0 in {self.cutting_dia}:
- self.app.inform.emit('[ERROR_NOTCL] %s' %
- _("Tool Diameter is zero value. Change it to a positive real number."))
- return
- if self.ui.gaptype_radio.get_value() == 'mb':
- mb_dia = self.ui.mb_dia_entry.get_value()
- b_dia = (self.cutting_dia / 2.0) - (mb_dia / 2.0)
- self.mb_manual_solid_geo = self.flatten(unary_union(self.manual_solid_geo).buffer(b_dia).interiors)
- self.cutting_gapsize = self.ui.gapsize.get_value()
- name = self.ui.man_object_combo.currentText()
- # Get Geometry source object to be used as target for Manual adding Gaps
- try:
- self.man_cutout_obj = self.app.collection.get_by_name(str(name))
- except Exception as e:
- log.debug("CutOut.on_manual_cutout() --> %s" % str(e))
- self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), name))
- return
- if self.app.is_legacy is False:
- self.app.plotcanvas.graph_event_disconnect('key_press', self.app.ui.keyPressEvent)
- self.app.plotcanvas.graph_event_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
- self.app.plotcanvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
- self.app.plotcanvas.graph_event_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
- else:
- self.app.plotcanvas.graph_event_disconnect(self.app.kp)
- self.app.plotcanvas.graph_event_disconnect(self.app.mp)
- self.app.plotcanvas.graph_event_disconnect(self.app.mr)
- self.app.plotcanvas.graph_event_disconnect(self.app.mm)
- self.kp = self.app.plotcanvas.graph_event_connect('key_press', self.on_key_press)
- self.mm = self.app.plotcanvas.graph_event_connect('mouse_move', self.on_mouse_move)
- self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_click_release)
- self.mouse_events_connected = True
- if self.ui.big_cursor_cb.get_value():
- self.old_cursor_type = self.app.defaults["global_cursor_type"]
- self.app.on_cursor_type(val="big")
- self.app.defaults['global_selection_shape'] = False
- def on_manual_cutout(self, click_pos):
- if self.man_cutout_obj is None:
- msg = '[ERROR_NOTCL] %s: %s' % (_("Geometry object for manual cutout not found"), self.man_cutout_obj)
- self.app.inform.emit(msg)
- return
- # use the snapped position as reference
- snapped_pos = self.app.geo_editor.snap(click_pos[0], click_pos[1])
- cut_poly = self.cutting_geo(pos=(snapped_pos[0], snapped_pos[1]))
- gap_type = self.ui.gaptype_radio.get_value()
- gaps_solid_geo = None
- if gap_type == 'bt' and self.ui.thin_depth_entry.get_value() != 0:
- gaps_solid_geo = self.intersect_geo(self.manual_solid_geo, cut_poly)
- if gap_type == 'mb':
- rests_geo = self.intersect_geo(self.mb_manual_solid_geo, cut_poly)
- if isinstance(rests_geo, list):
- self.mb_manual_cuts += rests_geo
- else:
- self.mb_manual_cuts.append(rests_geo)
- # first subtract geometry for the total solid_geometry
- new_solid_geometry = CutOut.subtract_geo(self.man_cutout_obj.solid_geometry, cut_poly)
- new_solid_geometry = linemerge(new_solid_geometry)
- self.man_cutout_obj.solid_geometry = new_solid_geometry
- # then do it on each tool in the manual cutout Geometry object
- try:
- self.man_cutout_obj.multigeo = True
- self.man_cutout_obj.tools[1]['solid_geometry'] = new_solid_geometry
- self.man_cutout_obj.tools[1]['data']['name'] = self.man_cutout_obj.options['name'] + '_cutout'
- self.man_cutout_obj.tools[1]['data']['cutz'] = self.ui.cutz_entry.get_value()
- self.man_cutout_obj.tools[1]['data']['multidepth'] = self.ui.mpass_cb.get_value()
- self.man_cutout_obj.tools[1]['data']['depthperpass'] = self.ui.maxdepth_entry.get_value()
- except KeyError:
- self.app.inform.emit('[ERROR_NOTCL] %s' % _("No tool in the Geometry object."))
- return
- dia = self.ui.dia.get_value()
- if gaps_solid_geo:
- if 9999 not in self.man_cutout_obj.tools:
- self.man_cutout_obj.tools.update({
- 9999: self.cut_tool_dict
- })
- self.man_cutout_obj.tools[9999]['tooldia'] = str(dia)
- self.man_cutout_obj.tools[9999]['solid_geometry'] = [gaps_solid_geo]
- self.man_cutout_obj.tools[9999]['data']['name'] = self.man_cutout_obj.options['name'] + '_cutout'
- self.man_cutout_obj.tools[9999]['data']['cutz'] = self.ui.thin_depth_entry.get_value()
- self.man_cutout_obj.tools[9999]['data']['multidepth'] = self.ui.mpass_cb.get_value()
- self.man_cutout_obj.tools[9999]['data']['depthperpass'] = self.ui.maxdepth_entry.get_value()
- self.man_cutout_obj.tools[9999]['data']['override_color'] = "#29a3a3fa"
- else:
- self.man_cutout_obj.tools[9999]['solid_geometry'].append(gaps_solid_geo)
- self.man_cutout_obj.plot(plot_tool=1)
- self.app.inform.emit('%s' % _("Added manual Bridge Gap. Left click to add another or right click to finish."))
- self.app.should_we_save = True
- def on_manual_geo(self):
- name = self.ui.obj_combo.currentText()
- # Get source object.
- try:
- cutout_obj = self.app.collection.get_by_name(str(name))
- except Exception as e:
- log.debug("CutOut.on_manual_geo() --> %s" % str(e))
- self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), name))
- return "Could not retrieve object: %s" % name
- if cutout_obj is None:
- self.app.inform.emit('[ERROR_NOTCL] %s' %
- _("There is no Gerber object selected for Cutout.\n"
- "Select one and try again."))
- return
- if cutout_obj.kind != 'gerber':
- self.app.inform.emit('[ERROR_NOTCL] %s' %
- _("The selected object has to be of Gerber type.\n"
- "Select a Gerber file and try again."))
- return
- dia = float(self.ui.dia.get_value())
- if 0 in {dia}:
- self.app.inform.emit('[ERROR_NOTCL] %s' %
- _("Tool Diameter is zero value. Change it to a positive real number."))
- return
- try:
- kind = self.ui.obj_kind_combo.get_value()
- except ValueError:
- return
- margin = float(self.ui.margin.get_value())
- convex_box = self.ui.convex_box_cb.get_value()
- def geo_init(geo_obj, app_obj):
- geo_union = unary_union(cutout_obj.solid_geometry)
- if convex_box:
- geo = geo_union.convex_hull
- geo_obj.solid_geometry = geo.buffer(margin + abs(dia / 2))
- elif kind == 'single':
- if isinstance(geo_union, Polygon) or \
- (isinstance(geo_union, list) and len(geo_union) == 1) or \
- (isinstance(geo_union, MultiPolygon) and len(geo_union) == 1):
- geo_obj.solid_geometry = geo_union.buffer(margin + abs(dia / 2)).exterior
- elif isinstance(geo_union, MultiPolygon):
- x0, y0, x1, y1 = geo_union.bounds
- geo = box(x0, y0, x1, y1)
- geo_obj.solid_geometry = geo.buffer(margin + abs(dia / 2))
- else:
- app_obj.inform.emit('[ERROR_NOTCL] %s: %s' % (
- _("Geometry not supported"), type(geo_union)))
- return 'fail'
- else:
- geo = geo_union
- geo = geo.buffer(margin + abs(dia / 2))
- if isinstance(geo, Polygon):
- geo_obj.solid_geometry = geo.exterior
- elif isinstance(geo, MultiPolygon):
- solid_geo = []
- for poly in geo:
- solid_geo.append(poly.exterior)
- geo_obj.solid_geometry = deepcopy(solid_geo)
- geo_obj.options['cnctooldia'] = str(dia)
- geo_obj.options['cutz'] = self.ui.cutz_entry.get_value()
- geo_obj.options['multidepth'] = self.ui.mpass_cb.get_value()
- geo_obj.options['depthperpass'] = self.ui.maxdepth_entry.get_value()
- geo_obj.multigeo = True
- geo_obj.tools.update({
- 1: self.cut_tool_dict
- })
- geo_obj.tools[1]['tooldia'] = str(dia)
- geo_obj.tools[1]['solid_geometry'] = geo_obj.solid_geometry
- geo_obj.tools[1]['data']['name'] = outname
- geo_obj.tools[1]['data']['cutz'] = self.ui.cutz_entry.get_value()
- geo_obj.tools[1]['data']['multidepth'] = self.ui.mpass_cb.get_value()
- geo_obj.tools[1]['data']['depthperpass'] = self.ui.maxdepth_entry.get_value()
- outname = cutout_obj.options["name"] + "_cutout"
- self.app.app_obj.new_object('geometry', outname, geo_init)
- def cutting_geo(self, pos):
- self.cutting_dia = float(self.ui.dia.get_value())
- self.cutting_gapsize = float(self.ui.gapsize.get_value())
- offset = self.cutting_dia / 2 + self.cutting_gapsize / 2
- # cutting area definition
- orig_x = pos[0]
- orig_y = pos[1]
- xmin = orig_x - offset
- ymin = orig_y - offset
- xmax = orig_x + offset
- ymax = orig_y + offset
- cut_poly = box(xmin, ymin, xmax, ymax)
- return cut_poly
- # To be called after clicking on the plot.
- def on_mouse_click_release(self, event):
- if self.app.is_legacy is False:
- event_pos = event.pos
- # event_is_dragging = event.is_dragging
- right_button = 2
- else:
- event_pos = (event.xdata, event.ydata)
- # event_is_dragging = self.app.plotcanvas.is_dragging
- right_button = 3
- try:
- x = float(event_pos[0])
- y = float(event_pos[1])
- except TypeError:
- return
- event_pos = (x, y)
- # do paint single only for left mouse clicks
- if event.button == 1:
- self.app.inform.emit(_("Making manual bridge gap..."))
- pos = self.app.plotcanvas.translate_coords(event_pos)
- self.on_manual_cutout(click_pos=pos)
- # if RMB then we exit
- elif event.button == right_button and self.mouse_is_dragging is False:
- if self.app.is_legacy is False:
- self.app.plotcanvas.graph_event_disconnect('key_press', self.on_key_press)
- self.app.plotcanvas.graph_event_disconnect('mouse_move', self.on_mouse_move)
- self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_click_release)
- else:
- self.app.plotcanvas.graph_event_disconnect(self.kp)
- self.app.plotcanvas.graph_event_disconnect(self.mm)
- self.app.plotcanvas.graph_event_disconnect(self.mr)
- self.app.kp = self.app.plotcanvas.graph_event_connect('key_press', self.app.ui.keyPressEvent)
- self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press', self.app.on_mouse_click_over_plot)
- self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
- self.app.on_mouse_click_release_over_plot)
- self.app.mm = self.app.plotcanvas.graph_event_connect('mouse_move', self.app.on_mouse_move_over_plot)
- # Remove any previous utility shape
- self.app.geo_editor.tool_shape.clear(update=True)
- self.app.geo_editor.tool_shape.enabled = False
- # signal that the mouse events are disconnected from local methods
- self.mouse_events_connected = False
- if self.ui.big_cursor_cb.get_value():
- # restore cursor
- self.app.on_cursor_type(val=self.old_cursor_type)
- # restore selection
- self.app.defaults['global_selection_shape'] = self.old_selection_state
- # rebuild the manual Geometry object
- self.man_cutout_obj.build_ui()
- # plot the final object
- self.man_cutout_obj.plot()
- # mouse bytes
- if self.ui.gaptype_radio.get_value() == 'mb':
- with self.app.proc_container.new("Generating Excellon ..."):
- outname_exc = self.man_cutout_obj.options["name"] + "_mouse_bites"
- self.app.collection.promise(outname_exc)
- def job_thread(app_obj):
- # list of Shapely Points to mark the drill points centers
- holes = []
- mb_dia = self.ui.mb_dia_entry.get_value()
- mb_spacing = self.ui.mb_spacing_entry.get_value()
- for line in self.mb_manual_cuts:
- calc_len = 0
- while calc_len < line.length:
- holes.append(line.interpolate(calc_len))
- calc_len += mb_dia + mb_spacing
- self.mb_manual_cuts[:] = []
- def excellon_init(exc_obj, app_o):
- if not holes:
- return 'fail'
- tools = {
- 1: {
- "tooldia": mb_dia,
- "drills": holes,
- "solid_geometry": []
- }
- }
- exc_obj.tools = tools
- exc_obj.create_geometry()
- exc_obj.source_file = app_o.f_handlers.export_excellon(obj_name=exc_obj.options['name'],
- local_use=exc_obj,
- filename=None,
- use_thread=False)
- # calculate the bounds
- xmin, ymin, xmax, ymax = CutOut.recursive_bounds(exc_obj.solid_geometry)
- exc_obj.options['xmin'] = xmin
- exc_obj.options['ymin'] = ymin
- exc_obj.options['xmax'] = xmax
- exc_obj.options['ymax'] = ymax
- ret = app_obj.app_obj.new_object('excellon', outname_exc, excellon_init)
- if ret == 'fail':
- app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Mouse bites failed."))
- self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
- self.app.inform.emit('[success] %s' % _("Finished manual adding of gaps."))
- def on_mouse_move(self, event):
- self.app.on_mouse_move_over_plot(event=event)
- if self.app.is_legacy is False:
- event_pos = event.pos
- event_is_dragging = event.is_dragging
- # right_button = 2
- else:
- event_pos = (event.xdata, event.ydata)
- event_is_dragging = self.app.plotcanvas.is_dragging
- # right_button = 3
- try:
- x = float(event_pos[0])
- y = float(event_pos[1])
- except TypeError:
- return
- event_pos = (x, y)
- pos = self.canvas.translate_coords(event_pos)
- event.xdata, event.ydata = pos[0], pos[1]
- if event_is_dragging is True:
- self.mouse_is_dragging = True
- else:
- self.mouse_is_dragging = False
- try:
- x = float(event.xdata)
- y = float(event.ydata)
- except TypeError:
- return
- if self.app.grid_status():
- snap_x, snap_y = self.app.geo_editor.snap(x, y)
- else:
- snap_x, snap_y = x, y
- self.x_pos, self.y_pos = snap_x, snap_y
- # #################################################
- # ### This section makes the cutting geo to #######
- # ### rotate if it intersects the target geo ######
- # #################################################
- cut_geo = self.cutting_geo(pos=(snap_x, snap_y))
- man_geo = self.man_cutout_obj.solid_geometry
- def get_angle(geo):
- line = cut_geo.intersection(geo)
- try:
- pt1_x = line.coords[0][0]
- pt1_y = line.coords[0][1]
- pt2_x = line.coords[1][0]
- pt2_y = line.coords[1][1]
- dx = pt1_x - pt2_x
- dy = pt1_y - pt2_y
- if dx == 0 or dy == 0:
- angle = 0
- else:
- radian = math.atan(dx / dy)
- angle = radian * 180 / math.pi
- except Exception:
- angle = 0
- return angle
- try:
- rot_angle = 0
- for geo_el in man_geo:
- if isinstance(geo_el, Polygon):
- work_geo = geo_el.exterior
- if cut_geo.intersects(work_geo):
- rot_angle = get_angle(geo=work_geo)
- else:
- rot_angle = 0
- else:
- rot_angle = 0
- if cut_geo.intersects(geo_el):
- rot_angle = get_angle(geo=geo_el)
- if rot_angle != 0:
- break
- except TypeError:
- if isinstance(man_geo, Polygon):
- work_geo = man_geo.exterior
- if cut_geo.intersects(work_geo):
- rot_angle = get_angle(geo=work_geo)
- else:
- rot_angle = 0
- else:
- rot_angle = 0
- if cut_geo.intersects(man_geo):
- rot_angle = get_angle(geo=man_geo)
- # rotate only if there is an angle to rotate to
- if rot_angle != 0:
- cut_geo = affinity.rotate(cut_geo, -rot_angle)
- # Remove any previous utility shape
- self.app.geo_editor.tool_shape.clear(update=True)
- self.draw_utility_geometry(geo=cut_geo)
- def draw_utility_geometry(self, geo):
- self.app.geo_editor.tool_shape.add(
- shape=geo,
- color=(self.app.defaults["global_draw_color"] + '80'),
- update=False,
- layer=0,
- tolerance=None)
- self.app.geo_editor.tool_shape.redraw()
- def on_key_press(self, event):
- # events out of the self.app.collection view (it's about Project Tab) are of type int
- if type(event) is int:
- key = event
- # events from the GUI are of type QKeyEvent
- elif type(event) == QtGui.QKeyEvent:
- key = event.key()
- elif isinstance(event, mpl_key_event): # MatPlotLib key events are trickier to interpret than the rest
- key = event.key
- key = QtGui.QKeySequence(key)
- # check for modifiers
- key_string = key.toString().lower()
- if '+' in key_string:
- mod, __, key_text = key_string.rpartition('+')
- if mod.lower() == 'ctrl':
- # modifiers = QtCore.Qt.ControlModifier
- pass
- elif mod.lower() == 'alt':
- # modifiers = QtCore.Qt.AltModifier
- pass
- elif mod.lower() == 'shift':
- # modifiers = QtCore.Qt.ShiftModifier
- pass
- else:
- # modifiers = QtCore.Qt.NoModifier
- pass
- key = QtGui.QKeySequence(key_text)
- # events from Vispy are of type KeyEvent
- else:
- key = event.key
- # Escape = Deselect All
- if key == QtCore.Qt.Key_Escape or key == 'Escape':
- if self.mouse_events_connected is True:
- self.mouse_events_connected = False
- if self.app.is_legacy is False:
- self.app.plotcanvas.graph_event_disconnect('key_press', self.on_key_press)
- self.app.plotcanvas.graph_event_disconnect('mouse_move', self.on_mouse_move)
- self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_click_release)
- else:
- self.app.plotcanvas.graph_event_disconnect(self.kp)
- self.app.plotcanvas.graph_event_disconnect(self.mm)
- self.app.plotcanvas.graph_event_disconnect(self.mr)
- self.app.kp = self.app.plotcanvas.graph_event_connect('key_press', self.app.ui.keyPressEvent)
- self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press', self.app.on_mouse_click_over_plot)
- self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
- self.app.on_mouse_click_release_over_plot)
- self.app.mm = self.app.plotcanvas.graph_event_connect('mouse_move', self.app.on_mouse_move_over_plot)
- if self.ui.big_cursor_cb.get_value():
- # restore cursor
- self.app.on_cursor_type(val=self.old_cursor_type)
- # restore selection
- self.app.defaults['global_selection_shape'] = self.old_selection_state
- # Remove any previous utility shape
- self.app.geo_editor.tool_shape.clear(update=True)
- self.app.geo_editor.tool_shape.enabled = False
- # Grid toggle
- if key == QtCore.Qt.Key_G or key == 'G':
- self.app.ui.grid_snap_btn.trigger()
- # Jump to coords
- if key == QtCore.Qt.Key_J or key == 'J':
- l_x, l_y = self.app.on_jump_to()
- self.app.geo_editor.tool_shape.clear(update=True)
- geo = self.cutting_geo(pos=(l_x, l_y))
- self.draw_utility_geometry(geo=geo)
- @staticmethod
- def subtract_poly_from_geo(solid_geo, pts):
- """
- Subtract polygon made from points from the given object.
- This only operates on the paths in the original geometry,
- i.e. it converts polygons into paths.
- :param solid_geo: Geometry from which to subtract.
- :param pts: a tuple of coordinates in format (x0, y0, x1, y1)
- :type pts: tuple
- x0: x coord for lower left vertex of the polygon.
- y0: y coord for lower left vertex of the polygon.
- x1: x coord for upper right vertex of the polygon.
- y1: y coord for upper right vertex of the polygon.
- :return: none
- """
- x0 = pts[0]
- y0 = pts[1]
- x1 = pts[2]
- y1 = pts[3]
- points = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
- # pathonly should be always True, otherwise polygons are not subtracted
- flat_geometry = CutOut.flatten(geometry=solid_geo)
- log.debug("%d paths" % len(flat_geometry))
- polygon = Polygon(points)
- toolgeo = unary_union(polygon)
- diffs = []
- for target in flat_geometry:
- if type(target) == LineString or type(target) == LinearRing:
- diffs.append(target.difference(toolgeo))
- else:
- log.warning("Not implemented.")
- return unary_union(diffs)
- @staticmethod
- def flatten(geometry):
- """
- Creates a list of non-iterable linear geometry objects.
- Polygons are expanded into its exterior and interiors.
- Results are placed in self.flat_geometry
- :param geometry: Shapely type or list or list of list of such.
- """
- flat_geo = []
- try:
- for geo in geometry:
- if geo:
- flat_geo += CutOut.flatten(geometry=geo)
- except TypeError:
- if isinstance(geometry, Polygon) and not geometry.is_empty:
- flat_geo.append(geometry.exterior)
- CutOut.flatten(geometry=geometry.interiors)
- elif not geometry.is_empty:
- flat_geo.append(geometry)
- return flat_geo
- @staticmethod
- def recursive_bounds(geometry):
- """
- Return the bounds of the biggest bounding box in geometry, one that include all.
- :param geometry: a iterable object that holds geometry
- :return: Returns coordinates of rectangular bounds of geometry: (xmin, ymin, xmax, ymax).
- """
- # now it can get bounds for nested lists of objects
- def bounds_rec(obj):
- try:
- minx = Inf
- miny = Inf
- maxx = -Inf
- maxy = -Inf
- for k in obj:
- minx_, miny_, maxx_, maxy_ = bounds_rec(k)
- minx = min(minx, minx_)
- miny = min(miny, miny_)
- maxx = max(maxx, maxx_)
- maxy = max(maxy, maxy_)
- return minx, miny, maxx, maxy
- except TypeError:
- # it's a Shapely object, return it's bounds
- if obj:
- return obj.bounds
- return bounds_rec(geometry)
- @staticmethod
- def subtract_geo(target_geo, subtractor):
- """
- Subtract subtractor polygon from the target_geo. This only operates on the paths in the target_geo,
- i.e. it converts polygons into paths.
- :param target_geo: geometry from which to subtract
- :param subtractor: a list of Points, a LinearRing or a Polygon that will be subtracted from target_geo
- :return: a unary_union of the resulting geometry
- """
- if target_geo is None:
- target_geo = []
- # flatten() takes care of possible empty geometry making sure that is filtered
- flat_geometry = CutOut.flatten(target_geo)
- log.debug("%d paths" % len(flat_geometry))
- toolgeo = unary_union(subtractor)
- diffs = []
- for target in flat_geometry:
- if isinstance(target, LineString) or isinstance(target, LinearRing) or isinstance(target, MultiLineString):
- diffs.append(target.difference(toolgeo))
- else:
- log.warning("Not implemented.")
- return unary_union(diffs)
- @staticmethod
- def intersect_geo(target_geo, second_geo):
- """
- :param target_geo:
- :type target_geo:
- :param second_geo:
- :type second_geo:
- :return:
- :rtype:
- """
- results = []
- try:
- __ = iter(target_geo)
- except TypeError:
- target_geo = [target_geo]
- for geo in target_geo:
- if second_geo.intersects(geo):
- results.append(second_geo.intersection(geo))
- return CutOut.flatten(results)
- def reset_fields(self):
- self.ui.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
- class CutoutUI:
- toolName = _("Cutout PCB")
- def __init__(self, layout, app):
- self.app = app
- self.decimals = self.app.decimals
- self.layout = layout
- # Title
- title_label = QtWidgets.QLabel("%s" % self.toolName)
- title_label.setStyleSheet("""
- QLabel
- {
- font-size: 16px;
- font-weight: bold;
- }
- """)
- self.layout.addWidget(title_label)
- self.layout.addWidget(QtWidgets.QLabel(''))
- # Form Layout
- grid0 = QtWidgets.QGridLayout()
- grid0.setColumnStretch(0, 0)
- grid0.setColumnStretch(1, 1)
- self.layout.addLayout(grid0)
- self.object_label = QtWidgets.QLabel('<b>%s:</b>' % _("Source Object"))
- self.object_label.setToolTip('%s.' % _("Object to be cutout"))
- grid0.addWidget(self.object_label, 0, 0, 1, 2)
- # Object kind
- self.kindlabel = QtWidgets.QLabel('%s:' % _('Kind'))
- self.kindlabel.setToolTip(
- _("Choice of what kind the object we want to cutout is.\n"
- "- Single: contain a single PCB Gerber outline object.\n"
- "- Panel: a panel PCB Gerber object, which is made\n"
- "out of many individual PCB outlines.")
- )
- self.obj_kind_combo = RadioSet([
- {"label": _("Single"), "value": "single"},
- {"label": _("Panel"), "value": "panel"},
- ])
- grid0.addWidget(self.kindlabel, 2, 0)
- grid0.addWidget(self.obj_kind_combo, 2, 1)
- # Type of object to be cutout
- self.type_obj_radio = RadioSet([
- {"label": _("Gerber"), "value": "grb"},
- {"label": _("Geometry"), "value": "geo"},
- ])
- self.type_obj_combo_label = QtWidgets.QLabel('%s:' % _("Type"))
- self.type_obj_combo_label.setToolTip(
- _("Specify the type of object to be cutout.\n"
- "It can be of type: Gerber or Geometry.\n"
- "What is selected here will dictate the kind\n"
- "of objects that will populate the 'Object' combobox.")
- )
- grid0.addWidget(self.type_obj_combo_label, 4, 0)
- grid0.addWidget(self.type_obj_radio, 4, 1)
- # Object to be cutout
- self.obj_combo = FCComboBox()
- self.obj_combo.setModel(self.app.collection)
- self.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
- self.obj_combo.is_last = True
- grid0.addWidget(self.obj_combo, 6, 0, 1, 2)
- # Convex Shape
- # Surrounding convex box shape
- self.convex_box_label = QtWidgets.QLabel('%s:' % _("Convex Shape"))
- self.convex_box_label.setToolTip(
- _("Create a convex shape surrounding the entire PCB.\n"
- "Used only if the source object type is Gerber.")
- )
- self.convex_box_cb = FCCheckBox()
- self.convex_box_cb.setToolTip(
- _("Create a convex shape surrounding the entire PCB.\n"
- "Used only if the source object type is Gerber.")
- )
- grid0.addWidget(self.convex_box_label, 8, 0)
- grid0.addWidget(self.convex_box_cb, 8, 1)
- separator_line = QtWidgets.QFrame()
- separator_line.setFrameShape(QtWidgets.QFrame.HLine)
- separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
- grid0.addWidget(separator_line, 10, 0, 1, 2)
- self.tool_sel_label = FCLabel('<b>%s</b>' % _('Cutout Tool'))
- grid0.addWidget(self.tool_sel_label, 12, 0, 1, 2)
- # Tool Diameter
- self.dia = FCDoubleSpinner(callback=self.confirmation_message)
- self.dia.set_precision(self.decimals)
- self.dia.set_range(0.0000, 10000.0000)
- self.dia_label = QtWidgets.QLabel('%s:' % _("Tool Dia"))
- self.dia_label.setToolTip(
- _("Diameter of the tool used to cutout\n"
- "the PCB shape out of the surrounding material.")
- )
- grid0.addWidget(self.dia_label, 14, 0)
- grid0.addWidget(self.dia, 14, 1)
- hlay = QtWidgets.QHBoxLayout()
- # Search and Add new Tool
- self.add_newtool_button = FCButton(_('Search and Add'))
- self.add_newtool_button.setIcon(QtGui.QIcon(self.app.resource_location + '/plus16.png'))
- self.add_newtool_button.setToolTip(
- _("Add a new tool to the Tool Table\n"
- "with the diameter specified above.\n"
- "This is done by a background search\n"
- "in the Tools Database. If nothing is found\n"
- "in the Tools DB then a default tool is added.")
- )
- hlay.addWidget(self.add_newtool_button)
- # Pick from DB new Tool
- self.addtool_from_db_btn = FCButton(_('Pick from DB'))
- self.addtool_from_db_btn.setIcon(QtGui.QIcon(self.app.resource_location + '/search_db32.png'))
- self.addtool_from_db_btn.setToolTip(
- _("Add a new tool to the Tool Table\n"
- "from the Tools Database.\n"
- "Tools database administration in in:\n"
- "Menu: Options -> Tools Database")
- )
- hlay.addWidget(self.addtool_from_db_btn)
- grid0.addLayout(hlay, 16, 0, 1, 2)
- separator_line = QtWidgets.QFrame()
- separator_line.setFrameShape(QtWidgets.QFrame.HLine)
- separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
- grid0.addWidget(separator_line, 18, 0, 1, 2)
- self.param_label = QtWidgets.QLabel('<b>%s:</b>' % _("Tool Parameters"))
- grid0.addWidget(self.param_label, 20, 0, 1, 2)
- # Cut Z
- cutzlabel = QtWidgets.QLabel('%s:' % _('Cut Z'))
- cutzlabel.setToolTip(
- _(
- "Cutting depth (negative)\n"
- "below the copper surface."
- )
- )
- self.cutz_entry = FCDoubleSpinner(callback=self.confirmation_message)
- self.cutz_entry.set_precision(self.decimals)
- if machinist_setting == 0:
- self.cutz_entry.setRange(-10000.0000, -0.00001)
- else:
- self.cutz_entry.setRange(-10000.0000, 10000.0000)
- self.cutz_entry.setSingleStep(0.1)
- grid0.addWidget(cutzlabel, 22, 0)
- grid0.addWidget(self.cutz_entry, 22, 1)
- # Multi-pass
- self.mpass_cb = FCCheckBox('%s:' % _("Multi-Depth"))
- self.mpass_cb.setToolTip(
- _(
- "Use multiple passes to limit\n"
- "the cut depth in each pass. Will\n"
- "cut multiple times until Cut Z is\n"
- "reached."
- )
- )
- self.maxdepth_entry = FCDoubleSpinner(callback=self.confirmation_message)
- self.maxdepth_entry.set_precision(self.decimals)
- self.maxdepth_entry.setRange(0, 10000.0000)
- self.maxdepth_entry.setSingleStep(0.1)
- self.maxdepth_entry.setToolTip(
- _(
- "Depth of each pass (positive)."
- )
- )
- grid0.addWidget(self.mpass_cb, 24, 0)
- grid0.addWidget(self.maxdepth_entry, 24, 1)
- self.ois_mpass_geo = OptionalInputSection(self.mpass_cb, [self.maxdepth_entry])
- # Margin
- self.margin = FCDoubleSpinner(callback=self.confirmation_message)
- self.margin.set_range(-10000.0000, 10000.0000)
- self.margin.setSingleStep(0.1)
- self.margin.set_precision(self.decimals)
- self.margin_label = QtWidgets.QLabel('%s:' % _("Margin"))
- self.margin_label.setToolTip(
- _("Margin over bounds. A positive value here\n"
- "will make the cutout of the PCB further from\n"
- "the actual PCB border")
- )
- grid0.addWidget(self.margin_label, 26, 0)
- grid0.addWidget(self.margin, 26, 1)
- # Gapsize
- self.gapsize_label = QtWidgets.QLabel('%s:' % _("Gap size"))
- self.gapsize_label.setToolTip(
- _("The size of the bridge gaps in the cutout\n"
- "used to keep the board connected to\n"
- "the surrounding material (the one \n"
- "from which the PCB is cutout).")
- )
- self.gapsize = FCDoubleSpinner(callback=self.confirmation_message)
- self.gapsize.set_precision(self.decimals)
- grid0.addWidget(self.gapsize_label, 28, 0)
- grid0.addWidget(self.gapsize, 28, 1)
- # Gap Type
- self.gaptype_label = FCLabel('%s:' % _("Gap type"))
- self.gaptype_label.setToolTip(
- _("The type of gap:\n"
- "- Bridge -> the cutout will be interrupted by bridges\n"
- "- Thin -> same as 'bridge' but it will be thinner by partially milling the gap\n"
- "- M-Bites -> 'Mouse Bites' - same as 'bridge' but covered with drill holes")
- )
- self.gaptype_radio = RadioSet(
- [
- {'label': _('Bridge'), 'value': 'b'},
- {'label': _('Thin'), 'value': 'bt'},
- {'label': "M-Bites", 'value': 'mb'}
- ],
- stretch=True
- )
- grid0.addWidget(self.gaptype_label, 30, 0)
- grid0.addWidget(self.gaptype_radio, 30, 1)
- # Thin gaps Depth
- self.thin_depth_label = FCLabel('%s:' % _("Depth"))
- self.thin_depth_label.setToolTip(
- _("The depth until the milling is done\n"
- "in order to thin the gaps.")
- )
- self.thin_depth_entry = FCDoubleSpinner(callback=self.confirmation_message)
- self.thin_depth_entry.set_precision(self.decimals)
- if machinist_setting == 0:
- self.thin_depth_entry.setRange(-10000.0000, -0.00001)
- else:
- self.thin_depth_entry.setRange(-10000.0000, 10000.0000)
- self.thin_depth_entry.setSingleStep(0.1)
- grid0.addWidget(self.thin_depth_label, 32, 0)
- grid0.addWidget(self.thin_depth_entry, 32, 1)
- # Mouse Bites Tool Diameter
- self.mb_dia_label = FCLabel('%s:' % _("Tool Diameter"))
- self.mb_dia_label.setToolTip(
- _("The drill hole diameter when doing mouse bites.")
- )
- self.mb_dia_entry = FCDoubleSpinner(callback=self.confirmation_message)
- self.mb_dia_entry.set_precision(self.decimals)
- self.mb_dia_entry.setRange(0, 100.0000)
- grid0.addWidget(self.mb_dia_label, 34, 0)
- grid0.addWidget(self.mb_dia_entry, 34, 1)
- # Mouse Bites Holes Spacing
- self.mb_spacing_label = FCLabel('%s:' % _("Spacing"))
- self.mb_spacing_label.setToolTip(
- _("The spacing between drill holes when doing mouse bites.")
- )
- self.mb_spacing_entry = FCDoubleSpinner(callback=self.confirmation_message)
- self.mb_spacing_entry.set_precision(self.decimals)
- self.mb_spacing_entry.setRange(0, 100.0000)
- grid0.addWidget(self.mb_spacing_label, 36, 0)
- grid0.addWidget(self.mb_spacing_entry, 36, 1)
- separator_line = QtWidgets.QFrame()
- separator_line.setFrameShape(QtWidgets.QFrame.HLine)
- separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
- grid0.addWidget(separator_line, 38, 0, 1, 2)
- # Title2
- title_param_label = QtWidgets.QLabel("<b>%s %s</b>:" % (_('Automatic'), _("Bridge Gaps")))
- title_param_label.setToolTip(
- _("This section handle creation of automatic bridge gaps.")
- )
- grid0.addWidget(title_param_label, 40, 0, 1, 2)
- # Gaps
- # How gaps wil be rendered:
- # lr - left + right
- # tb - top + bottom
- # 4 - left + right +top + bottom
- # 2lr - 2*left + 2*right
- # 2tb - 2*top + 2*bottom
- # 8 - 2*left + 2*right +2*top + 2*bottom
- gaps_label = QtWidgets.QLabel('%s:' % _('Gaps'))
- gaps_label.setToolTip(
- _("Number of gaps used for the Automatic cutout.\n"
- "There can be maximum 8 bridges/gaps.\n"
- "The choices are:\n"
- "- None - no gaps\n"
- "- lr - left + right\n"
- "- tb - top + bottom\n"
- "- 4 - left + right +top + bottom\n"
- "- 2lr - 2*left + 2*right\n"
- "- 2tb - 2*top + 2*bottom\n"
- "- 8 - 2*left + 2*right +2*top + 2*bottom")
- )
- # gaps_label.setMinimumWidth(60)
- self.gaps = FCComboBox()
- gaps_items = ['None', 'LR', 'TB', '4', '2LR', '2TB', '8']
- for it in gaps_items:
- self.gaps.addItem(it)
- # self.gaps.setStyleSheet('background-color: rgb(255,255,255)')
- grid0.addWidget(gaps_label, 42, 0)
- grid0.addWidget(self.gaps, 42, 1)
- # Buttons
- self.ff_cutout_object_btn = FCButton(_("Generate Geometry"))
- self.ff_cutout_object_btn.setIcon(QtGui.QIcon(self.app.resource_location + '/irregular32.png'))
- self.ff_cutout_object_btn.setToolTip(
- _("Cutout the selected object.\n"
- "The cutout shape can be of any shape.\n"
- "Useful when the PCB has a non-rectangular shape.")
- )
- self.ff_cutout_object_btn.setStyleSheet("""
- QPushButton
- {
- font-weight: bold;
- }
- """)
- grid0.addWidget(self.ff_cutout_object_btn, 44, 0, 1, 2)
- self.rect_cutout_object_btn = FCButton(_("Generate Geometry"))
- self.rect_cutout_object_btn.setIcon(QtGui.QIcon(self.app.resource_location + '/rectangle32.png'))
- self.rect_cutout_object_btn.setToolTip(
- _("Cutout the selected object.\n"
- "The resulting cutout shape is\n"
- "always a rectangle shape and it will be\n"
- "the bounding box of the Object.")
- )
- self.rect_cutout_object_btn.setStyleSheet("""
- QPushButton
- {
- font-weight: bold;
- }
- """)
- grid0.addWidget(self.rect_cutout_object_btn, 46, 0, 1, 2)
- separator_line = QtWidgets.QFrame()
- separator_line.setFrameShape(QtWidgets.QFrame.HLine)
- separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
- grid0.addWidget(separator_line, 48, 0, 1, 2)
- # MANUAL BRIDGE GAPS
- title_manual_label = QtWidgets.QLabel("<b>%s %s</b>:" % (_('Manual'), _("Bridge Gaps")))
- title_manual_label.setToolTip(
- _("This section handle creation of manual bridge gaps.\n"
- "This is done by mouse clicking on the perimeter of the\n"
- "Geometry object that is used as a cutout object. ")
- )
- grid0.addWidget(title_manual_label, 50, 0, 1, 2)
- # Big Cursor
- big_cursor_label = QtWidgets.QLabel('%s:' % _("Big cursor"))
- big_cursor_label.setToolTip(
- _("Use a big cursor when adding manual gaps."))
- self.big_cursor_cb = FCCheckBox()
- grid0.addWidget(big_cursor_label, 52, 0)
- grid0.addWidget(self.big_cursor_cb, 52, 1)
- # Generate a surrounding Geometry object
- self.man_geo_creation_btn = FCButton(_("Generate Manual Geometry"))
- self.man_geo_creation_btn.setIcon(QtGui.QIcon(self.app.resource_location + '/rectangle32.png'))
- self.man_geo_creation_btn.setToolTip(
- _("If the object to be cutout is a Gerber\n"
- "first create a Geometry that surrounds it,\n"
- "to be used as the cutout, if one doesn't exist yet.\n"
- "Select the source Gerber file in the top object combobox.")
- )
- # self.man_geo_creation_btn.setStyleSheet("""
- # QPushButton
- # {
- # font-weight: bold;
- # }
- # """)
- grid0.addWidget(self.man_geo_creation_btn, 54, 0, 1, 2)
- # Manual Geo Object
- self.man_object_combo = FCComboBox()
- self.man_object_combo.setModel(self.app.collection)
- self.man_object_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))
- self.man_object_combo.is_last = True
- self.man_object_combo.obj_type = "Geometry"
- self.man_object_label = QtWidgets.QLabel('%s:' % _("Manual cutout Geometry"))
- self.man_object_label.setToolTip(
- _("Geometry object used to create the manual cutout.")
- )
- # self.man_object_label.setMinimumWidth(60)
- grid0.addWidget(self.man_object_label, 56, 0, 1, 2)
- grid0.addWidget(self.man_object_combo, 56, 0, 1, 2)
- self.man_gaps_creation_btn = FCButton(_("Manual Add Bridge Gaps"))
- self.man_gaps_creation_btn.setIcon(QtGui.QIcon(self.app.resource_location + '/gaps32.png'))
- self.man_gaps_creation_btn.setToolTip(
- _("Use the left mouse button (LMB) click\n"
- "to create a bridge gap to separate the PCB from\n"
- "the surrounding material.\n"
- "The LMB click has to be done on the perimeter of\n"
- "the Geometry object used as a cutout geometry.")
- )
- self.man_gaps_creation_btn.setStyleSheet("""
- QPushButton
- {
- font-weight: bold;
- }
- """)
- grid0.addWidget(self.man_gaps_creation_btn, 58, 0, 1, 2)
- self.layout.addStretch()
- # ## Reset Tool
- self.reset_button = FCButton(_("Reset Tool"))
- self.reset_button.setIcon(QtGui.QIcon(self.app.resource_location + '/reset32.png'))
- self.reset_button.setToolTip(
- _("Will reset the tool parameters.")
- )
- self.reset_button.setStyleSheet("""
- QPushButton
- {
- font-weight: bold;
- }
- """)
- self.layout.addWidget(self.reset_button)
- self.gaptype_radio.activated_custom.connect(self.on_gap_type_radio)
- # ############################ FINSIHED GUI ###################################
- # #############################################################################
- def on_gap_type_radio(self, val):
- if val == 'b':
- self.thin_depth_label.hide()
- self.thin_depth_entry.hide()
- self.mb_dia_label.hide()
- self.mb_dia_entry.hide()
- self.mb_spacing_label.hide()
- self.mb_spacing_entry.hide()
- elif val == 'bt':
- self.thin_depth_label.show()
- self.thin_depth_entry.show()
- self.mb_dia_label.hide()
- self.mb_dia_entry.hide()
- self.mb_spacing_label.hide()
- self.mb_spacing_entry.hide()
- elif val == 'mb':
- self.thin_depth_label.hide()
- self.thin_depth_entry.hide()
- self.mb_dia_label.show()
- self.mb_dia_entry.show()
- self.mb_spacing_label.show()
- self.mb_spacing_entry.show()
- def confirmation_message(self, accepted, minval, maxval):
- if accepted is False:
- self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' % (_("Edited value is out of range"),
- self.decimals,
- minval,
- self.decimals,
- maxval), False)
- else:
- self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
- def confirmation_message_int(self, accepted, minval, maxval):
- if accepted is False:
- self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%d, %d]' %
- (_("Edited value is out of range"), minval, maxval), False)
- else:
- self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
|