diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 9455ed8b..711a6d66 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -2137,7 +2137,7 @@ class App(QtCore.QObject): def geocutout(name, *args): """ - cut gaps in current geometry + subtract gaps from geometry, this will not create new object :param name: :param args: @@ -2148,6 +2148,14 @@ class App(QtCore.QObject): 'gapsize': float, 'gaps': str} + #way 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 + for key in kwa: if key not in types: return 'Unknown parameter: %s' % key @@ -2159,15 +2167,23 @@ class App(QtCore.QObject): return "Could not retrieve object: %s" % name - + #get min and max data for each object as we just cut rectangles across X or Y xmin, ymin, xmax, ymax = obj.bounds() px = 0.5 * (xmin + xmax) py = 0.5 * (ymin + ymax) + lenghtx = (xmax - xmin) + lenghty = (ymax - ymin) gapsize = kwa['gapsize']+kwa['dia']/2 + if kwa['gaps'] == '8' or kwa['gaps']=='2lr': + subtract_rectangle(name,xmin-gapsize,py-gapsize+lenghty/4,xmax+gapsize,py+gapsize+lenghty/4) + subtract_rectangle(name,xmin-gapsize,py-gapsize-lenghty/4,xmax+gapsize,py+gapsize-lenghty/4) + if kwa['gaps'] == '8' or kwa['gaps']=='2tb': + subtract_rectangle(name,px-gapsize+lenghtx/4,ymin-gapsize,px+gapsize+lenghtx/4,ymax+gapsize) + subtract_rectangle(name,px-gapsize-lenghtx/4,ymin-gapsize,px+gapsize-lenghtx/4,ymax+gapsize) if kwa['gaps'] == '4' or kwa['gaps']=='lr': - del_rectangle(name,xmin-gapsize,py-gapsize,xmax+gapsize,py+gapsize) + subtract_rectangle(name,xmin-gapsize,py-gapsize,xmax+gapsize,py+gapsize) if kwa['gaps'] == '4' or kwa['gaps']=='tb': - del_rectangle(name,px-gapsize,ymin-gapsize,px+gapsize,ymax+gapsize) + subtract_rectangle(name,px-gapsize,ymin-gapsize,px+gapsize,ymax+gapsize) return 'Ok' def mirror(name, *args): @@ -2299,6 +2315,7 @@ class App(QtCore.QObject): 'axis': str, 'holes': str, 'grid': float, + 'minoffset': float, 'gridoffset': float, 'axisoffset': float, 'dia': float, @@ -2359,25 +2376,22 @@ class App(QtCore.QObject): else: axisoffset=0 - + #this will align hole to given aligngridoffset and minimal offset from pcb, based on selected axis if axis == "X": - firstpoint=-kwa['gridoffset']+xmin - #-5 - minlenght=(xmax-xmin+2*kwa['gridoffset']) - #57+10=67 - gridstripped=(minlenght//kwa['grid'])*kwa['grid'] - #67//10=60 - if (minlenght-gridstripped) >kwa['gridoffset']: - gridstripped=gridstripped+kwa['grid'] - lastpoint=(firstpoint+gridstripped) + firstpoint=kwa['gridoffset'] + while (xmin-kwa['minoffset'])lastpoint: + lastpoint=lastpoint+kwa['grid'] localHoles=(firstpoint,axisoffset),(lastpoint,axisoffset) else: - firstpoint=-kwa['gridoffset']+ymin - minlenght=(ymax-ymin+2*kwa['gridoffset']) - gridstripped=minlenght//kwa['grid']*kwa['grid'] - if (minlenght-gridstripped) >kwa['gridoffset']: - gridstripped=gridstripped+kwa['grid'] - lastpoint=(firstpoint+gridstripped) + firstpoint=kwa['gridoffset'] + while (ymin-kwa['minoffset'])lastpoint: + lastpoint=lastpoint+kwa['grid'] localHoles=(axisoffset,firstpoint),(axisoffset,lastpoint) for hole in localHoles: @@ -2456,26 +2470,26 @@ class App(QtCore.QObject): return "ERROR: Only Excellon objects can be drilled." try: - + # Get the tools from the list job_name = kwa["outname"] - + # Object initialization function for app.new_object() def job_init(job_obj, app_obj): assert isinstance(job_obj, FlatCAMCNCjob), \ "Initializer expected FlatCAMCNCjob, got %s" % type(job_obj) - + job_obj.z_cut = kwa["drillz"] job_obj.z_move = kwa["travelz"] job_obj.feedrate = kwa["feedrate"] job_obj.spindlespeed = kwa["spindlespeed"] if "spindlespeed" in kwa else None toolchange = True if "toolchange" in kwa and kwa["toolchange"] == 1 else False job_obj.generate_from_excellon_by_tool(obj, kwa["tools"], toolchange) - + job_obj.gcode_parse() - + job_obj.create_geometry() - + obj.app.new_object("cncjob", job_name, job_init) except Exception, e: @@ -2600,7 +2614,7 @@ class App(QtCore.QObject): types = {'dia': float, 'passes': int, 'overlap': float, - 'outname': str, + 'outname': str, 'combine': int} for key in kwa: @@ -2634,7 +2648,9 @@ class App(QtCore.QObject): 'feedrate': float, 'tooldia': float, 'outname': str, - 'spindlespeed': int + 'spindlespeed': int, + 'multidepth' : bool, + 'depthperpass' : float } for key in kwa: @@ -2718,7 +2734,7 @@ class App(QtCore.QObject): return add_poly(obj_name, botleft_x, botleft_y, botleft_x, topright_y, topright_x, topright_y, topright_x, botleft_y) - def del_poly(obj_name, *args): + def subtract_poly(obj_name, *args): if len(args) % 2 != 0: return "Incomplete coordinate." @@ -2731,19 +2747,13 @@ class App(QtCore.QObject): if obj is None: return "Object not found: %s" % obj_name - def init_obj_me(init_obj, app): - assert isinstance(init_obj, FlatCAMGeometry) - init_obj.solid_geometry=cascaded_union(diff) + obj.subtract_polygon(points) + obj.plot() - diff= obj.del_polygon(points) - try: - delete(obj_name) - obj.app.new_object("geometry", obj_name, init_obj_me) - except Exception as e: - return "Failed: %s" % str(e) + return "OK." - def del_rectangle(obj_name, botleft_x, botleft_y, topright_x, topright_y): - return del_poly(obj_name, botleft_x, botleft_y, botleft_x, topright_y, + def subtract_rectangle(obj_name, botleft_x, botleft_y, topright_x, topright_y): + return subtract_poly(obj_name, botleft_x, botleft_y, botleft_x, topright_y, topright_x, topright_y, topright_x, botleft_y) def add_circle(obj_name, center_x, center_y, radius): @@ -2764,6 +2774,8 @@ class App(QtCore.QObject): def delete(obj_name): try: + #deselect all to avoid delete selected object when run delete from shell + self.collection.set_all_inactive() self.collection.set_active(str(obj_name)) self.on_delete() except Exception, e: @@ -2805,7 +2817,7 @@ class App(QtCore.QObject): objs.append(obj) def initialize(obj, app): - FlatCAMExcellon.merge(objs, obj,True) + FlatCAMExcellon.merge(objs, obj) if objs is not None: self.new_object("excellon", obj_name, initialize) @@ -2870,14 +2882,14 @@ class App(QtCore.QObject): obj_init.offset([float(currentx), float(currenty)]), def initialize_local_excellon(obj_init, app): - FlatCAMExcellon.merge(obj, obj_init,True) + FlatCAMExcellon.merge(obj, obj_init) obj_init.offset([float(currentx), float(currenty)]), def initialize_geometry(obj_init, app): FlatCAMGeometry.merge(objs, obj_init) def initialize_excellon(obj_init, app): - FlatCAMExcellon.merge(objs, obj_init,True) + FlatCAMExcellon.merge(objs, obj_init) objs=[] if obj is not None: @@ -2899,7 +2911,8 @@ class App(QtCore.QObject): else: self.new_object("geometry", outname, initialize_geometry) - + #deselect all to avoid delete selected object when run delete from shell + self.collection.set_all_inactive() for delobj in objs: self.collection.set_active(delobj.options['name']) self.on_delete() @@ -3091,8 +3104,8 @@ class App(QtCore.QObject): }, 'geocutout': { 'fcn': geocutout, - 'help': "Cut holding gaps closed geometry.\n" + - "> geocutout [-dia <3.0 (float)>] [-margin <0.0 (float)>] [-gapsize <0.5 (float)>] [-gaps ]\n" + + 'help': "Cut holding gaps from geometry.\n" + + "> geocutout [-dia <3.0 (float)>] [-margin <0.0 (float)>] [-gapsize <0.5 (float)>] [-gaps ]\n" + " name: Name of the geometry object\n" + " dia: Tool diameter\n" + " margin: Margin over bounds\n" + @@ -3138,12 +3151,13 @@ class App(QtCore.QObject): 'aligndrill': { 'fcn': aligndrill, 'help': "Create excellon with drills for aligment.\n" + - "> aligndrill [-dia <3.0 (float)>] -axis [-box [-grid <10 (float)> -gridoffset <5 (float)> [-axisoffset <0 (float)>]] | -dist ]\n" + + "> aligndrill [-dia <3.0 (float)>] -axis [-box -minoffset [-grid <10 (float)> -gridoffset <5 (float)> [-axisoffset <0 (float)>]] | -dist ]\n" + " name: Name of the object (Gerber or Excellon) to mirror.\n" + " dia: Tool diameter\n" + " box: Name of object which act as box (cutout for example.)\n" + " grid: aligning to grid, for thouse, who have aligning pins inside table in grid (-5,0),(5,0),(15,0)..." + - " gridoffset: offset from pcb from 0 position and minimal offset to grid on max" + + " gridoffset: offset of grid from 0 position" + + " minoffset: min and max distance between align hole and pcb" + " axisoffset: offset on second axis before aligment holes" + " axis: Mirror axis parallel to the X or Y axis.\n" + " dist: Distance of the mirror axis to the X or Y axis." @@ -3207,13 +3221,15 @@ class App(QtCore.QObject): 'cncjob': { 'fcn': cncjob, 'help': 'Generates a CNC Job from a Geometry Object.\n' + - '> cncjob [-z_cut ] [-z_move ] [-feedrate ] [-tooldia ] [-spindlespeed (int)] [-outname ]\n' + + '> cncjob [-z_cut ] [-z_move ] [-feedrate ] [-tooldia ] [-spindlespeed ] [-multidepth ] [-depthperpass ] [-outname ]\n' + ' name: Name of the source object\n' + ' z_cut: Z-axis cutting position\n' + ' z_move: Z-axis moving position\n' + ' feedrate: Moving speed when cutting\n' + ' tooldia: Tool diameter to show on screen\n' + ' spindlespeed: Speed of the spindle in rpm (example: 4000)\n' + + ' multidepth: Use or not multidepth cnccut\n'+ + ' depthperpass: Height of one layer for multidepth\n'+ ' outname: Name of the output object' }, 'write_gcode': { @@ -3245,11 +3261,11 @@ class App(QtCore.QObject): ' name: Name of the geometry object to which to append the polygon.\n' + ' xi, yi: Coordinates of points in the polygon.' }, - 'del_poly': { - 'fcn': del_poly, - 'help': ' - Remove a polygon from the given Geometry object.\n' + - '> del_poly [x3 y3 [...]]\n' + - ' name: Name of the geometry object to which to remove the polygon.\n' + + 'subtract_poly': { + 'fcn': subtract_poly, + 'help': 'Subtract polygon from the given Geometry object.\n' + + '> subtract_poly [x3 y3 [...]]\n' + + ' name: Name of the geometry object, which will be sutracted.\n' + ' xi, yi: Coordinates of points in the polygon.' }, 'delete': { @@ -3295,11 +3311,11 @@ class App(QtCore.QObject): " rows: number of rows\n"+ " outname: Name of the new geometry object." }, - 'del_rect': { - 'fcn': del_rectangle, - 'help': 'Delete a rectange from the given Geometry object.\n' + - '> del_rect \n' + - ' name: Name of the geometry object to which to remove the rectangle.\n' + + 'subtract_rect': { + 'fcn': subtract_rectangle, + 'help': 'Subtract rectange from the given Geometry object.\n' + + '> subtract_rect \n' + + ' name: Name of the geometry object, which will be subtracted.\n' + ' botleft_x, botleft_y: Coordinates of the bottom left corner.\n' + ' topright_x, topright_y Coordinates of the top right corner.' }, @@ -3412,13 +3428,17 @@ class App(QtCore.QObject): for recent in self.recent: filename = recent['filename'].split('/')[-1].split('\\')[-1] - action = QtGui.QAction(QtGui.QIcon(icons[recent["kind"]]), filename, self) + try: + action = QtGui.QAction(QtGui.QIcon(icons[recent["kind"]]), filename, self) - # Attach callback - o = make_callback(openers[recent["kind"]], recent['filename']) - action.triggered.connect(o) + # Attach callback + o = make_callback(openers[recent["kind"]], recent['filename']) + action.triggered.connect(o) - self.ui.recent.addAction(action) + self.ui.recent.addAction(action) + + except KeyError: + App.log.error("Unsupported file type: %s" % recent["kind"]) # self.builder.get_object('open_recent').set_submenu(recent_menu) # self.ui.menufilerecent.set_submenu(recent_menu) diff --git a/FlatCAMObj.py b/FlatCAMObj.py index 69eea0de..b8315fc5 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -123,8 +123,12 @@ class FlatCAMObj(QtCore.QObject): :return: None """ + FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> FlatCAMObj.to_form()") for option in self.options: - self.set_form_item(option) + try: + self.set_form_item(option) + except: + self.app.log.warning("Unexpected error:", sys.exc_info()) def read_form(self): """ @@ -135,7 +139,10 @@ class FlatCAMObj(QtCore.QObject): """ FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> FlatCAMObj.read_form()") for option in self.options: - self.read_form_item(option) + try: + self.read_form_item(option) + except: + self.app.log.warning("Unexpected error:", sys.exc_info()) def build_ui(self): """ @@ -197,6 +204,17 @@ class FlatCAMObj(QtCore.QObject): except KeyError: self.app.log.warning("Failed to read option from field: %s" % option) + # #try read field only when option have equivalent in form_fields + # if option in self.form_fields: + # option_type=type(self.options[option]) + # try: + # value=self.form_fields[option].get_value() + # #catch per option as it was ignored anyway, also when syntax error (probably uninitialized field),don't read either. + # except (KeyError,SyntaxError): + # self.app.log.warning("Failed to read option from field: %s" % option) + # else: + # self.app.log.warning("Form fied does not exists: %s" % option) + def plot(self): """ Plot this object (Extend this method to implement the actual plotting). @@ -631,13 +649,16 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): self.ser_attrs += ['options', 'kind'] @staticmethod - def merge(exc_list, exc_final, copy_options): + def merge(exc_list, exc_final): """ - Merges(copy if used on one) the excellon of objects in exc_list into - options have same like exc_final - the geometry of geo_final. + Merge excellons in exc_list into exc_final. + Options are allways copied from source . - :param exc_list: List of FlatCAMExcellon Objects to join. + Tools are also merged, if name for tool is same and size differs, then as name is used next available number from both lists + + if only one object is specified in exc_list then this acts as copy only + + :param exc_list: List or one object of FlatCAMExcellon Objects to join. :param exc_final: Destination FlatCAMExcellon object. :return: None """ @@ -648,26 +669,27 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): else: exc_list_real=exc_list - for exc in exc_list_real: # Expand lists if type(exc) is list: - FlatCAMExcellon.merge(exc, exc_final, copy_options) - - # If not list, just append + FlatCAMExcellon.merge(exc, exc_final) + # If not list, merge excellons else: - if copy_options is True: - exc_final.options["plot"]=exc.options["plot"] - exc_final.options["solid"]=exc.options["solid"] - exc_final.options["drillz"]=exc.options["drillz"] - exc_final.options["travelz"]=exc.options["travelz"] - exc_final.options["feedrate"]=exc.options["feedrate"] - exc_final.options["tooldia"]=exc.options["tooldia"] - exc_final.options["toolchange"]=exc.options["toolchange"] - exc_final.options["toolchangez"]=exc.options["toolchangez"] - exc_final.options["spindlespeed"]=exc.options["spindlespeed"] + # TODO: I realize forms does not save values into options , when object is deselected + # leave this here for future use + # this reinitialize options based on forms, all steps may not be necessary + # exc.app.collection.set_active(exc.options['name']) + # exc.to_form() + # exc.read_form() + for option in exc.options: + if option is not 'name': + try: + exc_final.options[option] = exc.options[option] + except: + exc.app.log.warning("Failed to copy option.",option) + #deep copy of all drills,to avoid any references for drill in exc.drills: point = Point(drill['point'].x,drill['point'].y) exc_final.drills.append({"point": point, "tool": drill['tool']}) @@ -679,7 +701,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): max_numeric_tool=numeric_tool toolsrework[exc.tools[toolname]['C']]=toolname - #final as last becouse names from final tools will be used + #exc_final as last because names from final tools will be used for toolname in exc_final.tools.iterkeys(): numeric_tool=int(toolname) if numeric_tool>max_numeric_tool: @@ -692,9 +714,10 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): exc_final.tools[str(max_numeric_tool+1)]={"C": toolvalues} else: exc_final.tools[toolsrework[toolvalues]]={"C": toolvalues} + #this value was not co + exc_final.zeros=exc.zeros exc_final.create_geometry() - def build_ui(self): FlatCAMObj.build_ui(self) diff --git a/ObjectCollection.py b/ObjectCollection.py index 005f2bcc..727358df 100644 --- a/ObjectCollection.py +++ b/ObjectCollection.py @@ -244,6 +244,27 @@ class ObjectCollection(QtCore.QAbstractListModel): iobj = self.createIndex(self.get_names().index(name), 0) # Column 0 self.view.selectionModel().select(iobj, QtGui.QItemSelectionModel.Select) + def set_inactive(self, name): + """ + Unselect object by name from the project list. This triggers the + list_selection_changed event and call on_list_selection_changed. + + :param name: Name of the FlatCAM Object + :return: None + """ + iobj = self.createIndex(self.get_names().index(name), 0) # Column 0 + self.view.selectionModel().select(iobj, QtGui.QItemSelectionModel.Deselect) + + def set_all_inactive(self): + """ + Unselect all objects from the project list. This triggers the + list_selection_changed event and call on_list_selection_changed. + + :return: None + """ + for name in self.get_names(): + self.set_inactive(name) + def on_list_selection_change(self, current, previous): FlatCAMApp.App.log.debug("on_list_selection_change()") FlatCAMApp.App.log.debug("Current: %s, Previous %s" % (str(current), str(previous))) diff --git a/camlib.py b/camlib.py index b919f398..f576ed98 100644 --- a/camlib.py +++ b/camlib.py @@ -136,17 +136,17 @@ class Geometry(object): log.error("Failed to run union on polygons.") raise - def del_polygon(self, points): + def subtract_polygon(self, points): """ - Delete a polygon from the object + Subtract polygon from the given object. This only operates on the paths in the original geometry, i.e. it converts polygons into paths. :param points: The vertices of the polygon. - :return: None + :return: none """ if self.solid_geometry is None: self.solid_geometry = [] - + #pathonly should be allways True, otherwise polygons are not subtracted flat_geometry = self.flatten(pathonly=True) log.debug("%d paths" % len(flat_geometry)) polygon=Polygon(points) @@ -157,7 +157,7 @@ class Geometry(object): diffs.append(target.difference(toolgeo)) else: log.warning("Not implemented.") - return cascaded_union(diffs) + self.solid_geometry=cascaded_union(diffs) def bounds(self): """