- fixed the Edit -> Conversion -> Join ... functions (merge() functions)

- updated translations
- Russian translate by @camellan is not finished yet
- some PEP8 cleanup in camlib.py
- RELEASE 8.918
This commit is contained in:
Marius Stanciu 2019-06-11 23:04:31 +03:00
parent d2d90493fc
commit a106472900
11 changed files with 1388 additions and 1644 deletions

View File

@ -95,7 +95,7 @@ class App(QtCore.QObject):
# Version
version = 8.918
version_date = "2019/06/09"
version_date = "2019/06/11"
beta = True
# current date now
@ -3457,14 +3457,14 @@ class App(QtCore.QObject):
obj_name_multi = str(name) if name else "Combo_MultiGeo"
tooldias = []
geo_type_list = []
geo_type_list = set()
objs = self.collection.get_selected()
for obj in objs:
geo_type_list.append(obj.multigeo)
geo_type_list.add(obj.multigeo)
# if len(set(geo_type_list)) == 1 means that all list elements are the same
if len(set(geo_type_list)) != 1:
# if len(geo_type_list) == 1 means that all list elements are the same
if len(geo_type_list) != 1:
self.inform.emit(_("[ERROR] 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"
@ -3476,7 +3476,7 @@ class App(QtCore.QObject):
# 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_list:
def initialize(obj, app):
FlatCAMGeometry.merge(objs, obj, multigeo=True)
FlatCAMGeometry.merge(self, geo_list=objs, geo_final=obj, multigeo=True)
# rename all the ['name] key in obj.tools[tooluid]['data'] to the obj_name_multi
for v in obj.tools.values():
@ -3484,7 +3484,7 @@ class App(QtCore.QObject):
self.new_object("geometry", obj_name_multi, initialize)
else:
def initialize(obj, app):
FlatCAMGeometry.merge(objs, obj, multigeo=False)
FlatCAMGeometry.merge(self, geo_list=objs, geo_final=obj, multigeo=False)
# rename all the ['name] key in obj.tools[tooluid]['data'] to the obj_name_multi
for v in obj.tools.values():
@ -3510,7 +3510,7 @@ class App(QtCore.QObject):
return
def initialize(obj, app):
FlatCAMExcellon.merge(objs, obj)
FlatCAMExcellon.merge(self, exc_list=objs, exc_final=obj)
self.new_object("excellon", 'Combo_Excellon', initialize)
self.should_we_save = True
@ -3532,7 +3532,7 @@ class App(QtCore.QObject):
return
def initialize(obj, app):
FlatCAMGerber.merge(objs, obj)
FlatCAMGerber.merge(self, grb_list=objs, grb_final=obj)
self.new_object("gerber", 'Combo_Gerber', initialize)
self.should_we_save = True

View File

@ -387,8 +387,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
ui_type = GerberObjectUI
@staticmethod
def merge(grb_list, grb_final):
def merge(self, grb_list, grb_final):
"""
Merges the geometry of objects in geo_list into
the geometry of geo_final.
@ -1590,15 +1589,14 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
# store the source file here
self.source_file = ""
self.multigeo = True
self.multigeo = False
# Attributes to be included in serialization
# Always append to it because it carries contents
# from predecessors.
self.ser_attrs += ['options', 'kind']
@staticmethod
def merge(exc_list, exc_final):
def merge(self, exc_list, exc_final):
"""
Merge Excellon objects found in exc_list parameter into exc_final object.
Options are always copied from source .
@ -1659,10 +1657,13 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
exc_final.zeros = exc.zeros
exc_final.units = exc.units
# ##########################################
# Here we add data to the exc_final object #
# ##########################################
# variable to make tool_name for the tools
current_tool = 0
# Here we add data to the exc_final object
# the tools diameter are now the keys in the drill_dia dict and the values are the Shapely Points in case of
# The tools diameter are now the keys in the drill_dia dict and the values are the Shapely Points in case of
# drills
for tool_dia in custom_dict_drills:
# we create a tool name for each key in the drill_dia dict (the key is a unique drill diameter)
@ -1681,8 +1682,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
}
)
# Here we add data to the exc_final object
# the tools diameter are now the keys in the drill_dia dict and the values are a list ([start, stop])
# The tools diameter are now the keys in the drill_dia dict and the values are a list ([start, stop])
# of two Shapely Points in case of slots
for tool_dia in custom_dict_slots:
# we create a tool name for each key in the slot_dia dict (the key is a unique slot diameter)
@ -2771,25 +2771,25 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
if not FlatCAMObj.plot(self):
return
try:
# Plot excellon (All polygons?)
if self.options["solid"]:
for tool in self.tools:
for geo in self.tools[tool]['solid_geometry']:
self.add_shape(shape=geo, color='#750000BF', face_color='#C40000BF',
visible=self.options['plot'],
layer=2)
else:
for tool in self.tools:
for geo in self.tools[tool]['solid_geometry']:
self.add_shape(shape=geo.exterior, color='red', visible=self.options['plot'])
for ints in geo.interiors:
self.add_shape(shape=ints, color='green', visible=self.options['plot'])
self.shapes.redraw()
return
except (ObjectDeleted, AttributeError, KeyError):
self.shapes.clear(update=True)
# try:
# # Plot Excellon (All polygons?)
# if self.options["solid"]:
# for tool in self.tools:
# for geo in self.tools[tool]['solid_geometry']:
# self.add_shape(shape=geo, color='#750000BF', face_color='#C40000BF',
# visible=self.options['plot'],
# layer=2)
# else:
# for tool in self.tools:
# for geo in self.tools[tool]['solid_geometry']:
# self.add_shape(shape=geo.exterior, color='red', visible=self.options['plot'])
# for ints in geo.interiors:
# self.add_shape(shape=ints, color='orange', visible=self.options['plot'])
#
# self.shapes.redraw()
# return
# except (ObjectDeleted, AttributeError, KeyError):
# self.shapes.clear(update=True)
# this stays for compatibility reasons, in case we try to open old projects
try:
@ -2798,55 +2798,22 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
self.solid_geometry = [self.solid_geometry]
try:
# Plot excellon (All polygons?)
# Plot Excellon (All polygons?)
if self.options["solid"]:
for geo in self.solid_geometry:
self.add_shape(shape=geo, color='#750000BF', face_color='#C40000BF', visible=self.options['plot'],
self.add_shape(shape=geo, color='#750000BF', face_color='#C40000BF',
visible=self.options['plot'],
layer=2)
else:
for geo in self.solid_geometry:
self.add_shape(shape=geo.exterior, color='red', visible=self.options['plot'])
for ints in geo.interiors:
self.add_shape(shape=ints, color='green', visible=self.options['plot'])
self.add_shape(shape=ints, color='orange', visible=self.options['plot'])
self.shapes.redraw()
except (ObjectDeleted, AttributeError):
self.shapes.clear(update=True)
# try:
# # Plot excellon (All polygons?)
# if self.options["solid"]:
# for geo_type in self.solid_geometry:
# if geo_type is not None:
# if type(geo_type) is dict:
# for tooldia in geo_type:
# geo_list = geo_type[tooldia]
# for geo in geo_list:
# self.add_shape(shape=geo, color='#750000BF', face_color='#C40000BF',
# visible=self.options['plot'],
# layer=2)
# else:
# self.add_shape(shape=geo_type, color='#750000BF', face_color='#C40000BF',
# visible=self.options['plot'],
# layer=2)
# else:
# for geo_type in self.solid_geometry:
# if geo_type is not None:
# if type(geo_type) is dict:
# for tooldia in geo_type:
# geo_list = geo_type[tooldia]
# for geo in geo_list:
# self.add_shape(shape=geo.exterior, color='red', visible=self.options['plot'])
# for ints in geo.interiors:
# self.add_shape(shape=ints, color='green', visible=self.options['plot'])
# else:
# self.add_shape(shape=geo_type.exterior, color='red', visible=self.options['plot'])
# for ints in geo_type.interiors:
# self.add_shape(shape=ints, color='green', visible=self.options['plot'])
# self.shapes.redraw()
# except (ObjectDeleted, AttributeError):
# self.shapes.clear(update=True)
class FlatCAMGeometry(FlatCAMObj, Geometry):
"""
@ -2856,8 +2823,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
optionChanged = QtCore.pyqtSignal(str)
ui_type = GeometryObjectUI
@staticmethod
def merge(geo_list, geo_final, multigeo=None):
def merge(self, geo_list, geo_final, multigeo=None):
"""
Merges the geometry of objects in grb_list into
the geometry of geo_final.
@ -2873,8 +2839,6 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
if type(geo_final.solid_geometry) is not list:
geo_final.solid_geometry = [geo_final.solid_geometry]
for geo in geo_list:
for option in geo.options:
if option is not 'name':
@ -2885,11 +2849,11 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
# Expand lists
if type(geo) is list:
FlatCAMGeometry.merge(geo, geo_final)
FlatCAMGeometry.merge(self, geo_list=geo, geo_final=geo_final)
# If not list, just append
else:
# merge solid_geometry, useful for singletool geometry, for multitool each is empty
if multigeo is None or multigeo == False:
if multigeo is None or multigeo is False:
geo_final.multigeo = False
try:
geo_final.solid_geometry.append(geo.solid_geometry)

View File

@ -9,6 +9,14 @@ CAD program, and create G-Code for Isolation routing.
=================================================
11.06.2019
- fixed the Edit -> Conversion -> Join ... functions (merge() functions)
- updated translations
- Russian translate by @camellan is not finished yet
- some PEP8 cleanup in camlib.py
- RELEASE 8.918
9.06.2019
- updated translations

View File

@ -52,16 +52,15 @@ from flatcamParsers.ParseDXF import *
import logging
import FlatCAMApp
import gettext
import FlatCAMTranslation as fcTranslate
import builtins
if platform.architecture()[0] == '64bit':
from ortools.constraint_solver import pywrapcp
from ortools.constraint_solver import routing_enums_pb2
import gettext
import FlatCAMTranslation as fcTranslate
fcTranslate.apply_language('strings')
import builtins
log = logging.getLogger('base2')
log.setLevel(logging.DEBUG)
@ -513,8 +512,8 @@ class Geometry(object):
:param offset: Offset distance.
:type offset: float
:param iso_type: type of isolation, can be 0 = exteriors or 1 = interiors or 2 = both (complete)
:type integer
:param corner: type of corner for the isolation: 0 = round; 1 = square; 2= beveled (line that connects the ends)
:param follow: whether the geometry to be isolated is a follow_geometry
:return: The buffered geometry.
:rtype: Shapely.MultiPolygon or Shapely.Polygon
"""
@ -533,7 +532,6 @@ class Geometry(object):
# geo_iso.append(self.solid_geometry.buffer(offset, int(int(self.geo_steps_per_circle) / 4)))
# return geo_iso
# commented this because of the bug with multiple passes cutting out of the copper
# geo_iso = []
# flattened_geo = self.flatten_list(self.solid_geometry)
@ -543,7 +541,6 @@ class Geometry(object):
# except TypeError:
# geo_iso.append(self.solid_geometry.buffer(offset, int(int(self.geo_steps_per_circle) / 4)))
# the previously commented block is replaced with this block - regression - to solve the bug with multiple
# isolation passes cutting from the copper features
if offset == 0:
@ -587,8 +584,10 @@ class Geometry(object):
:param filename: Path to the SVG file.
:type filename: str
:param object_type: parameter passed further along
:param flip: Flip the vertically.
:type flip: bool
:param units: FlatCAM units
:return: None
"""
@ -682,6 +681,10 @@ class Geometry(object):
:type filename: str
:param flip: Flip the object vertically.
:type flip: bool
:param units: FlatCAM units
:param dpi: dots per inch on the imported image
:param mode: how to import the image: as 'black' or 'color'
:param mask: level of detail for the import
:return: None
"""
scale_factor = 0.264583333
@ -780,8 +783,7 @@ class Geometry(object):
return boundary.difference(self.solid_geometry)
@staticmethod
def clear_polygon(polygon, tooldia, steps_per_circle, overlap=0.15, connect=True,
contour=True):
def clear_polygon(polygon, tooldia, steps_per_circle, overlap=0.15, connect=True, contour=True):
"""
Creates geometry inside a polygon for a tool to cover
the whole area.
@ -791,6 +793,7 @@ class Geometry(object):
:param polygon: Polygon to clear.
:param tooldia: Diameter of the tool.
:param steps_per_circle: number of linear segments to be used to approximate a circle
:param overlap: Overlap of toolpasses.
:param connect: Draw lines between disjoint segments to
minimize tool lifts.
@ -875,6 +878,7 @@ class Geometry(object):
the polygon.
:param polygon_to_clear: Shapely.geometry.Polygon
:param steps_per_circle: how many linear segments to use to approximate a circle
:param tooldia: Diameter of the tool
:param seedpoint: Shapely.geometry.Point or None
:param overlap: Tool fraction overlap bewteen passes
@ -916,8 +920,8 @@ class Geometry(object):
if path.is_empty:
break
else:
#geoms.append(path)
#geoms.insert(path)
# geoms.append(path)
# geoms.insert(path)
# path can be a collection of paths.
try:
for p in path:
@ -929,12 +933,14 @@ class Geometry(object):
# Clean inside edges (contours) of the original polygon
if contour:
outer_edges = [x.exterior for x in autolist(polygon_to_clear.buffer(-tooldia / 2, int(steps_per_circle / 4)))]
outer_edges = [x.exterior for x in autolist(
polygon_to_clear.buffer(-tooldia / 2, int(steps_per_circle / 4)))]
inner_edges = []
for x in autolist(polygon_to_clear.buffer(-tooldia / 2, int(steps_per_circle / 4))): # Over resulting polygons
# Over resulting polygons
for x in autolist(polygon_to_clear.buffer(-tooldia / 2, int(steps_per_circle / 4))):
for y in x.interiors: # Over interiors of each polygon
inner_edges.append(y)
#geoms += outer_edges + inner_edges
# geoms += outer_edges + inner_edges
for g in outer_edges + inner_edges:
geoms.insert(g)
@ -950,8 +956,7 @@ class Geometry(object):
return geoms
@staticmethod
def clear_polygon3(polygon, tooldia, steps_per_circle, overlap=0.15, connect=True,
contour=True):
def clear_polygon3(polygon, tooldia, steps_per_circle, overlap=0.15, connect=True, contour=True):
"""
Creates geometry inside a polygon for a tool to cover
the whole area.
@ -961,6 +966,7 @@ class Geometry(object):
:param polygon: The polygon being painted.
:type polygon: shapely.geometry.Polygon
:param tooldia: Tool diameter.
:param steps_per_circle: how many linear segments to use to approximate a circle
:param overlap: Tool path overlap percentage.
:param connect: Connect lines to avoid tool lifts.
:param contour: Paint around the edges.
@ -1026,8 +1032,11 @@ class Geometry(object):
"""
Scales all of the object's geometry by a given factor. Override
this method.
:param factor: Number by which to scale.
:type factor: float
:param xfactor: Number by which to scale on X axis.
:type xfactor: float
:param yfactor: Number by which to scale on Y axis.
:type yfactor: float
:param point: point to be used as reference for scaling; a tuple
:return: None
:rtype: None
"""
@ -1055,6 +1064,7 @@ class Geometry(object):
:type boundary: Polygon
:param tooldia: Tool diameter.
:rtype tooldia: float
:param steps_per_circle: how many linear segments to use to approximate a circle
:param max_walk: Maximum allowable distance without lifting tool.
:type max_walk: float or None
:return: Optimized geometry.
@ -1094,7 +1104,7 @@ class Geometry(object):
try:
while True:
path_count += 1
#log.debug("Path %d" % path_count)
# log.debug("Path %d" % path_count)
pt, candidate = storage.nearest(current_pt)
storage.remove(candidate)
@ -1112,7 +1122,7 @@ class Geometry(object):
walk_cut = walk_path.buffer(tooldia / 2, int(steps_per_circle / 4))
if walk_cut.within(boundary) and walk_path.length < max_walk:
#log.debug("Walk to path #%d is inside. Joining." % path_count)
# log.debug("Walk to path #%d is inside. Joining." % path_count)
# Completely inside. Append...
geo.coords = list(geo.coords) + list(candidate.coords)
@ -1136,7 +1146,7 @@ class Geometry(object):
# pt, geo = storage.nearest(current_pt)
except StopIteration: # Nothing left in storage.
#pass
# pass
optimized_paths.insert(geo)
return optimized_paths
@ -1273,7 +1283,7 @@ class Geometry(object):
return 1.0
self.units = units
self.scale(factor)
self.scale(factor, factor)
self.file_units_factor = factor
return factor
@ -1370,7 +1380,7 @@ class Geometry(object):
new_obj.append(mirror_geom(g))
return new_obj
else:
return affinity.scale(obj, xscale, yscale, origin=(px,py))
return affinity.scale(obj, xscale, yscale, origin=(px, py))
try:
if self.multigeo is True:
@ -1534,7 +1544,7 @@ class ApertureMacro:
# Separate parts
parts = self.raw.split('*')
#### Every part in the macro ## ##
# ### Every part in the macro ####
for part in parts:
# ## Comments. Ignored.
match = ApertureMacro.amcomm_re.search(part)
@ -2404,7 +2414,7 @@ class Gerber (Geometry):
if match.group(2): # Append
self.aperture_macros[current_macro].append(match.group(2))
if match.group(3): # Finish macro
#self.aperture_macros[current_macro].parse_content()
# self.aperture_macros[current_macro].parse_content()
current_macro = None
log.debug("Macro complete in 1 line.")
continue
@ -2414,7 +2424,7 @@ class Gerber (Geometry):
if match: # Finish macro
log.debug("End of macro. Line %d." % line_num)
self.aperture_macros[current_macro].append(match.group(1))
#self.aperture_macros[current_macro].parse_content()
# self.aperture_macros[current_macro].parse_content()
current_macro = None
else: # Append
self.aperture_macros[current_macro].append(gline)
@ -2592,7 +2602,7 @@ class Gerber (Geometry):
# is not and error.
if len(path) < 3:
# print "ERROR: Path contains less than 3 points:"
#path = [[current_x, current_y]]
# path = [[current_x, current_y]]
continue
# For regions we may ignore an aperture that is None
@ -2765,8 +2775,8 @@ class Gerber (Geometry):
except ValueError:
log.warning("Problem %s %s" % (gline, line_num))
self.app.inform.emit(_("[ERROR] Region does not have enough points. "
"File will be processed but there are parser errors. "
"Line number: %s") % str(line_num))
"File will be processed but there are parser errors. "
"Line number: %s") % str(line_num))
else:
if last_path_aperture is None:
log.warning("No aperture defined for curent path. (%d)" % line_num)
@ -2860,7 +2870,7 @@ class Gerber (Geometry):
# this treats the case when we are storing geometry as solids
flash = self.create_flash_geometry(
Point( [linear_x, linear_y]),
Point([linear_x, linear_y]),
self.apertures[current_aperture],
self.steps_per_circle
)
@ -3152,7 +3162,7 @@ class Gerber (Geometry):
except Exception as err:
ex_type, ex, tb = sys.exc_info()
traceback.print_tb(tb)
#print traceback.format_exc()
# print traceback.format_exc()
log.error("Gerber PARSING FAILED. Line %d: %s" % (line_num, gline))
loc = 'Gerber Line #%d Gerber Line Content: %s\n' % (line_num, gline) + repr(err)
@ -3323,8 +3333,10 @@ class Gerber (Geometry):
are recreated, the scaling will be lost. This behavior was modified
because of the complexity reached in this class.
:param factor: Number by which to scale.
:type factor: float
:param xfactor: Number by which to scale on X axis.
:type xfactor: float
:param yfactor: Number by which to scale on Y axis.
:type yfactor: float
:rtype : None
"""
log.debug("camlib.Gerber.scale()")
@ -3380,7 +3392,6 @@ class Gerber (Geometry):
self.app.inform.emit(_("[success] Gerber Scale done."))
# ## solid_geometry ???
# It's a cascaded union of objects.
# self.solid_geometry = affinity.scale(self.solid_geometry, factor,
@ -3506,7 +3517,7 @@ class Gerber (Geometry):
Parameters
----------
xs, ys : float, float
angle_x, angle_y : float, float
The shear angle(s) for the x and y axes respectively. These can be
specified in either degrees (default) or radians by setting
use_radians=True.
@ -3697,7 +3708,7 @@ class Excellon(Geometry):
'excellon_format_upper_in', 'excellon_format_lower_in', 'excellon_units', 'slots',
'source_file']
#### Patterns ## ##
# ### Patterns ####
# Regex basics:
# ^ - beginning
# $ - end
@ -4509,24 +4520,26 @@ class Excellon(Geometry):
:return: None
"""
self.solid_geometry = []
try:
# clear the solid_geometry in self.tools
for tool in self.tools:
self.tools[tool]['solid_geometry'][:] = []
try:
self.tools[tool]['solid_geometry'][:] = []
except KeyError:
self.tools[tool]['solid_geometry'] = []
for drill in self.drills:
# poly = drill['point'].buffer(self.tools[drill['tool']]["C"]/2.0)
if drill['tool'] is '':
self.app.inform.emit(_("[WARNING] Excellon.create_geometry() -> a drill location was skipped "
"due of not having a tool associated.\n"
"Check the resulting GCode."))
"due of not having a tool associated.\n"
"Check the resulting GCode."))
log.debug("Excellon.create_geometry() -> a drill location was skipped "
"due of not having a tool associated")
continue
tooldia = self.tools[drill['tool']]['C']
poly = drill['point'].buffer(tooldia / 2.0, int(int(self.geo_steps_per_circle) / 4))
# self.solid_geometry.append(poly)
self.solid_geometry.append(poly)
self.tools[drill['tool']]['solid_geometry'].append(poly)
for slot in self.slots:
@ -4536,7 +4549,7 @@ class Excellon(Geometry):
lines_string = LineString([start, stop])
poly = lines_string.buffer(slot_tooldia / 2.0, int(int(self.geo_steps_per_circle) / 4))
# self.solid_geometry.append(poly)
self.solid_geometry.append(poly)
self.tools[slot['tool']]['solid_geometry'].append(poly)
except Exception as e:

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff