- 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

@@ -264,17 +264,17 @@ class TclCommand(object):
raise unknown_exception
def raise_tcl_error(self, text):
"""
this method pass exception from python into TCL as error, so we get stacktrace and reason
:param text: text of error
:return: raise exception
"""
# 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
# handling and displayed after command is finished
raise self.app.shell.TclErrorException(text)
# def raise_tcl_error(self, text):
# """
# this method pass exception from python into TCL as error, so we get stacktrace and reason
# :param text: text of error
# :return: raise exception
# """
#
# # 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
# # handling and displayed after command is finished
# raise self.app.shell.TclErrorException(text)
def execute_wrapper(self, *args):
"""
@@ -310,7 +310,7 @@ class TclCommand(object):
:return: None, output text or exception
"""
raise NotImplementedError("Please Implement this method")
return "Not Implemented Error -> Incorrect call."
class TclCommandSignaled(TclCommand):
@@ -330,7 +330,7 @@ class TclCommandSignaled(TclCommand):
@abc.abstractmethod
def execute(self, args, unnamed_args):
raise NotImplementedError("Please Implement this method")
return "Not Implemented Error -> Incorrect call."
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)
aliases = ['add_rectangle']
aliases = ['add_rect', 'add_rectangle']
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']
else:
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:
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:
name = 'new_grb'
def initialize(grb_obj, app_obj):
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)
self.app.app_obj.new_gerber_object(new_name=name)

View File

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