diff --git a/FlatCAMObj.py b/FlatCAMObj.py index 2a9a46ec..c5bf5d15 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -6016,7 +6016,12 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob): try: if self.multitool is False: # single tool usage - self.plot2(tooldia=float(self.options["tooldia"]), obj=self, visible=visible, kind=kind) + try: + dia_plot = float(self.options["tooldia"]) + except ValueError: + # we may have a tuple with only one element and a comma + dia_plot = [float(el) for el in self.options["tooldia"].split(',') if el != ''][0] + self.plot2(dia_plot, obj=self, visible=visible, kind=kind) else: # multiple tools usage for tooluid_key in self.cnc_tools: diff --git a/README.md b/README.md index 9ff67d64..c3431a12 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,11 @@ CAD program, and create G-Code for Isolation routing. - fixed issue #298. The changes in postprocessors done in Preferences dis not update the object UI layout as it was supposed to. The selection of Marlin postproc. did not unhidden the Feedrate Rapids entry. - fixed minor issues - fixed Tcl Command AddPolygon, AddPolyline +- fixed Tcl Command CncJob +- fixed crash due of Properties Tool trying to have a convex hull area on FlatCAMCNCJob objects which is not possible due of their nature +- modified Tcl Command SubtractRectangle +- fixed and modernized the Tcl Command Scale to be able to scale on X axis or on Y axis or on both and having as scale reference either the (0, 0) point or the minimum point of the bounding box or the center of the bounding box. +- fixed and modernized the Tcl Command Skew 24.08.2019 diff --git a/camlib.py b/camlib.py index 5faf3d5b..fecba319 100644 --- a/camlib.py +++ b/camlib.py @@ -6001,7 +6001,10 @@ class CNCjob(Geometry): flat_geometry = self.flatten(temp_solid_geometry, pathonly=True) log.debug("%d paths" % len(flat_geometry)) - self.tooldia = float(tooldia) if tooldia else None + try: + self.tooldia = float(tooldia) if tooldia else None + except ValueError: + self.tooldia = [float(el) for el in tooldia.split(',') if el != ''] if tooldia else None self.z_cut = float(z_cut) if z_cut else None self.z_move = float(z_move) if z_move else None @@ -6669,6 +6672,10 @@ class CNCjob(Geometry): if tooldia is None: tooldia = self.tooldia + # this should be unlikely unless when upstream the tooldia is a tuple made by one dia and a comma like (2.4,) + if isinstance(tooldia, list): + tooldia = tooldia[0] if tooldia[0] is not None else self.tooldia + if tooldia == 0: for geo in gcode_parsed: if kind == 'all': @@ -7042,7 +7049,10 @@ class CNCjob(Geometry): bounds_coords = bounds_rec(self.solid_geometry) else: - + minx = Inf + miny = Inf + maxx = -Inf + maxy = -Inf for k, v in self.cnc_tools.items(): minx = Inf miny = Inf diff --git a/flatcamTools/ToolProperties.py b/flatcamTools/ToolProperties.py index d1517cc6..f733b953 100644 --- a/flatcamTools/ToolProperties.py +++ b/flatcamTools/ToolProperties.py @@ -169,25 +169,26 @@ class Properties(FlatCAMTool): area = length * width self.addChild(dims, ['%s:' % _('Box Area'), '%.4f %s' % (area, 'in2')], True) - # calculate and add convex hull area - geo = obj.solid_geometry - if isinstance(geo, MultiPolygon): - env_obj = geo.convex_hull - elif (isinstance(geo, MultiPolygon) and len(geo) == 1) or \ - (isinstance(geo, list) and len(geo) == 1) and isinstance(geo[0], Polygon): - env_obj = cascaded_union(obj.solid_geometry) - env_obj = env_obj.convex_hull - else: - env_obj = cascaded_union(obj.solid_geometry) - env_obj = env_obj.convex_hull + if not isinstance(obj, FlatCAMCNCjob): + # calculate and add convex hull area + geo = obj.solid_geometry + if isinstance(geo, MultiPolygon): + env_obj = geo.convex_hull + elif (isinstance(geo, MultiPolygon) and len(geo) == 1) or \ + (isinstance(geo, list) and len(geo) == 1) and isinstance(geo[0], Polygon): + env_obj = cascaded_union(obj.solid_geometry) + env_obj = env_obj.convex_hull + else: + env_obj = cascaded_union(obj.solid_geometry) + env_obj = env_obj.convex_hull - area_chull = env_obj.area - if self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().lower() == 'mm': - area_chull = area_chull / 100 - self.addChild(dims, ['%s:' % _('Convex_Hull Area'), '%.4f %s' % (area_chull, 'cm2')], True) - else: - area_chull = area_chull - self.addChild(dims, ['%s:' % _('Convex_Hull Area'), '%.4f %s' % (area_chull, 'in2')], True) + area_chull = env_obj.area + if self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().lower() == 'mm': + area_chull = area_chull / 100 + self.addChild(dims, ['%s:' % _('Convex_Hull Area'), '%.4f %s' % (area_chull, 'cm2')], True) + else: + area_chull = area_chull + self.addChild(dims, ['%s:' % _('Convex_Hull Area'), '%.4f %s' % (area_chull, 'in2')], True) self.addChild(units, ['FlatCAM units:', diff --git a/tclCommands/TclCommandClearShell.py b/tclCommands/TclCommandClearShell.py index f18e4e54..96aef988 100644 --- a/tclCommands/TclCommandClearShell.py +++ b/tclCommands/TclCommandClearShell.py @@ -4,7 +4,7 @@ from ObjectCollection import * class TclCommandClearShell(TclCommand): """ - Tcl shell command to creates a circle in the given Geometry object. + Tcl shell command to clear the text in the Tcl Shell browser. example: diff --git a/tclCommands/TclCommandCncjob.py b/tclCommands/TclCommandCncjob.py index 48727c94..c76fb3c9 100644 --- a/tclCommands/TclCommandCncjob.py +++ b/tclCommands/TclCommandCncjob.py @@ -24,15 +24,18 @@ class TclCommandCncjob(TclCommandSignaled): # dictionary of types from Tcl command, needs to be ordered , this is for options like -optionname value option_types = collections.OrderedDict([ + ('tooldia', float), ('z_cut', float), ('z_move', float), ('feedrate', float), ('feedrate_rapid', float), - ('tooldia', float), ('spindlespeed', int), ('multidepth', bool), ('extracut', bool), ('depthperpass', float), + ('toolchange', int), + ('toolchangez', float), + ('toolchangexy', tuple), ('endz', float), ('ppname_g', str), ('outname', str) @@ -62,7 +65,7 @@ class TclCommandCncjob(TclCommandSignaled): ('outname', 'Name of the resulting Geometry object.'), ('ppname_g', 'Name of the Geometry postprocessor. No quotes, case sensitive') ]), - 'examples': [] + 'examples': ['cncjob geo_name -tooldia 0.5 -z_cut -1.7 -z_move 2 -feedrate 120 -ppname_g default'] } def execute(self, args, unnamed_args): diff --git a/tclCommands/TclCommandScale.py b/tclCommands/TclCommandScale.py index 8a8f4068..1fbe670b 100644 --- a/tclCommands/TclCommandScale.py +++ b/tclCommands/TclCommandScale.py @@ -21,20 +21,30 @@ class TclCommandScale(TclCommand): # Dictionary of types from Tcl command, needs to be ordered , this is for options like -optionname value option_types = collections.OrderedDict([ - + ('x', float), + ('y', float), + ('origin', str) ]) # array of mandatory options for current Tcl command: required = {'name','outname'} - required = ['name', 'factor'] + required = ['name'] # structured help for current command, args needs to be ordered help = { - 'main': "Resizes the object by a factor.", + 'main': "Resizes the object by a factor on X axis and a factor on Y axis, having as scale origin the point ", 'args': collections.OrderedDict([ ('name', 'Name of the object to resize.'), - ('factor', 'Fraction by which to scale.') + ('factor', 'Fraction by which to scale on both axis. '), + ('x', 'Fraction by which to scale on X axis. If "factor" is used then this parameter is ignored'), + ('y', 'Fraction by which to scale on Y axis. If "factor" is used then this parameter is ignored'), + ('origin', 'Reference used for scale. It can be: "origin" which means point (0, 0) or "min_bounds" which ' + 'means the lower left point of the bounding box or it can be "center" which means the center ' + 'of the bounding box.') + ]), - 'examples': ['scale my_geometry 4.2'] + 'examples': ['scale my_geometry 4.2', + 'scale my_geo -x 3.1 -y 2.8', + 'scale my_geo 1.2 -origin min_bounds'] } def execute(self, args, unnamed_args): @@ -46,6 +56,49 @@ class TclCommandScale(TclCommand): """ name = args['name'] - factor = args['factor'] + try: + obj_to_scale = self.app.collection.get_by_name(name) + except Exception as e: + log.debug("TclCommandCopperClear.execute() --> %s" % str(e)) + self.raise_tcl_error("%s: %s" % (_("Could not retrieve box object"), name)) + return "Could not retrieve object: %s" % name - self.app.collection.get_by_name(name).scale(factor) + if 'origin' not in args: + xmin, ymin, xmax, ymax = obj_to_scale.bounds() + c_x = xmin + (xmax - xmin) / 2 + c_y = ymin + (ymax - ymin) / 2 + point = (c_x, c_y) + else: + if args['origin'] == 'origin': + point = (0, 0) + elif args['origin'] == 'min_bounds': + xmin, ymin, xmax, ymax = obj_to_scale.bounds() + point = (xmin, ymin) + elif args['origin'] == 'center': + xmin, ymin, xmax, ymax = obj_to_scale.bounds() + c_x = xmin + (xmax - xmin) / 2 + c_y = ymin + (ymax - ymin) / 2 + point = (c_x, c_y) + else: + self.raise_tcl_error('%s' % _("Expected -origin or -origin or -origin
.")) + return 'fail' + + if 'factor' in args: + factor = float(args['factor']) + obj_to_scale.scale(factor, point=point) + return + + if 'x' not in args and 'y' not in args: + self.raise_tcl_error('%s' % _("Expected -x -y .")) + return 'fail' + + if 'x' in args and 'y' not in args: + f_x = float(args['x']) + obj_to_scale.scale(f_x, 0, point=point) + elif 'x' not in args and 'y' in args: + f_y = float(args['y']) + obj_to_scale.scale(0, f_y, point=point) + elif 'x' in args and 'y' in args: + f_x = float(args['x']) + f_y = float(args['y']) + obj_to_scale.scale(f_x, f_y, point=point) diff --git a/tclCommands/TclCommandSkew.py b/tclCommands/TclCommandSkew.py index fccf4398..ac44e9c6 100644 --- a/tclCommands/TclCommandSkew.py +++ b/tclCommands/TclCommandSkew.py @@ -30,7 +30,8 @@ class TclCommandSkew(TclCommand): # structured help for current command, args needs to be ordered help = { - 'main': "Shear/Skew an object by angles along x and y dimensions.", + 'main': "Shear/Skew an object by angles along x and y dimensions. The reference point is the left corner of " + "the bounding box of the object.", 'args': collections.OrderedDict([ ('name', 'Name of the object to skew.'), ('angle_x', 'Angle in degrees by which to skew on the X axis.'), @@ -48,7 +49,9 @@ class TclCommandSkew(TclCommand): """ name = args['name'] - angle_x = args['angle_x'] - angle_y = args['angle_y'] + angle_x = float(args['angle_x']) + angle_y = float(args['angle_y']) - self.app.collection.get_by_name(name).skew(angle_x, angle_y) + obj_to_skew = self.app.collection.get_by_name(name) + xmin, ymin, xmax, ymax = obj_to_skew.bounds() + obj_to_skew.skew(angle_x, angle_y, point=(xmin, ymin)) diff --git a/tclCommands/TclCommandSubtractRectangle.py b/tclCommands/TclCommandSubtractRectangle.py index 1ec2838c..82aee657 100644 --- a/tclCommands/TclCommandSubtractRectangle.py +++ b/tclCommands/TclCommandSubtractRectangle.py @@ -4,7 +4,7 @@ from tclCommands.TclCommand import TclCommandSignaled class TclCommandSubtractRectangle(TclCommandSignaled): """ - Tcl shell command to subtract a rectange from the given Geometry object. + Tcl shell command to subtract a rectangle from the given Geometry object. """ # array of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon) @@ -13,11 +13,7 @@ class TclCommandSubtractRectangle(TclCommandSignaled): # Dictionary of types from Tcl command, needs to be ordered. # For positional arguments arg_names = collections.OrderedDict([ - ('name', str), - ('x0', float), - ('y0', float), - ('x1', float), - ('y1', float) + ('name', str) ]) # Dictionary of types from Tcl command, needs to be ordered. @@ -27,7 +23,7 @@ class TclCommandSubtractRectangle(TclCommandSignaled): ]) # array of mandatory options for current Tcl command: required = {'name','outname'} - required = ['name', 'x0', 'y0', 'x1', 'y1'] + required = ['name'] # structured help for current command, args needs to be ordered help = { @@ -37,7 +33,7 @@ class TclCommandSubtractRectangle(TclCommandSignaled): ('x0 y0', 'Bottom left corner coordinates.'), ('x1 y1', 'Top right corner coordinates.') ]), - 'examples': [] + 'examples': ['subtract_rectangle geo_obj 8 8 15 15'] } def execute(self, args, unnamed_args): @@ -49,12 +45,19 @@ class TclCommandSubtractRectangle(TclCommandSignaled): without -somename and we do not have them in known arg_names :return: None or exception """ - + if 'name' not in args: + self.raise_tcl_error("%s:" % _("No Geometry name in args. Provide a name and try again.")) + return 'fail' obj_name = args['name'] - x0 = args['x0'] - y0 = args['y0'] - x1 = args['x1'] - y1 = args['y1'] + + if len(unnamed_args) != 4: + self.raise_tcl_error("Incomplete coordinates. There are 4 required: x0 y0 x1 y1.") + return 'fail' + + x0 = float(unnamed_args[0]) + y0 = float(unnamed_args[1]) + x1 = float(unnamed_args[2]) + y1 = float(unnamed_args[3]) try: obj = self.app.collection.get_by_name(str(obj_name)) diff --git a/tclCommands/TclCommandVersion.py b/tclCommands/TclCommandVersion.py index 86b539fd..693743ad 100644 --- a/tclCommands/TclCommandVersion.py +++ b/tclCommands/TclCommandVersion.py @@ -32,7 +32,7 @@ class TclCommandVersion(TclCommand): 'args': collections.OrderedDict([ ]), - 'examples': [] + 'examples': ['version'] } def execute(self, args, unnamed_args): diff --git a/tclCommands/TclCommandWriteGCode.py b/tclCommands/TclCommandWriteGCode.py index 1068def1..d1b1c9d4 100644 --- a/tclCommands/TclCommandWriteGCode.py +++ b/tclCommands/TclCommandWriteGCode.py @@ -37,7 +37,7 @@ class TclCommandWriteGCode(TclCommandSignaled): ('preamble', 'Text to append at the beginning.'), ('postamble', 'Text to append at the end.') ]), - 'examples': [] + 'examples': ["write_gcode name c:\\\\gcode_repo"] } def execute(self, args, unnamed_args):