- fixed the Tcl Commands: new_geometry, new_gerber and new_excellon to create correct objects. New Geometry object created with the new_geometry Tcl command accept now the usage of add_circle/polygon/polyline/rectangle Tcl commands

- updated the autocompleter list for the Tcl commands with missing Tcl commands names
- added three new Tcl Commands: add_aperture (adds an aperture to a Gerber object), add_drill and add_slot (add a drill/slot to an Excellon object)
This commit is contained in:
Marius Stanciu
2021-01-03 19:58:46 +02:00
committed by Marius
parent bc8b2e3b3a
commit f9b3cb0794
13 changed files with 438 additions and 52 deletions

View File

@@ -7,6 +7,12 @@ CHANGELOG for FlatCAM beta
================================================= =================================================
3.01.2021
- fixed the Tcl Commands: new_geometry, new_gerber and new_excellon to create correct objects. New Geometry object created with the new_geometry Tcl command accept now the usage of add_circle/polygon/polyline/rectangle Tcl commands
- updated the autocompleter list for the Tcl commands with missing Tcl commands names
- added three new Tcl Commands: add_aperture (adds an aperture to a Gerber object), add_drill and add_slot (add a drill/slot to an Excellon object)
2.01.2021 2.01.2021
- removed the 'machinist setting' and allow all over the app the usages of both negative and positive values (where it is the case) - removed the 'machinist setting' and allow all over the app the usages of both negative and positive values (where it is the case)

View File

@@ -395,14 +395,16 @@ class AppObject(QtCore.QObject):
""" """
self.app.on_zoom_fit() self.app.on_zoom_fit()
def new_excellon_object(self): def new_excellon_object(self, new_name=None):
""" """
Creates a new, blank Excellon object. Creates a new, blank Excellon object.
:return: None :param new_name: new name for the new Geometry object
:type new_name: str
:return: None
""" """
outname = 'new_exc' outname = 'new_exc' if new_name is None else new_name
def obj_init(new_obj, app_obj): def obj_init(new_obj, app_obj):
new_obj.tools = {} new_obj.tools = {}
@@ -411,13 +413,16 @@ class AppObject(QtCore.QObject):
self.new_object('excellon', outname, obj_init, plot=False) self.new_object('excellon', outname, obj_init, plot=False)
def new_geometry_object(self): def new_geometry_object(self, new_name=None):
""" """
Creates a new, blank and single-tool Geometry object. Creates a new, blank and single-tool Geometry object.
:return: None :param new_name: new name for the new Geometry object
:type new_name: str
:return: None
""" """
outname = 'new_geo'
outname = 'new_geo' if new_name is None else new_name
def initialize(new_obj, app): def initialize(new_obj, app):
new_obj.multitool = True new_obj.multitool = True
@@ -450,13 +455,17 @@ class AppObject(QtCore.QObject):
self.new_object('geometry', outname, initialize, plot=False) self.new_object('geometry', outname, initialize, plot=False)
def new_gerber_object(self): def new_gerber_object(self, new_name=None):
""" """
Creates a new, blank Gerber object. Creates a new, blank Gerber object.
:return: None :param new_name: new name for the new Geometry object
:type new_name: str
:return: None
""" """
outname = 'new_geo' if new_name is None else new_name
def initialize(new_obj, app): def initialize(new_obj, app):
new_obj.multitool = False new_obj.multitool = False
new_obj.source_file = '' new_obj.source_file = ''
@@ -474,15 +483,19 @@ class AppObject(QtCore.QObject):
except KeyError: except KeyError:
pass pass
self.new_object('gerber', 'new_grb', initialize, plot=False) self.new_object('gerber', outname, initialize, plot=False)
def new_script_object(self): def new_script_object(self, new_name=None):
""" """
Creates a new, blank TCL Script object. Creates a new, blank TCL Script object.
:return: None :param new_name: new name for the new Geometry object
:type new_name: str
:return: None
""" """
outname = 'new_script' if new_name is None else new_name
# commands_list = "# AddCircle, AddPolygon, AddPolyline, AddRectangle, AlignDrill, " \ # commands_list = "# AddCircle, AddPolygon, AddPolyline, AddRectangle, AlignDrill, " \
# "AlignDrillGrid, Bbox, Bounds, ClearShell, CopperClear,\n" \ # "AlignDrillGrid, Bbox, Bounds, ClearShell, CopperClear,\n" \
# "# Cncjob, Cutout, Delete, Drillcncjob, ExportDXF, ExportExcellon, ExportGcode,\n" \ # "# Cncjob, Cutout, Delete, Drillcncjob, ExportDXF, ExportExcellon, ExportGcode,\n" \
@@ -505,17 +518,20 @@ class AppObject(QtCore.QObject):
def initialize(new_obj, app): def initialize(new_obj, app):
new_obj.source_file = deepcopy(new_source_file) new_obj.source_file = deepcopy(new_source_file)
outname = 'new_script'
self.new_object('script', outname, initialize, plot=False) self.new_object('script', outname, initialize, plot=False)
def new_document_object(self): def new_document_object(self, new_name=None):
""" """
Creates a new, blank Document object. Creates a new, blank Document object.
:return: None :param new_name: new name for the new Geometry object
:type new_name: str
:return: None
""" """
outname = 'new_document' if new_name is None else new_name
def initialize(new_obj, app): def initialize(new_obj, app):
new_obj.source_file = "" new_obj.source_file = ""
self.new_object('document', 'new_document', initialize, plot=False) self.new_object('document', outname, initialize, plot=False)

View File

@@ -552,7 +552,7 @@ class FCShell(TermWidget):
""" """
self.display_tcl_error(text) self.display_tcl_error(text)
raise self.TclErrorException(text) # raise self.TclErrorException(text)
class TclErrorException(Exception): class TclErrorException(Exception):
""" """

View File

@@ -604,7 +604,8 @@ class App(QtCore.QObject):
# ####################################### Auto-complete KEYWORDS ############################################ # ####################################### Auto-complete KEYWORDS ############################################
# ######################## Setup after the Defaults class was instantiated ################################## # ######################## Setup after the Defaults class was instantiated ##################################
# ########################################################################################################### # ###########################################################################################################
self.tcl_commands_list = ['add_circle', 'add_poly', 'add_polygon', 'add_polyline', 'add_rectangle', self.tcl_commands_list = ['add_aperture', 'add_circle', 'add_drill', 'add_poly', 'add_polygon', 'add_polyline',
'add_rectangle', 'add_rect', 'add_slot',
'aligndrill', 'aligndrillgrid', 'bbox', 'clear', 'cncjob', 'cutout', 'aligndrill', 'aligndrillgrid', 'bbox', 'clear', 'cncjob', 'cutout',
'del', 'drillcncjob', 'export_dxf', 'edxf', 'export_excellon', 'del', 'drillcncjob', 'export_dxf', 'edxf', 'export_excellon',
'export_exc', 'export_exc',
@@ -613,7 +614,8 @@ class App(QtCore.QObject):
'interiors', 'isolate', 'join_excellon', 'interiors', 'isolate', 'join_excellon',
'join_geometry', 'list_sys', 'milld', 'mills', 'milldrills', 'millslots', 'join_geometry', 'list_sys', 'milld', 'mills', 'milldrills', 'millslots',
'mirror', 'ncc', 'mirror', 'ncc',
'ncr', 'new', 'new_geometry', 'non_copper_regions', 'offset', 'ncr', 'new', 'new_geometry', 'new_gerber', 'new_excellon', 'non_copper_regions',
'offset',
'open_dxf', 'open_excellon', 'open_gcode', 'open_gerber', 'open_project', 'open_svg', 'open_dxf', 'open_excellon', 'open_gcode', 'open_gerber', 'open_project', 'open_svg',
'options', 'origin', 'options', 'origin',
'paint', 'panelize', 'plot_all', 'plot_objects', 'plot_status', 'quit_flatcam', 'paint', 'panelize', 'plot_all', 'plot_objects', 'plot_status', 'quit_flatcam',

View File

@@ -264,17 +264,17 @@ class TclCommand(object):
raise unknown_exception raise unknown_exception
def raise_tcl_error(self, text): # def raise_tcl_error(self, text):
""" # """
this method pass exception from python into TCL as error, so we get stacktrace and reason # this method pass exception from python into TCL as error, so we get stacktrace and reason
:param text: text of error # :param text: text of error
:return: raise exception # :return: raise exception
""" # """
#
# because of signaling we cannot call error to TCL from here but when task # # because of signaling we cannot call error to TCL from here but when task
# is finished also non-signaled are handled here to better exception # # is finished also non-signaled are handled here to better exception
# handling and displayed after command is finished # # handling and displayed after command is finished
raise self.app.shell.TclErrorException(text) # raise self.app.shell.TclErrorException(text)
def execute_wrapper(self, *args): def execute_wrapper(self, *args):
""" """
@@ -310,7 +310,7 @@ class TclCommand(object):
:return: None, output text or exception :return: None, output text or exception
""" """
raise NotImplementedError("Please Implement this method") return "Not Implemented Error -> Incorrect call."
class TclCommandSignaled(TclCommand): class TclCommandSignaled(TclCommand):
@@ -330,7 +330,7 @@ class TclCommandSignaled(TclCommand):
@abc.abstractmethod @abc.abstractmethod
def execute(self, args, unnamed_args): def execute(self, args, unnamed_args):
raise NotImplementedError("Please Implement this method") return "Not Implemented Error -> Incorrect call."
output = None output = None

View File

@@ -0,0 +1,117 @@
from tclCommands.TclCommand import *
import math
class TclCommandAddAperture(TclCommandSignaled):
"""
Tcl shell command to add a rectange to the given Geometry object.
"""
# array of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon)
aliases = ['add_aperture']
description = '%s %s' % ("--", "Adds an aperture in the given Gerber object, if it does not exist already.")
# Dictionary of types from Tcl command, needs to be ordered.
# For positional arguments
arg_names = collections.OrderedDict([
('name', str)
])
# Dictionary of types from Tcl command, needs to be ordered.
# For options like -optionname value
option_types = collections.OrderedDict([
('apid', int),
('type', str),
('size', float),
('width', float),
('height', float)
])
# array of mandatory options for current Tcl command: required = {'name','outname'}
required = ['name']
# structured help for current command, args needs to be ordered
help = {
'main': "Creates an aperture in the given Gerber object.",
'args': collections.OrderedDict([
('name', 'Name of the Gerber object in which to add the aperture.'),
('apid', 'Aperture ID. If not used then it will add one automatically.'),
('type', "Aperture Type. It can be letter 'C' or 'R' or 'O'. "
"If not used then it will use 'C' by default."),
('size', "The aperture size. Used only if aperture type is 'C'."),
('width', "Aperture width. Used only if aperture type is 'R' or 'O'."),
('height', "Aperture height. Used only if aperture type is 'R' or 'O'.")
]),
'examples': ["add_aperture gerber_name -apid 10 -type 'C' -size 1.0"]
}
def execute(self, args, unnamed_args):
"""
execute current TCL shell command
:param args: array of known named arguments and options
:param unnamed_args: array of other values which were passed into command
without -somename and we do not have them in known arg_names
:return: None or exception
"""
if unnamed_args:
self.raise_tcl_error(
"Too many arguments. Correct format: %s" %
'["add_aperture gerber_name -apid -type -size -width -height"]')
name = args['name']
try:
obj = self.app.collection.get_by_name(str(name))
except Exception:
return "Could not retrieve object: %s" % name
if obj is None:
return "Object not found: %s" % name
if obj.kind != 'gerber':
return 'Expected Gerber, got %s %s.' % (name, type(obj))
obj_apertures_keys = list(obj.apertures.keys())
if 'apid' in args:
new_apid = args['apid']
else:
ap_list = [int(ap) for ap in obj_apertures_keys]
max_ap = max(ap_list)
# we can't have aperture ID's between 0 an 10
new_apid = 10 if max_ap == 0 else (max_ap + 1)
if str(new_apid) in obj_apertures_keys:
return "The aperture is already used. Try another 'apid' parameter value."
if 'type' in args:
try:
# if it's a string using the quotes
new_type = eval(args['type'])
except NameError:
# if it's a string without quotes
new_type = args['type']
else:
new_type = 'C'
if new_type == 'C':
new_size = args['size'] if 'size' in args else 0.6
obj.apertures[str(new_apid)] = {
'type': new_type,
'size': new_size,
'geometry': []
}
elif new_type in ['R', 'O']:
new_width = args['width'] if 'width' in args else 0.0
new_height = args['height'] if 'height' in args else 0.0
new_size = math.sqrt(new_width ** 2 + new_height ** 2) if new_width > 0 and new_height > 0 else 0
obj.apertures[str(new_apid)] = {
'type': new_type,
'size': new_size,
'width': new_width,
'height': new_height,
'geometry': []
}
else:
return 'The supported aperture types are: C=circular, R=rectangular, O=oblong'
print(obj.apertures)

View File

@@ -0,0 +1,123 @@
from tclCommands.TclCommand import *
from copy import deepcopy
from shapely.geometry import Point
class TclCommandAddDrill(TclCommandSignaled):
"""
Tcl shell command to add a rectange to the given Geometry object.
"""
# array of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon)
aliases = ['add_drill']
description = '%s %s' % ("--", "Adds a drill in the given Excellon object, if it does not exist already.")
# Dictionary of types from Tcl command, needs to be ordered.
# For positional arguments
arg_names = collections.OrderedDict([
('name', str)
])
# Dictionary of types from Tcl command, needs to be ordered.
# For options like -optionname value
option_types = collections.OrderedDict([
('dia', float),
('x', float),
('y', float),
])
# array of mandatory options for current Tcl command: required = {'name','outname'}
required = ['name']
# structured help for current command, args needs to be ordered
help = {
'main': "Adds a drill hole in the given Excellon object.",
'args': collections.OrderedDict([
('name', 'Name of the Excellon object in which to add the drill hole.'),
('dia', 'The tool diameter.'),
('x', "X coordinate for the drill hole. If not used then the assumed X value is 0.0."),
('y', "Y coordinate for the drill hole. If not used then the assumed Y value is 0.0."),
]),
'examples': ["add_drill excellon_name -dia 0.8 -x 13.3 -y 6.2"]
}
def execute(self, args, unnamed_args):
"""
execute current TCL shell command
:param args: array of known named arguments and options
:param unnamed_args: array of other values which were passed into command
without -somename and we do not have them in known arg_names
:return: None or exception
"""
if unnamed_args:
self.raise_tcl_error(
"Too many arguments. Correct format: %s" %
'["add_drill excellon_name -dia -x -y"]')
name = args['name']
try:
obj = self.app.collection.get_by_name(str(name))
except Exception:
return "Could not retrieve object: %s" % name
if obj is None:
return "Object not found: %s" % name
if obj.kind != 'excellon':
return 'Expected Excellon, got %s %s.' % (name, type(obj))
if 'dia' not in args:
return "Failed. The -dia parameter is missing and it is required."
new_dia = args['dia']
drill_x = 0.0 if 'x' not in args else args['x']
drill_y = 0.0 if 'y' not in args else args['y']
# create a dict with the keys the tool dimater value truncated to the nr of decimals use by the app and the
# value is the tool number
dia_tool_dict = {
self.app.dec_format(diam['tooldia'], self.app.decimals): tool for tool, diam in list(obj.tools.items())
}
new_data = {}
kind = 'excellon'
for option in self.app.options:
if option.find(kind + "_") == 0:
oname = option[len(kind) + 1:]
new_data[oname] = self.app.options[option]
if option.find('tools_drill_') == 0:
new_data[option] = self.app.options[option]
drill_point = Point((drill_x, drill_y))
if not dia_tool_dict:
obj.tools[1] = {
'tooldia': new_dia,
'drills': [drill_point],
'slots': [],
'data': deepcopy(new_data)
}
elif new_dia not in list(dia_tool_dict.keys()):
new_tool = max(list(obj.tools.keys())) + 1
obj.tools[new_tool] = {
'tooldia': new_dia,
'drills': [drill_point],
'slots': [],
'data': deepcopy(new_data)
}
# sort the updated tools dict
new_tools = {}
tools_sorted = sorted(obj.tools.items(), key=lambda x: x[1]['tooldia'])
for idx, tool in enumerate(tools_sorted, start=1):
new_tools[idx] = tool[1]
obj.tools = deepcopy(new_tools)
elif new_dia in list(dia_tool_dict.keys()):
if drill_point not in obj.tools[dia_tool_dict[new_dia]]['drills']:
obj.tools[dia_tool_dict[new_dia]]['drills'].append(drill_point)
else:
return "Drill with the given coordinates is already in the object."
obj.create_geometry()

View File

@@ -7,7 +7,7 @@ class TclCommandAddRectangle(TclCommandSignaled):
""" """
# array of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon) # array of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon)
aliases = ['add_rectangle'] aliases = ['add_rect', 'add_rectangle']
description = '%s %s' % ("--", "Creates a rectangle in the given Geometry object.") description = '%s %s' % ("--", "Creates a rectangle in the given Geometry object.")

View File

@@ -0,0 +1,136 @@
from tclCommands.TclCommand import *
from copy import deepcopy
from shapely.geometry import Point
class TclCommandAddSlot(TclCommandSignaled):
"""
Tcl shell command to add a rectange to the given Geometry object.
"""
# array of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon)
aliases = ['add_slot']
description = '%s %s' % ("--", "Adds a slot in the given Excellon object, if it does not exist already.")
# Dictionary of types from Tcl command, needs to be ordered.
# For positional arguments
arg_names = collections.OrderedDict([
('name', str)
])
# Dictionary of types from Tcl command, needs to be ordered.
# For options like -optionname value
option_types = collections.OrderedDict([
('dia', float),
('startx', float),
('starty', float),
('stopx', float),
('stopy', float),
])
# array of mandatory options for current Tcl command: required = {'name','outname'}
required = ['name']
# structured help for current command, args needs to be ordered
help = {
'main': "Adds a slot hole in the given Excellon object.",
'args': collections.OrderedDict([
('name', 'Name of the Excellon object in which to add the slot.'),
('dia', 'The tool diameter. Required.'),
('startx', "The X coordinate for the slot start point. Required."),
('starty', "The Y coordinate for the slot stop point. Required."),
('stopx', "The X coordinate for the slot start point. Required."),
('stopy', "The Y coordinate for the slot stop point. Required."),
]),
'examples': ["add_slot excellon_name -dia 0.8 -startx 1 -starty 1 -stopx 1.3 -stopy 1"]
}
def execute(self, args, unnamed_args):
"""
execute current TCL shell command
:param args: array of known named arguments and options
:param unnamed_args: array of other values which were passed into command
without -somename and we do not have them in known arg_names
:return: None or exception
"""
if unnamed_args:
self.raise_tcl_error(
"Too many arguments. Correct format: %s" %
'["add_drill excellon_name -dia -x -y"]')
name = args['name']
try:
obj = self.app.collection.get_by_name(str(name))
except Exception:
return "Could not retrieve object: %s" % name
if obj is None:
return "Object not found: %s" % name
if obj.kind != 'excellon':
return 'Expected Excellon, got %s %s.' % (name, type(obj))
if 'dia' not in args:
return "Failed. The -dia parameter is missing and it is required."
if 'startx' not in args:
return "Failed. The -startx parameter is missing and it is required."
if 'starty' not in args:
return "Failed. The -starty parameter is missing and it is required."
if 'stopx' not in args:
return "Failed. The -stopx parameter is missing and it is required."
if 'stopy' not in args:
return "Failed. The -stopy parameter is missing and it is required."
new_dia = args['dia']
slot_start_x = args['startx']
slot_start_y = args['starty']
slot_stop_x = args['stopx']
slot_stop_y = args['stopy']
dia_tool_dict = {diam['tooldia']: tool for tool, diam in list(obj.tools.items())}
new_data = {}
kind = 'excellon'
for option in self.app.options:
if option.find(kind + "_") == 0:
oname = option[len(kind) + 1:]
new_data[oname] = self.app.options[option]
if option.find('tools_drill_') == 0:
new_data[option] = self.app.options[option]
new_slot = (
Point(slot_start_x, slot_start_y),
Point(slot_stop_x, slot_stop_y)
)
if not dia_tool_dict:
obj.tools[1] = {
'tooldia': new_dia,
'drills': [],
'slots': [new_slot],
'data': deepcopy(new_data)
}
elif new_dia not in list(dia_tool_dict.keys()):
new_tool = max(list(obj.tools.keys())) + 1
obj.tools[new_tool] = {
'tooldia': new_dia,
'drills': [],
'slots': [new_slot],
'data': deepcopy(new_data)
}
# sort the updated tools dict
new_tools = {}
tools_sorted = sorted(obj.tools.items(), key=lambda x: x[1]['tooldia'])
for idx, tool in enumerate(tools_sorted, start=1):
new_tools[idx] = tool[1]
obj.tools = deepcopy(new_tools)
elif new_dia in list(dia_tool_dict.keys()):
if new_slot not in obj.tools[dia_tool_dict[new_dia]]['slots']:
obj.tools[dia_tool_dict[new_dia]]['slots'].append(new_slot)
else:
return "Slot with the given coordinates is already in the object."
obj.create_geometry()

View File

@@ -58,4 +58,4 @@ class TclCommandNewExcellon(TclCommandSignaled):
name = args['name'] name = args['name']
else: else:
name = 'new_exc' name = 'new_exc'
self.app.app_obj.new_object('excellon', name, lambda x, y: None, plot=False) self.app.app_obj.new_excellon_object(new_name=name)

View File

@@ -52,4 +52,4 @@ class TclCommandNewGeometry(TclCommandSignaled):
else: else:
name = 'new_geo' name = 'new_geo'
self.app.app_obj.new_object('geometry', str(name), lambda x, y: None, plot=False) self.app.app_obj.new_geometry_object(new_name=name)

View File

@@ -59,21 +59,4 @@ class TclCommandNewGerber(TclCommandSignaled):
else: else:
name = 'new_grb' name = 'new_grb'
def initialize(grb_obj, app_obj): self.app.app_obj.new_gerber_object(new_name=name)
grb_obj.multitool = False
grb_obj.source_file = []
grb_obj.multigeo = False
grb_obj.follow = False
grb_obj.apertures = {}
grb_obj.solid_geometry = []
grb_obj.follow_geometry = []
try:
grb_obj.options['xmin'] = 0
grb_obj.options['ymin'] = 0
grb_obj.options['xmax'] = 0
grb_obj.options['ymax'] = 0
except KeyError:
pass
self.app.app_obj.new_object('gerber', name, initialize, plot=False)

View File

@@ -2,10 +2,13 @@ import pkgutil
import sys import sys
# allowed command modules (please append them alphabetically ordered) # allowed command modules (please append them alphabetically ordered)
import tclCommands.TclCommandAddAperture
import tclCommands.TclCommandAddCircle import tclCommands.TclCommandAddCircle
import tclCommands.TclCommandAddDrill
import tclCommands.TclCommandAddPolygon import tclCommands.TclCommandAddPolygon
import tclCommands.TclCommandAddPolyline import tclCommands.TclCommandAddPolyline
import tclCommands.TclCommandAddRectangle import tclCommands.TclCommandAddRectangle
import tclCommands.TclCommandAddSlot
import tclCommands.TclCommandAlignDrill import tclCommands.TclCommandAlignDrill
import tclCommands.TclCommandAlignDrillGrid import tclCommands.TclCommandAlignDrillGrid
import tclCommands.TclCommandBbox import tclCommands.TclCommandBbox