716 lines
29 KiB
Python
716 lines
29 KiB
Python
|
|
from PyQt6 import QtCore, QtGui
|
|
|
|
from appObjects.ObjectCollection import GeometryObject, GerberObject, ExcellonObject
|
|
from appGUI.GUIElements import DialogBoxChoice
|
|
|
|
from copy import deepcopy
|
|
from shapely import MultiPolygon, Polygon, LinearRing, LineString, Point, unary_union
|
|
|
|
# App Translation
|
|
import gettext
|
|
import appTranslation as fcTranslate
|
|
import builtins
|
|
|
|
fcTranslate.apply_language('strings')
|
|
if '_' not in builtins.__dict__:
|
|
_ = gettext.gettext
|
|
|
|
|
|
class appEditor(QtCore.QObject):
|
|
def __init__(self, app):
|
|
super(appEditor, self).__init__()
|
|
|
|
self.app = app
|
|
self.log = self.app.log
|
|
self.inform = self.app.inform
|
|
self.splash = self.app.splash
|
|
self.worker_task = self.app.worker_task
|
|
self.options = self.app.options
|
|
self.app_units = self.app.app_units
|
|
self.defaults = self.app.defaults
|
|
self.collection = self.app.collection
|
|
self.app_obj = self.app.app_obj
|
|
self.decimals = self.app.decimals
|
|
|
|
def convert_any2geo(self):
|
|
"""
|
|
Will convert any object out of Gerber, Excellon, Geometry to Geometry object.
|
|
:return:
|
|
"""
|
|
self.defaults.report_usage("convert_any2geo()")
|
|
|
|
# store here the default data for Geometry Data
|
|
default_data = {}
|
|
|
|
for opt_key, opt_val in self.options.items():
|
|
if opt_key.find('geometry' + "_") == 0:
|
|
o_name = opt_key[len('geometry') + 1:]
|
|
default_data[o_name] = self.options[opt_key]
|
|
else:
|
|
default_data[opt_key] = self.options[opt_key]
|
|
|
|
if isinstance(self.options["tools_mill_tooldia"], float):
|
|
tools_diameters = [self.options["tools_mill_tooldia"]]
|
|
else:
|
|
try:
|
|
dias = str(self.options["tools_mill_tooldia"]).strip('[').strip(']')
|
|
tools_string = dias.split(",")
|
|
tools_diameters = [eval(a) for a in tools_string if a != '']
|
|
except Exception as e:
|
|
self.log.error("appEditor.convert_any2geo() --> %s" % str(e))
|
|
return 'fail'
|
|
|
|
tools = {}
|
|
t_id = 0
|
|
for tooldia in tools_diameters:
|
|
t_id += 1
|
|
new_tool = {
|
|
'tooldia': tooldia,
|
|
'offset': 'Path',
|
|
'offset_value': 0.0,
|
|
'type': 'Rough',
|
|
'tool_type': 'C1',
|
|
'data': deepcopy(default_data),
|
|
'solid_geometry': []
|
|
}
|
|
tools[t_id] = deepcopy(new_tool)
|
|
|
|
def initialize_from_gerber(new_obj, app_obj):
|
|
app_obj.log.debug("Gerber converted to Geometry: %s" % str(obj.obj_options["name"]))
|
|
new_obj.solid_geometry = deepcopy(obj.solid_geometry)
|
|
try:
|
|
new_obj.follow_geometry = obj.follow_geometry
|
|
except AttributeError:
|
|
pass
|
|
|
|
new_obj.obj_options.update(deepcopy(default_data))
|
|
new_obj.obj_options["tools_mill_tooldia"] = tools_diameters[0] if tools_diameters else 0.0
|
|
new_obj.tools = deepcopy(tools)
|
|
for k in new_obj.tools:
|
|
new_obj.tools[k]['solid_geometry'] = deepcopy(obj.solid_geometry)
|
|
|
|
def initialize_from_excellon(new_obj, app_obj):
|
|
app_obj.log.debug("Excellon converted to Geometry: %s" % str(obj.obj_options["name"]))
|
|
solid_geo = []
|
|
for tool in obj.tools:
|
|
for geo in obj.tools[tool]['solid_geometry']:
|
|
solid_geo.append(geo)
|
|
new_obj.solid_geometry = deepcopy(solid_geo)
|
|
if not new_obj.solid_geometry:
|
|
app_obj.log("convert_any2geo() failed")
|
|
return 'fail'
|
|
|
|
new_obj.obj_options.update(deepcopy(default_data))
|
|
new_obj.obj_options["tools_mill_tooldia"] = tools_diameters[0] if tools_diameters else 0.0
|
|
new_obj.tools = deepcopy(tools)
|
|
for k in new_obj.tools:
|
|
new_obj.tools[k]['solid_geometry'] = deepcopy(obj.solid_geometry)
|
|
|
|
if not self.collection.get_selected():
|
|
self.log.warning("appEditor.convert_any2geo --> No object selected")
|
|
self.inform.emit('[WARNING_NOTCL] %s' % _("No object is selected."))
|
|
return
|
|
|
|
for obj in self.collection.get_selected():
|
|
out_name = '%s_conv' % obj.obj_options["name"]
|
|
|
|
try:
|
|
if obj.kind == 'excellon':
|
|
self.app_obj.new_object("geometry", out_name, initialize_from_excellon)
|
|
|
|
if obj.kind == 'gerber':
|
|
self.app_obj.new_object("geometry", out_name, initialize_from_gerber)
|
|
except Exception as e:
|
|
self.log.error("Convert any2geo operation failed: %s" % str(e))
|
|
|
|
def convert_any2gerber(self):
|
|
"""
|
|
Will convert any object out of Gerber, Excellon, Geometry to Gerber object.
|
|
|
|
:return:
|
|
"""
|
|
|
|
def initialize_from_geometry(obj_init, app_obj):
|
|
apertures = {
|
|
0: {
|
|
'size': 0.0,
|
|
'type': 'REG',
|
|
'geometry': []
|
|
}
|
|
}
|
|
|
|
for obj_orig in obj.solid_geometry:
|
|
new_elem = {'solid': obj_orig}
|
|
try:
|
|
new_elem['follow'] = obj_orig.exterior
|
|
except AttributeError:
|
|
pass
|
|
apertures[0]['geometry'].append(deepcopy(new_elem))
|
|
|
|
obj_init.solid_geometry = deepcopy(obj.solid_geometry)
|
|
obj_init.tools = deepcopy(apertures)
|
|
|
|
if not obj_init.tools:
|
|
app_obj.log("convert_any2gerber() failed")
|
|
return 'fail'
|
|
|
|
def initialize_from_excellon(obj_init, app_obj):
|
|
apertures = {}
|
|
|
|
aperture_id = 10
|
|
for tool in obj.tools:
|
|
apertures[aperture_id] = {
|
|
'size': float(obj.tools[tool]['tooldia']),
|
|
'type': 'C',
|
|
'geometry': []
|
|
}
|
|
|
|
for geo in obj.tools[tool]['solid_geometry']:
|
|
new_el = {
|
|
'solid': geo,
|
|
'follow': geo.exterior
|
|
}
|
|
apertures[aperture_id]['geometry'].append(deepcopy(new_el))
|
|
|
|
aperture_id += 1
|
|
|
|
# create solid_geometry
|
|
solid_geometry = []
|
|
for apid_val in apertures.values():
|
|
for geo_el in apid_val['geometry']:
|
|
solid_geometry.append(geo_el['solid']) # noqa
|
|
|
|
solid_geometry = MultiPolygon(solid_geometry)
|
|
solid_geometry = solid_geometry.buffer(0.0000001)
|
|
|
|
obj_init.solid_geometry = deepcopy(solid_geometry)
|
|
obj_init.tools = deepcopy(apertures)
|
|
|
|
if not obj_init.tools:
|
|
app_obj.log("convert_any2gerber() failed")
|
|
return 'fail'
|
|
|
|
if not self.collection.get_selected():
|
|
self.log.warning("appEditor.convert_any2gerber --> No object selected")
|
|
self.inform.emit('[WARNING_NOTCL] %s' % _("No object is selected."))
|
|
return
|
|
|
|
for obj in self.collection.get_selected():
|
|
|
|
outname = '%s_conv' % obj.obj_options["name"]
|
|
|
|
try:
|
|
if obj.kind == 'excellon':
|
|
self.app_obj.new_object("gerber", outname, initialize_from_excellon)
|
|
elif obj.kind == 'geometry':
|
|
self.app_obj.new_object("gerber", outname, initialize_from_geometry)
|
|
else:
|
|
self.log.warning("appEditor.convert_any2gerber --> This is no valid object for conversion.")
|
|
|
|
except Exception as e:
|
|
return "Operation failed: %s" % str(e)
|
|
|
|
def convert_any2excellon(self, conv_obj_name=None):
|
|
"""
|
|
Will convert any object out of Gerber, Excellon, Geometry to an Excellon object.
|
|
|
|
:param conv_obj_name: a FlatCAM object
|
|
:return:
|
|
"""
|
|
|
|
self.log.debug("Running conversion to Excellon object...")
|
|
|
|
def initialize_from_geometry(obj_init, app_obj):
|
|
tools = {}
|
|
tool_uid = 1
|
|
|
|
obj_init.solid_geometry = []
|
|
|
|
for tool in obj.tools:
|
|
print(obj.tools[tool])
|
|
|
|
for geo in obj.solid_geometry:
|
|
if not isinstance(geo, (Polygon, MultiPolygon, LinearRing)):
|
|
continue
|
|
|
|
minx, miny, maxx, maxy = geo.bounds
|
|
new_dia = min([maxx - minx, maxy - miny])
|
|
|
|
new_drill = geo.centroid
|
|
new_drill_geo = new_drill.buffer(new_dia / 2.0)
|
|
|
|
current_tool_dias = []
|
|
if tools:
|
|
for tool in tools:
|
|
if tools[tool] and 'tooldia' in tools[tool]:
|
|
current_tool_dias.append(tools[tool]['tooldia'])
|
|
|
|
if new_dia in current_tool_dias:
|
|
digits = app_obj.decimals
|
|
for tool in tools:
|
|
if app_obj.dec_format(tools[tool]["tooldia"], digits) == app_obj.dec_format(new_dia, digits):
|
|
tools[tool]['drills'].append(new_drill)
|
|
tools[tool]['solid_geometry'].append(deepcopy(new_drill_geo))
|
|
else:
|
|
tools[tool_uid] = {}
|
|
tools[tool_uid]['tooldia'] = new_dia
|
|
tools[tool_uid]['drills'] = [new_drill]
|
|
tools[tool_uid]['slots'] = []
|
|
tools[tool_uid]['solid_geometry'] = [new_drill_geo]
|
|
tool_uid += 1
|
|
|
|
try:
|
|
obj_init.solid_geometry.append(new_drill_geo)
|
|
except (TypeError, AttributeError):
|
|
obj_init.solid_geometry = [new_drill_geo]
|
|
|
|
obj_init.tools = deepcopy(tools)
|
|
obj_init.solid_geometry = unary_union(obj_init.solid_geometry)
|
|
|
|
if not obj_init.solid_geometry:
|
|
return 'fail'
|
|
|
|
def initialize_from_gerber(obj_init, app_obj):
|
|
tools = {}
|
|
tool_uid = 1
|
|
digits = app_obj.decimals
|
|
|
|
obj_init.solid_geometry = []
|
|
|
|
for aperture_id in obj.tools:
|
|
if 'geometry' in obj.tools[aperture_id]:
|
|
for geo_dict in obj.tools[aperture_id]['geometry']:
|
|
if 'follow' in geo_dict:
|
|
if isinstance(geo_dict['follow'], Point):
|
|
geo = geo_dict['solid']
|
|
minx, miny, maxx, maxy = geo.bounds
|
|
new_dia = min([maxx - minx, maxy - miny])
|
|
|
|
new_drill = geo.centroid
|
|
new_drill_geo = new_drill.buffer(new_dia / 2.0)
|
|
|
|
current_tool_dias = []
|
|
if tools:
|
|
for tool in tools:
|
|
if tools[tool] and 'tooldia' in tools[tool]:
|
|
current_tool_dias.append(
|
|
app_obj.dec_format(tools[tool]['tooldia'], digits)
|
|
)
|
|
|
|
formatted_new_dia = app_obj.dec_format(new_dia, digits)
|
|
if formatted_new_dia in current_tool_dias:
|
|
for tool in tools:
|
|
if app_obj.dec_format(tools[tool]["tooldia"], digits) == formatted_new_dia:
|
|
if new_drill not in tools[tool]['drills']:
|
|
tools[tool]['drills'].append(new_drill)
|
|
tools[tool]['solid_geometry'].append(deepcopy(new_drill_geo))
|
|
else:
|
|
tools[tool_uid] = {
|
|
'tooldia': new_dia,
|
|
'drills': [new_drill],
|
|
'slots': [],
|
|
'solid_geometry': [new_drill_geo]
|
|
}
|
|
tool_uid += 1
|
|
|
|
try:
|
|
obj_init.solid_geometry.append(new_drill_geo)
|
|
except (TypeError, AttributeError):
|
|
obj_init.solid_geometry = [new_drill_geo]
|
|
elif isinstance(geo_dict['follow'], LineString):
|
|
geo_coordinates = list(geo_dict['follow'].coords)
|
|
|
|
# slots can have only a start and stop point and no intermediate points
|
|
if len(geo_coordinates) != 2:
|
|
continue
|
|
|
|
geo = geo_dict['solid']
|
|
try:
|
|
new_dia = obj.tools[aperture_id]['size']
|
|
except Exception:
|
|
continue
|
|
|
|
new_slot = (Point(geo_coordinates[0]), Point(geo_coordinates[1]))
|
|
new_slot_geo = geo
|
|
|
|
current_tool_dias = []
|
|
if tools:
|
|
for tool in tools:
|
|
if tools[tool] and 'tooldia' in tools[tool]:
|
|
current_tool_dias.append(
|
|
float('%.*f' % (self.decimals, tools[tool]['tooldia']))
|
|
)
|
|
|
|
if float('%.*f' % (self.decimals, new_dia)) in current_tool_dias:
|
|
for tool in tools:
|
|
if float('%.*f' % (self.decimals, tools[tool]["tooldia"])) == float(
|
|
'%.*f' % (self.decimals, new_dia)):
|
|
if new_slot not in tools[tool]['slots']:
|
|
tools[tool]['slots'].append(new_slot)
|
|
tools[tool]['solid_geometry'].append(deepcopy(new_slot_geo))
|
|
else:
|
|
tools[tool_uid] = {}
|
|
tools[tool_uid]['tooldia'] = new_dia
|
|
tools[tool_uid]['drills'] = []
|
|
tools[tool_uid]['slots'] = [new_slot]
|
|
tools[tool_uid]['solid_geometry'] = [new_slot_geo]
|
|
tool_uid += 1
|
|
|
|
try:
|
|
obj_init.solid_geometry.append(new_slot_geo)
|
|
except (TypeError, AttributeError):
|
|
obj_init.solid_geometry = [new_slot_geo]
|
|
|
|
obj_init.tools = deepcopy(tools)
|
|
obj_init.solid_geometry = unary_union(obj_init.solid_geometry)
|
|
|
|
if not obj_init.solid_geometry:
|
|
return 'fail'
|
|
obj_init.source_file = app_obj.f_handlers.export_excellon(obj_name=out_name, local_use=obj_init,
|
|
filename=None, use_thread=False)
|
|
|
|
if conv_obj_name is None:
|
|
if not self.collection.get_selected():
|
|
self.log.warning("appEditor.convert_any2excellon--> No object selected")
|
|
self.inform.emit('[WARNING_NOTCL] %s' % _("No object is selected."))
|
|
return
|
|
|
|
for obj in self.collection.get_selected():
|
|
|
|
obj_name = obj.obj_options["name"]
|
|
out_name = "%s_conv" % str(obj_name)
|
|
try:
|
|
if obj.kind == 'gerber':
|
|
self.app_obj.new_object("excellon", out_name, initialize_from_gerber)
|
|
elif obj.kind == 'geometry':
|
|
self.app_obj.new_object("excellon", out_name, initialize_from_geometry)
|
|
else:
|
|
self.log.warning("appEditor.convert_any2excellon --> This is no valid object for conversion.")
|
|
|
|
except Exception as e:
|
|
return "Operation failed: %s" % str(e)
|
|
else:
|
|
out_name = conv_obj_name
|
|
obj = self.collection.get_by_name(out_name)
|
|
|
|
try:
|
|
if obj.kind == 'gerber':
|
|
self.app_obj.new_object("excellon", out_name, initialize_from_gerber)
|
|
elif obj.kind == 'geometry':
|
|
self.app_obj.new_object("excellon", out_name, initialize_from_geometry)
|
|
else:
|
|
self.log.warning("appEditor.convert_any2excellon --> This is no valid object for conversion.")
|
|
|
|
except Exception as e:
|
|
self.log.error("appEditor.convert_any2excellon() --> %s" % str(e))
|
|
return "Operation failed: %s" % str(e)
|
|
|
|
def on_convert_singlegeo_to_multigeo(self):
|
|
"""
|
|
Called for converting a Geometry object from single-geo to multi-geo.
|
|
Single-geo Geometry objects store their geometry data into self.solid_geometry.
|
|
Multi-geo Geometry objects store their geometry data into the `self.tools` dictionary, each key
|
|
(a tool actually) having as a value another dictionary. This value dictionary has
|
|
one of its keys 'solid_geometry' which holds the solid-geometry of that tool.
|
|
|
|
:return: None
|
|
"""
|
|
self.defaults.report_usage("on_convert_singlegeo_to_multigeo()")
|
|
|
|
obj = self.collection.get_active()
|
|
|
|
if obj is None:
|
|
self.inform.emit('[ERROR_NOTCL] %s' % _("Failed. Select a Geometry Object and try again."))
|
|
return
|
|
|
|
if not isinstance(obj, GeometryObject):
|
|
self.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Expected a GeometryObject, got"), type(obj)))
|
|
return
|
|
|
|
obj.multigeo = True
|
|
for tooluid, dict_value in obj.tools.items():
|
|
dict_value['solid_geometry'] = deepcopy(obj.solid_geometry)
|
|
|
|
if not isinstance(obj.solid_geometry, list):
|
|
obj.solid_geometry = [obj.solid_geometry]
|
|
|
|
# obj.solid_geometry[:] = []
|
|
obj.plot()
|
|
|
|
self.app.should_we_save = True
|
|
|
|
self.inform.emit('[success] %s' % _("A Geometry object was converted to MultiGeo type."))
|
|
|
|
def on_convert_multigeo_to_singlegeo(self):
|
|
"""
|
|
Called for converting a Geometry object from multi-geo to single-geo.
|
|
Single-geo Geometry objects store their geometry data into self.solid_geometry.
|
|
Multi-geo Geometry objects store their geometry data into the self.tools dictionary, each key (a tool actually)
|
|
having as a value another dictionary. This value dictionary has one of its keys 'solid_geometry' which holds
|
|
the solid-geometry of that tool.
|
|
|
|
:return: None
|
|
"""
|
|
self.defaults.report_usage("on_convert_multigeo_to_singlegeo()")
|
|
|
|
obj = self.collection.get_active()
|
|
|
|
if obj is None:
|
|
self.inform.emit('[ERROR_NOTCL] %s' % _("Failed. Select a Geometry Object and try again."))
|
|
return
|
|
|
|
if not isinstance(obj, GeometryObject):
|
|
self.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Expected a GeometryObject, got"), type(obj)))
|
|
return
|
|
|
|
obj.multigeo = False
|
|
total_solid_geometry = []
|
|
for tool_uid, dict_value in obj.tools.items():
|
|
total_solid_geometry += deepcopy(dict_value['solid_geometry'])
|
|
# clear the original geometry
|
|
if isinstance(dict_value['solid_geometry'], list):
|
|
dict_value['solid_geometry'][:] = []
|
|
else:
|
|
dict_value['solid_geometry'] = []
|
|
obj.solid_geometry = deepcopy(total_solid_geometry)
|
|
obj.plot()
|
|
|
|
self.app.should_we_save = True
|
|
|
|
self.inform.emit('[success] %s' % _("A Geometry object was converted to SingleGeo type."))
|
|
|
|
def on_edit_join(self, name=None):
|
|
"""
|
|
Callback for Edit->Join. Joins the selected geometry objects into
|
|
a new one.
|
|
|
|
:return: None
|
|
"""
|
|
self.defaults.report_usage("on_edit_join()")
|
|
|
|
obj_name_single = str(name) if name else "Combo_SingleGeo"
|
|
obj_name_multi = str(name) if name else "Combo_MultiGeo"
|
|
|
|
geo_type_set = set()
|
|
|
|
objs = self.collection.get_selected()
|
|
|
|
if len(objs) < 2:
|
|
self.inform.emit('[ERROR_NOTCL] %s: %d' %
|
|
(_("At least two objects are required for join. Objects currently selected"), len(objs)))
|
|
return 'fail'
|
|
|
|
for obj in objs:
|
|
geo_type_set.add(obj.multigeo)
|
|
|
|
# if len(geo_type_list) == 1 means that all list elements are the same
|
|
if len(geo_type_set) != 1:
|
|
self.inform.emit('[ERROR] %s' %
|
|
_("Failed join. The Geometry objects are of different types.\n"
|
|
"At least one is MultiGeo type and the other is SingleGeo type. A possibility is to "
|
|
"convert from one to another and retry joining \n"
|
|
"but in the case of converting from MultiGeo to SingleGeo, informations may be lost and "
|
|
"the result may not be what was expected. \n"
|
|
"Check the generated GCODE."))
|
|
return
|
|
|
|
fuse_tools = self.options["geometry_merge_fuse_tools"]
|
|
|
|
# if at least one True object is in the list then due of the previous check, all list elements are True objects
|
|
if True in geo_type_set:
|
|
def initialize(geo_obj, app):
|
|
GeometryObject.merge(geo_list=objs, geo_final=geo_obj, multi_geo=True, fuse_tools=fuse_tools,
|
|
log=app.log)
|
|
app.inform.emit('[success] %s.' % _("Geometry merging finished"))
|
|
|
|
# rename all the ['name] key in obj.tools[tool_uid]['data'] to the obj_name_multi
|
|
for v in geo_obj.tools.values():
|
|
v['data']['name'] = obj_name_multi
|
|
|
|
self.app_obj.new_object("geometry", obj_name_multi, initialize)
|
|
else:
|
|
def initialize(geo_obj, app):
|
|
GeometryObject.merge(geo_list=objs, geo_final=geo_obj, multi_geo=False, fuse_tools=fuse_tools,
|
|
log=app.log)
|
|
app.inform.emit('[success] %s.' % _("Geometry merging finished"))
|
|
|
|
# rename all the ['name] key in obj.tools[tooluid]['data'] to the obj_name_multi
|
|
for v in geo_obj.tools.values():
|
|
v['data']['name'] = obj_name_single
|
|
|
|
self.app_obj.new_object("geometry", obj_name_single, initialize)
|
|
|
|
self.app.should_we_save = True
|
|
|
|
def on_edit_join_exc(self):
|
|
"""
|
|
Callback for Edit->Join Excellon. Joins the selected Excellon objects into
|
|
a new Excellon.
|
|
|
|
:return: None
|
|
"""
|
|
self.defaults.report_usage("on_edit_join_exc()")
|
|
|
|
objs = self.collection.get_selected()
|
|
|
|
for obj in objs:
|
|
if not isinstance(obj, ExcellonObject):
|
|
self.inform.emit('[ERROR_NOTCL] %s' % _("Failed. Excellon joining works only on Excellon objects."))
|
|
return
|
|
|
|
if len(objs) < 2:
|
|
self.inform.emit('[ERROR_NOTCL] %s: %d' %
|
|
(_("At least two objects are required for join. Objects currently selected"), len(objs)))
|
|
return 'fail'
|
|
|
|
fuse_tools = self.options["excellon_merge_fuse_tools"]
|
|
|
|
def initialize(exc_obj, app):
|
|
ExcellonObject.merge(exc_list=objs, exc_final=exc_obj, decimals=self.decimals, fuse_tools=fuse_tools,
|
|
log=app.log)
|
|
app.inform.emit('[success] %s.' % _("Excellon merging finished"))
|
|
|
|
self.app_obj.new_object("excellon", 'Combo_Excellon', initialize)
|
|
self.app.should_we_save = True
|
|
|
|
def on_edit_join_grb(self):
|
|
"""
|
|
Callback for Edit->Join Gerber. Joins the selected Gerber objects into
|
|
a new Gerber object.
|
|
|
|
:return: None
|
|
"""
|
|
self.defaults.report_usage("on_edit_join_grb()")
|
|
|
|
objs = self.collection.get_selected()
|
|
|
|
for obj in objs:
|
|
if not isinstance(obj, GerberObject):
|
|
self.inform.emit('[ERROR_NOTCL] %s' % _("Failed. Gerber joining works only on Gerber objects."))
|
|
return
|
|
|
|
if len(objs) < 2:
|
|
self.inform.emit('[ERROR_NOTCL] %s: %d' %
|
|
(_("At least two objects are required for join. Objects currently selected"), len(objs)))
|
|
return 'fail'
|
|
|
|
def initialize(grb_obj, app):
|
|
GerberObject.merge(grb_list=objs, grb_final=grb_obj, app=self)
|
|
app.inform.emit('[success] %s.' % _("Gerber merging finished"))
|
|
|
|
self.app_obj.new_object("gerber", 'Combo_Gerber', initialize)
|
|
self.app.should_we_save = True
|
|
|
|
def on_custom_origin(self, use_thread=True):
|
|
"""
|
|
Move selected objects to be centered in certain standard locations of the object (corners and center).
|
|
:param use_thread: Control if to use threaded operation. Boolean.
|
|
:return:
|
|
"""
|
|
|
|
obj_list = self.collection.get_selected()
|
|
|
|
if not obj_list:
|
|
self.inform.emit('[ERROR_NOTCL] %s' % _("Failed. No object(s) selected..."))
|
|
return
|
|
|
|
choices = [
|
|
{"label": _("Quadrant 2"), "value": "tl"},
|
|
{"label": _("Quadrant 1"), "value": "tr"},
|
|
{"label": _("Quadrant 3"), "value": "bl"},
|
|
{"label": _("Quadrant 4"), "value": "br"},
|
|
{"label": _("Center"), "value": "c"}
|
|
]
|
|
dia_box = DialogBoxChoice(title='%s:' % _("Custom Origin"),
|
|
icon=QtGui.QIcon(self.app.resource_location + '/origin3_32.png'),
|
|
choices=choices,
|
|
default_choice='c',
|
|
parent=self.app.ui)
|
|
if dia_box.ok is True:
|
|
try:
|
|
location_point = dia_box.location_point
|
|
except Exception:
|
|
return
|
|
else:
|
|
return
|
|
|
|
def worker_task():
|
|
with self.app.proc_container.new('%s ...' % _("Custom Origin")):
|
|
|
|
xminlist = []
|
|
yminlist = []
|
|
xmaxlist = []
|
|
ymaxlist = []
|
|
|
|
# first get a bounding box to fit all
|
|
for obj in obj_list:
|
|
xmin, ymin, xmax, ymax = obj.bounds()
|
|
xminlist.append(xmin)
|
|
yminlist.append(ymin)
|
|
xmaxlist.append(xmax)
|
|
ymaxlist.append(ymax)
|
|
|
|
# get the minimum x,y for all objects selected
|
|
x0 = min(xminlist)
|
|
y0 = min(yminlist)
|
|
x1 = max(xmaxlist)
|
|
y1 = max(ymaxlist)
|
|
|
|
if location_point == 'bl':
|
|
location = (x0, y0)
|
|
elif location_point == 'tl':
|
|
location = (x0, y1)
|
|
elif location_point == 'br':
|
|
location = (x1, y0)
|
|
elif location_point == 'tr':
|
|
location = (x1, y1)
|
|
else:
|
|
# center
|
|
cx = x0 + abs((x1 - x0) / 2)
|
|
cy = y0 + abs((y1 - y0) / 2)
|
|
location = (cx, cy)
|
|
|
|
for obj in obj_list:
|
|
obj.offset((-location[0], -location[1]))
|
|
self.app_obj.object_changed.emit(obj)
|
|
|
|
# Update the object bounding box options
|
|
a, b, c, d = obj.bounds()
|
|
obj.obj_options['xmin'] = a
|
|
obj.obj_options['ymin'] = b
|
|
obj.obj_options['xmax'] = c
|
|
obj.obj_options['ymax'] = d
|
|
|
|
# make sure to update the Offset field in Properties Tab
|
|
try:
|
|
obj.set_offset_values()
|
|
except AttributeError:
|
|
# not all objects have this attribute
|
|
pass
|
|
|
|
for obj in obj_list:
|
|
obj.plot()
|
|
self.app.plotcanvas.fit_view()
|
|
|
|
for obj in obj_list:
|
|
out_name = obj.obj_options["name"]
|
|
|
|
if obj.kind == 'gerber':
|
|
obj.source_file = self.app.f_handlers.export_gerber(
|
|
obj_name=out_name, filename=None, local_use=obj, use_thread=False)
|
|
elif obj.kind == 'excellon':
|
|
obj.source_file = self.app.f_handlers.export_excellon(
|
|
obj_name=out_name, filename=None, local_use=obj, use_thread=False)
|
|
elif obj.kind == 'geometry':
|
|
obj.source_file = self.app.f_handlers.export_dxf(
|
|
obj_name=out_name, filename=None, local_use=obj, use_thread=False)
|
|
|
|
self.inform.emit('[success] %s...' % _('Origin set'))
|
|
|
|
if use_thread is True:
|
|
self.worker_task.emit({'fcn': worker_task, 'params': []})
|
|
else:
|
|
worker_task()
|
|
self.app.should_we_save = True
|