- updated the offset and scale Tcl commands to work on a selection of objects

This commit is contained in:
Marius Stanciu
2022-02-01 05:40:05 +02:00
committed by Marius
parent afdbce81b6
commit 2ccd807325
5 changed files with 119 additions and 70 deletions

View File

@@ -13,6 +13,7 @@ CHANGELOG for FlatCAM beta
- fixed the multi-color feature in the Gerber object UI - fixed the multi-color feature in the Gerber object UI
- fixed the marking of apertures in Gerber object UI and in Extract plugin - fixed the marking of apertures in Gerber object UI and in Extract plugin
- minor changes in `cncjob` Tcl command - minor changes in `cncjob` Tcl command
- updated the `offset` and `scale` Tcl commands to work on a selection of objects
31.02.2022 31.02.2022

View File

@@ -57,6 +57,10 @@ class TclCommandJoinExcellon(TclCommand):
outname = args['outname'] if 'outname' in args else "joined_exc" outname = args['outname'] if 'outname' in args else "joined_exc"
obj_names = unnamed_args obj_names = unnamed_args
if not obj_names:
self.app.log.error("Missing objects to be joined. Exiting.")
return "fail"
objs = [] objs = []
for obj_n in obj_names: for obj_n in obj_names:
obj = self.app.collection.get_by_name(str(obj_n)) obj = self.app.collection.get_by_name(str(obj_n))

View File

@@ -53,6 +53,9 @@ class TclCommandJoinGeometry(TclCommand):
outname = args['outname'] if 'outname' in args else "joined_geo" outname = args['outname'] if 'outname' in args else "joined_geo"
obj_names = unnamed_args obj_names = unnamed_args
if not obj_names:
self.app.log.error("Missing objects to be joined. Exiting.")
return "fail"
objs = [] objs = []
for obj_n in obj_names: for obj_n in obj_names:

View File

@@ -14,13 +14,11 @@ class TclCommandOffset(TclCommand):
# List of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon) # List of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon)
aliases = ['offset'] aliases = ['offset']
description = '%s %s' % ("--", "Will offset the geometry of a named object. Does not create a new object.") description = '%s %s' % ("--", "Will offset the geometry of named objects. Does not create a new object.")
# Dictionary of types from Tcl command, needs to be ordered # Dictionary of types from Tcl command, needs to be ordered
arg_names = collections.OrderedDict([ arg_names = collections.OrderedDict([
('name', str),
('x', float),
('y', float)
]) ])
# Dictionary of types from Tcl command, needs to be ordered , this is for options like -optionname value # Dictionary of types from Tcl command, needs to be ordered , this is for options like -optionname value
@@ -30,17 +28,19 @@ class TclCommandOffset(TclCommand):
]) ])
# array of mandatory options for current Tcl command: required = {'name','outname'} # array of mandatory options for current Tcl command: required = {'name','outname'}
required = ['name'] required = []
# structured help for current command, args needs to be ordered # structured help for current command, args needs to be ordered
help = { help = {
'main': "Changes the position of the object on X and/or Y axis.", 'main': "Changes the position of the named object(s) on X and/or Y axis.\n"
"The names of the objects to be offset will be entered after the command,\n"
"separated by spaces. See the example below.\n"
"WARNING: if the name of an object has spaces, enclose the name with quotes.",
'args': collections.OrderedDict([ 'args': collections.OrderedDict([
('name', 'Name of the object to offset. Required.'),
('x', 'Offset distance in the X axis. If it is not used it will be assumed to be 0.0'), ('x', 'Offset distance in the X axis. If it is not used it will be assumed to be 0.0'),
('y', 'Offset distance in the Y axis. If it is not used it will be assumed to be 0.0') ('y', 'Offset distance in the Y axis. If it is not used it will be assumed to be 0.0')
]), ]),
'examples': ['offset my_geometry -x 1.2 -y -0.3', 'offset my_geometry -x 1.0'] 'examples': ['offset my_object_1 "my object_1" -x 1.2 -y -0.3', 'offset my_object -x 1.0']
} }
def execute(self, args, unnamed_args): def execute(self, args, unnamed_args):
@@ -51,13 +51,24 @@ class TclCommandOffset(TclCommand):
:return: :return:
""" """
name = args['name']
off_x = args['x'] if 'x' in args else 0.0 off_x = args['x'] if 'x' in args else 0.0
off_y = args['y'] if 'y' in args else 0.0 off_y = args['y'] if 'y' in args else 0.0
x, y = float(off_x), float(off_y) x, y = float(off_x), float(off_y)
if (x, y) == (0.0, 0.0): if (x, y) == (0.0, 0.0):
self.app.log.warning("Offset by 0.0. Nothing to be done.")
return return
self.app.collection.get_by_name(name).offset((x, y)) obj_names = unnamed_args
if not obj_names:
self.app.log.error("Missing objects to be offset. Exiting.")
return "fail"
for name in obj_names:
obj = self.app.collection.get_by_name(str(name))
if obj is None or obj == '':
self.app.log.error("Object not found: %s" % name)
return "fail"
obj.offset((x, y))

View File

@@ -25,16 +25,16 @@ class TclCommandScale(TclCommand):
# List of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon) # List of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon)
aliases = ['scale'] aliases = ['scale']
description = '%s %s' % ("--", "Will scale the geometry of a named object. Does not create a new object.") description = '%s %s' % ("--", "Will scale the geometry of named objects. Does not create a new object.")
# Dictionary of types from Tcl command, needs to be ordered # Dictionary of types from Tcl command, needs to be ordered
arg_names = collections.OrderedDict([ arg_names = collections.OrderedDict([
('name', str),
('factor', float)
]) ])
# Dictionary of types from Tcl command, needs to be ordered , this is for options like -optionname value # Dictionary of types from Tcl command, needs to be ordered , this is for options like -optionname value
option_types = collections.OrderedDict([ option_types = collections.OrderedDict([
('factor', float),
('x', float), ('x', float),
('y', float), ('y', float),
('origin', str) ('origin', str)
@@ -45,24 +45,26 @@ class TclCommandScale(TclCommand):
# structured help for current command, args needs to be ordered # structured help for current command, args needs to be ordered
help = { help = {
'main': "Resizes the object by a factor on X axis and a factor on Y axis, having as scale origin the point ", 'main': "Resizes objects by a factor on X axis and a factor on Y axis, having a specified scale origin\n"
"The names of the objects to be scaled will be entered after the command,\n"
"separated by spaces. See the example below.\n"
"WARNING: if the name of an object has spaces, enclose the name with quotes.",
'args': collections.OrderedDict([ 'args': collections.OrderedDict([
('name', 'Name of the object (Gerber, Geometry or Excellon) to be resized. Required.'),
('factor', 'Fraction by which to scale on both axis.'), ('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'), ('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'), ('y', 'Fraction by which to scale on Y axis. If "factor" is used then this parameter is ignored'),
('origin', 'Reference used for scale.\n' ('origin', 'Reference used for scale.\n'
'The reference point can be:\n' 'The reference point can be:\n'
'- "origin" which means point (0, 0)\n' '- "origin" which means point (0, 0)\n'
'- "min_bounds" which means the lower left point of the bounding box\n' '- "min_bounds" which means the lower left point of the bounding box made for all objects\n'
'- "center" which means the center point of the bounding box of the object.\n' '- "center" which means the center point of the bounding box made for all objects.\n'
'- a tuple in format (x,y) with the X and Y coordinates separated by a comma. NO SPACES ALLOWED') '- a point in format (x,y) with the X and Y coordinates separated by a comma. NO SPACES ALLOWED')
]), ]),
'examples': ['scale my_geometry 4.2', 'examples': ['scale my_obj_1 "my obj_2" -factor 4.2',
'scale my_geo -x 3.1 -y 2.8', 'scale my_obj -x 3.1 -y 2.8',
'scale my_geo 1.2 -origin min_bounds', 'scale my_obj -factor 1.2 -origin min_bounds',
'scale my_geometry -x 2 -origin 3.0,2.1'] 'scale my_object -x 2 -origin 3.0,2.1']
} }
def execute(self, args, unnamed_args): def execute(self, args, unnamed_args):
@@ -72,17 +74,50 @@ class TclCommandScale(TclCommand):
:param unnamed_args: :param unnamed_args:
:return: :return:
""" """
if 'x' not in args and 'y' not in args and 'factor' not in args:
self.app.log.warning('%s' % "Expected -x <value> -y <value> or -factor <value>")
self.raise_tcl_error('%s' % "Expected -x <value> -y <value> or -factor <value>")
return 'fail'
name = args['name'] obj_names = unnamed_args
if not obj_names:
self.app.log.error("Missing objects to be offset. Exiting.")
return "fail"
# calculate the bounds
minx_lst = []
miny_lst = []
maxx_lst = []
maxy_lst = []
for name in obj_names:
obj = self.app.collection.get_by_name(str(name))
if obj is None or obj == '':
self.app.log.error("Object not found: %s" % name)
return "fail"
a, b, c, d = obj.bounds()
minx_lst.append(a)
miny_lst.append(b)
maxx_lst.append(c)
maxy_lst.append(d)
xmin = min(minx_lst)
ymin = min(miny_lst)
xmax = max(maxx_lst)
ymax = max(maxy_lst)
for name in obj_names:
try: try:
obj_to_scale = self.app.collection.get_by_name(name) obj_to_scale = self.app.collection.get_by_name(name)
except Exception as e: except Exception as e:
self.app.log.error("TclCommandCopperScale.execute() --> %s" % str(e)) self.app.log.error("TclCommandCopperScale.execute() --> %s" % str(e))
self.app.log.error("Could not retrieve object: %s" % name)
self.raise_tcl_error("%s: %s" % (_("Could not retrieve object"), name)) self.raise_tcl_error("%s: %s" % (_("Could not retrieve object"), name))
return "Could not retrieve object: %s" % name return "fail"
if obj_to_scale is None or obj_to_scale == '':
self.app.log.error("Object not found: %s" % name)
return "fail"
if 'origin' not in args: if 'origin' not in args:
xmin, ymin, xmax, ymax = obj_to_scale.bounds()
c_x = xmin + (xmax - xmin) / 2 c_x = xmin + (xmax - xmin) / 2
c_y = ymin + (ymax - ymin) / 2 c_y = ymin + (ymax - ymin) / 2
point = (c_x, c_y) point = (c_x, c_y)
@@ -90,18 +125,17 @@ class TclCommandScale(TclCommand):
if args['origin'] == 'origin': if args['origin'] == 'origin':
point = (0, 0) point = (0, 0)
elif args['origin'] == 'min_bounds': elif args['origin'] == 'min_bounds':
xmin, ymin, xmax, ymax = obj_to_scale.bounds()
point = (xmin, ymin) point = (xmin, ymin)
elif args['origin'] == 'center': elif args['origin'] == 'center':
xmin, ymin, xmax, ymax = obj_to_scale.bounds()
c_x = xmin + (xmax - xmin) / 2 c_x = xmin + (xmax - xmin) / 2
c_y = ymin + (ymax - ymin) / 2 c_y = ymin + (ymax - ymin) / 2
point = (c_x, c_y) point = (c_x, c_y)
else: else:
try: try:
point = eval(args['origin']) point = eval(str(args['origin']))
if not isinstance(point, tuple): if not isinstance(point, tuple):
raise Exception self.app.log.error("The -origin value is not a tuple in format e.g 3.32,4.5")
return "fail"
except Exception as e: except Exception as e:
self.raise_tcl_error('%s\n%s' % (_("Expected -origin <origin> or " self.raise_tcl_error('%s\n%s' % (_("Expected -origin <origin> or "
"-origin <min_bounds> or " "-origin <min_bounds> or "
@@ -112,11 +146,7 @@ class TclCommandScale(TclCommand):
if 'factor' in args: if 'factor' in args:
factor = float(args['factor']) factor = float(args['factor'])
obj_to_scale.scale(factor, point=point) obj_to_scale.scale(factor, point=point)
return continue
if 'x' not in args and 'y' not in args:
self.raise_tcl_error('%s' % _("Expected -x <value> -y <value>."))
return 'fail'
if 'x' in args and 'y' not in args: if 'x' in args and 'y' not in args:
f_x = float(args['x']) f_x = float(args['x'])