Merged in marius_stanciu/flatcam_beta/Beta (pull request #274)

Beta
This commit is contained in:
Marius Stanciu 2019-12-23 21:34:29 +00:00
commit ac54dd9bd7
9 changed files with 470 additions and 100 deletions

View File

@ -830,6 +830,8 @@ class App(QtCore.QObject):
"tools_transform_offset_y": 0.0, "tools_transform_offset_y": 0.0,
"tools_transform_mirror_reference": False, "tools_transform_mirror_reference": False,
"tools_transform_mirror_point": (0, 0), "tools_transform_mirror_point": (0, 0),
"tools_transform_buffer_dis": 0.0,
"tools_transform_buffer_corner": True,
# SolderPaste Tool # SolderPaste Tool
"tools_solderpaste_tools": "1.0, 0.3", "tools_solderpaste_tools": "1.0, 0.3",
@ -1432,6 +1434,8 @@ class App(QtCore.QObject):
"tools_transform_offset_y": self.ui.tools_defaults_form.tools_transform_group.offy_entry, "tools_transform_offset_y": self.ui.tools_defaults_form.tools_transform_group.offy_entry,
"tools_transform_mirror_reference": self.ui.tools_defaults_form.tools_transform_group.mirror_reference_cb, "tools_transform_mirror_reference": self.ui.tools_defaults_form.tools_transform_group.mirror_reference_cb,
"tools_transform_mirror_point": self.ui.tools_defaults_form.tools_transform_group.flip_ref_entry, "tools_transform_mirror_point": self.ui.tools_defaults_form.tools_transform_group.flip_ref_entry,
"tools_transform_buffer_dis": self.ui.tools_defaults_form.tools_transform_group.buffer_entry,
"tools_transform_buffer_corner": self.ui.tools_defaults_form.tools_transform_group.buffer_rounded_cb,
# SolderPaste Dispensing Tool # SolderPaste Dispensing Tool
"tools_solderpaste_tools": self.ui.tools_defaults_form.tools_solderpaste_group.nozzle_tool_dia_entry, "tools_solderpaste_tools": self.ui.tools_defaults_form.tools_solderpaste_group.nozzle_tool_dia_entry,
@ -8852,12 +8856,14 @@ class App(QtCore.QObject):
# create the selection box around the selected object # create the selection box around the selected object
if self.defaults['global_selection_shape'] is True: if self.defaults['global_selection_shape'] is True:
self.draw_selection_shape(obj) self.draw_selection_shape(obj)
obj.selection_shape_drawn = True
self.collection.set_active(obj.options['name']) self.collection.set_active(obj.options['name'])
else: else:
if poly_selection.intersects(poly_obj): if poly_selection.intersects(poly_obj):
# create the selection box around the selected object # create the selection box around the selected object
if self.defaults['global_selection_shape'] is True: if self.defaults['global_selection_shape'] is True:
self.draw_selection_shape(obj) self.draw_selection_shape(obj)
obj.selection_shape_drawn = True
self.collection.set_active(obj.options['name']) self.collection.set_active(obj.options['name'])
except Exception as e: except Exception as e:
# the Exception here will happen if we try to select on screen and we have an newly (and empty) # the Exception here will happen if we try to select on screen and we have an newly (and empty)
@ -8904,20 +8910,26 @@ class App(QtCore.QObject):
# create the selection box around the selected object # create the selection box around the selected object
if self.defaults['global_selection_shape'] is True: if self.defaults['global_selection_shape'] is True:
self.draw_selection_shape(curr_sel_obj) self.draw_selection_shape(curr_sel_obj)
curr_sel_obj.selection_shape_drawn = True
elif self.collection.get_active().options['name'] not in objects_under_the_click_list: elif curr_sel_obj.options['name'] not in objects_under_the_click_list:
self.on_objects_selection(False) self.on_objects_selection(False)
self.delete_selection_shape() self.delete_selection_shape()
curr_sel_obj.selection_shape_drawn = False
self.collection.set_active(objects_under_the_click_list[0]) self.collection.set_active(objects_under_the_click_list[0])
curr_sel_obj = self.collection.get_active() curr_sel_obj = self.collection.get_active()
# create the selection box around the selected object # create the selection box around the selected object
if self.defaults['global_selection_shape'] is True: if self.defaults['global_selection_shape'] is True:
self.draw_selection_shape(curr_sel_obj) self.draw_selection_shape(curr_sel_obj)
curr_sel_obj.selection_shape_drawn = True
self.selected_message(curr_sel_obj=curr_sel_obj) self.selected_message(curr_sel_obj=curr_sel_obj)
elif curr_sel_obj.selection_shape_drawn is False:
if self.defaults['global_selection_shape'] is True:
self.draw_selection_shape(curr_sel_obj)
curr_sel_obj.selection_shape_drawn = True
else: else:
self.on_objects_selection(False) self.on_objects_selection(False)
self.delete_selection_shape() self.delete_selection_shape()
@ -8932,6 +8944,7 @@ class App(QtCore.QObject):
# make active the first element of the overlapped objects list # make active the first element of the overlapped objects list
if self.collection.get_active() is None: if self.collection.get_active() is None:
self.collection.set_active(objects_under_the_click_list[0]) self.collection.set_active(objects_under_the_click_list[0])
objects_under_the_click_list[0].selection_shape_drawn = True
name_sel_obj = self.collection.get_active().options['name'] name_sel_obj = self.collection.get_active().options['name']
# In case that there is a selected object but it is not in the overlapped object list # In case that there is a selected object but it is not in the overlapped object list
@ -8949,9 +8962,12 @@ class App(QtCore.QObject):
curr_sel_obj = self.collection.get_active() curr_sel_obj = self.collection.get_active()
# delete the possible selection box around a possible selected object # delete the possible selection box around a possible selected object
self.delete_selection_shape() self.delete_selection_shape()
curr_sel_obj.selection_shape_drawn = False
# create the selection box around the selected object # create the selection box around the selected object
if self.defaults['global_selection_shape'] is True: if self.defaults['global_selection_shape'] is True:
self.draw_selection_shape(curr_sel_obj) self.draw_selection_shape(curr_sel_obj)
curr_sel_obj.selection_shape_drawn = True
self.selected_message(curr_sel_obj=curr_sel_obj) self.selected_message(curr_sel_obj=curr_sel_obj)
@ -8961,6 +8977,9 @@ class App(QtCore.QObject):
# delete the possible selection box around a possible selected object # delete the possible selection box around a possible selected object
self.delete_selection_shape() self.delete_selection_shape()
for o in self.collection.get_list():
o.selection_shape_drawn = False
# and as a convenience move the focus to the Project tab because Selected tab is now empty but # and as a convenience move the focus to the Project tab because Selected tab is now empty but
# only when working on App # only when working on App
if self.call_source == 'app': if self.call_source == 'app':
@ -11512,26 +11531,28 @@ class App(QtCore.QObject):
App.log.debug(" **************** Started PROEJCT loading... **************** ") App.log.debug(" **************** Started PROEJCT loading... **************** ")
for obj in d['objs']: for obj in d['objs']:
def obj_init(obj_inst, app_inst): try:
def obj_init(obj_inst, app_inst):
obj_inst.from_dict(obj) obj_inst.from_dict(obj)
App.log.debug("Recreating from opened project an %s object: %s" % App.log.debug("Recreating from opened project an %s object: %s" %
(obj['kind'].capitalize(), obj['options']['name'])) (obj['kind'].capitalize(), obj['options']['name']))
# for some reason, setting ui_title does not work when this method is called from Tcl Shell # for some reason, setting ui_title does not work when this method is called from Tcl Shell
# it's because the TclCommand is run in another thread (it inherit TclCommandSignaled) # it's because the TclCommand is run in another thread (it inherit TclCommandSignaled)
if cli is None: if cli is None:
self.set_ui_title(name="{} {}: {}".format(_("Loading Project ... restoring"), self.set_ui_title(name="{} {}: {}".format(_("Loading Project ... restoring"),
obj['kind'].upper(), obj['kind'].upper(),
obj['options']['name'] obj['options']['name']
) )
) )
self.new_object(obj['kind'], obj['options']['name'], obj_init, active=False, fit=False, plot=plot) self.new_object(obj['kind'], obj['options']['name'], obj_init, active=False, fit=False, plot=plot)
except Exception as e:
print('App.open_project() --> ' + str(e))
self.inform.emit('[success] %s: %s' % self.inform.emit('[success] %s: %s' % (_("Project loaded from"), filename))
(_("Project loaded from"), filename))
self.should_we_save = False self.should_we_save = False
self.file_opened.emit("project", filename) self.file_opened.emit("project", filename)
@ -12365,7 +12386,10 @@ class App(QtCore.QObject):
new_color = self.defaults['global_plot_fill'] new_color = self.defaults['global_plot_fill']
act_name = self.sender().text().lower() act_name = self.sender().text().lower()
sel_obj = self.collection.get_active() sel_obj_list = self.collection.get_selected()
if not sel_obj_list:
return
if act_name == 'red': if act_name == 'red':
new_color = '#FF0000' + \ new_color = '#FF0000' + \
@ -12397,22 +12421,22 @@ class App(QtCore.QObject):
new_color = str(plot_fill_color.name()) + \ new_color = str(plot_fill_color.name()) + \
str(hex(self.ui.general_defaults_form.general_gui_group.pf_color_alpha_slider.value())[2:]) str(hex(self.ui.general_defaults_form.general_gui_group.pf_color_alpha_slider.value())[2:])
if self.is_legacy is False: new_line_color = color_variant(new_color[:7], 0.7)
new_line_color = color_variant(new_color[:7], 0.7)
sel_obj.fill_color = new_color
sel_obj.outline_color = new_line_color
sel_obj.shapes.redraw( for sel_obj in sel_obj_list:
update_colors=(new_color, new_line_color) if self.is_legacy is False:
) sel_obj.fill_color = new_color
else: sel_obj.outline_color = new_line_color
new_line_color = color_variant(new_color[:7], 0.7)
sel_obj.fill_color = new_color sel_obj.shapes.redraw(
sel_obj.outline_color = new_line_color update_colors=(new_color, new_line_color)
sel_obj.shapes.redraw( )
update_colors=(new_color, new_line_color) else:
) sel_obj.fill_color = new_color
sel_obj.outline_color = new_line_color
sel_obj.shapes.redraw(
update_colors=(new_color, new_line_color)
)
def on_grid_snap_triggered(self, state): def on_grid_snap_triggered(self, state):
if state: if state:

View File

@ -128,6 +128,9 @@ class FlatCAMObj(QtCore.QObject):
self.isHovering = False self.isHovering = False
self.notHovering = True self.notHovering = True
# Flag to show if a selection shape is drawn
self.selection_shape_drawn = False
# self.units = 'IN' # self.units = 'IN'
self.units = self.app.defaults['units'] self.units = self.app.defaults['units']
@ -596,7 +599,9 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
def __init__(self, name): def __init__(self, name):
self.decimals = self.app.decimals self.decimals = self.app.decimals
Gerber.__init__(self, steps_per_circle=int(self.app.defaults["gerber_circle_steps"])) self.circle_steps = int(self.app.defaults["gerber_circle_steps"])
Gerber.__init__(self, steps_per_circle=self.circle_steps)
FlatCAMObj.__init__(self, name) FlatCAMObj.__init__(self, name)
self.kind = "gerber" self.kind = "gerber"
@ -2196,6 +2201,10 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
Gerber.skew(self, angle_x=angle_x, angle_y=angle_y, point=point) Gerber.skew(self, angle_x=angle_x, angle_y=angle_y, point=point)
self.replotApertures.emit() self.replotApertures.emit()
def buffer(self, distance, join):
Gerber.buffer(self, distance=distance, join=join)
self.replotApertures.emit()
def serialize(self): def serialize(self):
return { return {
"options": self.options, "options": self.options,
@ -2214,7 +2223,9 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
def __init__(self, name): def __init__(self, name):
self.decimals = self.app.decimals self.decimals = self.app.decimals
Excellon.__init__(self, geo_steps_per_circle=int(self.app.defaults["geometry_circle_steps"])) self.circle_steps = int(self.app.defaults["geometry_circle_steps"])
Excellon.__init__(self, geo_steps_per_circle=self.circle_steps)
FlatCAMObj.__init__(self, name) FlatCAMObj.__init__(self, name)
self.kind = "excellon" self.kind = "excellon"
@ -3542,8 +3553,11 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
def __init__(self, name): def __init__(self, name):
self.decimals = self.app.decimals self.decimals = self.app.decimals
self.circle_steps = int(self.app.defaults["geometry_circle_steps"])
FlatCAMObj.__init__(self, name) FlatCAMObj.__init__(self, name)
Geometry.__init__(self, geo_steps_per_circle=int(self.app.defaults["geometry_circle_steps"])) Geometry.__init__(self, geo_steps_per_circle=self.circle_steps)
self.kind = "geometry" self.kind = "geometry"
@ -3865,15 +3879,18 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
if def_key == opt_key: if def_key == opt_key:
self.default_data[def_key] = deepcopy(opt_val) self.default_data[def_key] = deepcopy(opt_val)
try: if type(self.options["cnctooldia"]) == float:
temp_tools = self.options["cnctooldia"].split(",") tools_list = [self.options["cnctooldia"]]
tools_list = [ else:
float(eval(dia)) for dia in temp_tools if dia != '' try:
] temp_tools = self.options["cnctooldia"].split(",")
except Exception as e: tools_list = [
log.error("At least one tool diameter needed. Verify in Edit -> Preferences -> Geometry General -> " float(eval(dia)) for dia in temp_tools if dia != ''
"Tool dia. %s" % str(e)) ]
return except Exception as e:
log.error("FlatCAMGeometry.set_ui() -> At least one tool diameter needed. "
"Verify in Edit -> Preferences -> Geometry General -> Tool dia. %s" % str(e))
return
self.tooluid += 1 self.tooluid += 1

View File

@ -14,6 +14,13 @@ CAD program, and create G-Code for Isolation routing.
- some fixes in the Legacy(2D) graphic mode regarding the possibility of changing the color of the Gerber objects - some fixes in the Legacy(2D) graphic mode regarding the possibility of changing the color of the Gerber objects
- added a method to darken the outline color for Gerber objects when they have the color set - added a method to darken the outline color for Gerber objects when they have the color set
- when Printing as PDF Gerber objects now the rendered color is the print color - when Printing as PDF Gerber objects now the rendered color is the print color
- speed up the plotting in OpenGL(3D) graphic mode
- spped up the color setting for Gerber object when using the OpenGL(3D) graphic mode
- setting color for Gerber objects work on a selection of Gerber objects
- ~~when the selection is changed in the Project Tree the selection shape on canvas is deleted~~
- if an object is selected on Project Tree and it does not have the selection shape drawn, first click on canvas over it will draw the selection shape
- in Tool Transform added a new feature named 'Buffer'. For Geometry and Gerber objects will create (and replace) a geometry at a distance from the original geometry and for Excellon will adjust the Tool diameters
- solved issue #355 - when the tool diameter field in the Edit → Preferences → Geometry → Geometry General → Tools → Tool dia is only one the app failed to read it
22.12.2019 22.12.2019

View File

@ -2118,6 +2118,69 @@ class Geometry(object):
# self.solid_geometry = affinity.skew(self.solid_geometry, angle_x, angle_y, # self.solid_geometry = affinity.skew(self.solid_geometry, angle_x, angle_y,
# origin=(px, py)) # origin=(px, py))
def buffer(self, distance, join):
"""
:param distance:
:param join:
:return:
"""
log.debug("camlib.Geometry.buffer()")
if distance == 0:
return
def buffer_geom(obj):
if type(obj) is list:
new_obj = []
for g in obj:
new_obj.append(buffer_geom(g))
return new_obj
else:
try:
self.el_count += 1
disp_number = int(np.interp(self.el_count, [0, self.geo_len], [0, 100]))
if self.old_disp_number < disp_number <= 100:
self.app.proc_container.update_view_text(' %d%%' % disp_number)
self.old_disp_number = disp_number
return obj.buffer(distance, resolution=self.geo_steps_per_circle, join_style=join)
except AttributeError:
return obj
try:
if self.multigeo is True:
for tool in self.tools:
# variables to display the percentage of work done
self.geo_len = 0
try:
for __ in self.tools[tool]['solid_geometry']:
self.geo_len += 1
except TypeError:
self.geo_len = 1
self.old_disp_number = 0
self.el_count = 0
self.tools[tool]['solid_geometry'] = buffer_geom(self.tools[tool]['solid_geometry'])
# variables to display the percentage of work done
self.geo_len = 0
try:
for __ in self.solid_geometry:
self.geo_len += 1
except TypeError:
self.geo_len = 1
self.old_disp_number = 0
self.el_count = 0
self.solid_geometry = buffer_geom(self.solid_geometry)
self.app.inform.emit('[success] %s...' % _('Object was buffered'))
except AttributeError:
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to buffer. No object selected"))
self.app.proc_container.new_text = ''
class AttrDict(dict): class AttrDict(dict):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):

View File

@ -5378,7 +5378,7 @@ class ToolsTransformPrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.skewy_label, 4, 0) grid0.addWidget(self.skewy_label, 4, 0)
grid0.addWidget(self.skewy_entry, 4, 1) grid0.addWidget(self.skewy_entry, 4, 1)
# ## Scale factor on X axis # ## Scale
scale_title_lbl = QtWidgets.QLabel('<b>%s</b>' % _("Scale")) scale_title_lbl = QtWidgets.QLabel('<b>%s</b>' % _("Scale"))
grid0.addWidget(scale_title_lbl, 5, 0, 1, 2) grid0.addWidget(scale_title_lbl, 5, 0, 1, 2)
@ -5425,7 +5425,7 @@ class ToolsTransformPrefGroupUI(OptionsGroupUI):
) )
grid0.addWidget(self.reference_cb, 8, 1) grid0.addWidget(self.reference_cb, 8, 1)
# ## Offset distance on X axis # ## Offset
offset_title_lbl = QtWidgets.QLabel('<b>%s</b>' % _("Offset")) offset_title_lbl = QtWidgets.QLabel('<b>%s</b>' % _("Offset"))
grid0.addWidget(offset_title_lbl, 9, 0, 1, 2) grid0.addWidget(offset_title_lbl, 9, 0, 1, 2)
@ -5454,6 +5454,10 @@ class ToolsTransformPrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.offy_label, 11, 0) grid0.addWidget(self.offy_label, 11, 0)
grid0.addWidget(self.offy_entry, 11, 1) grid0.addWidget(self.offy_entry, 11, 1)
# ## Mirror
mirror_title_lbl = QtWidgets.QLabel('<b>%s</b>' % _("Mirror"))
grid0.addWidget(mirror_title_lbl, 12, 0, 1, 2)
# ## Mirror (Flip) Reference Point # ## Mirror (Flip) Reference Point
self.mirror_reference_cb = FCCheckBox('%s' % _("Mirror Reference")) self.mirror_reference_cb = FCCheckBox('%s' % _("Mirror Reference"))
self.mirror_reference_cb.setToolTip( self.mirror_reference_cb.setToolTip(
@ -5466,9 +5470,9 @@ class ToolsTransformPrefGroupUI(OptionsGroupUI):
"Then click Add button to insert coordinates.\n" "Then click Add button to insert coordinates.\n"
"Or enter the coords in format (x, y) in the\n" "Or enter the coords in format (x, y) in the\n"
"Point Entry field and click Flip on X(Y)")) "Point Entry field and click Flip on X(Y)"))
grid0.addWidget(self.mirror_reference_cb, 12, 0, 1, 2) grid0.addWidget(self.mirror_reference_cb, 13, 0, 1, 2)
self.flip_ref_label = QtWidgets.QLabel('<b>%s</b>' % _("Mirror Reference point")) self.flip_ref_label = QtWidgets.QLabel('%s' % _("Mirror Reference point"))
self.flip_ref_label.setToolTip( self.flip_ref_label.setToolTip(
_("Coordinates in format (x, y) used as reference for mirroring.\n" _("Coordinates in format (x, y) used as reference for mirroring.\n"
"The 'x' in (x, y) will be used when using Flip on X and\n" "The 'x' in (x, y) will be used when using Flip on X and\n"
@ -5476,8 +5480,42 @@ class ToolsTransformPrefGroupUI(OptionsGroupUI):
) )
self.flip_ref_entry = EvalEntry2("(0, 0)") self.flip_ref_entry = EvalEntry2("(0, 0)")
grid0.addWidget(self.flip_ref_label, 13, 0, 1, 2) grid0.addWidget(self.flip_ref_label, 14, 0, 1, 2)
grid0.addWidget(self.flip_ref_entry, 14, 0, 1, 2) grid0.addWidget(self.flip_ref_entry, 15, 0, 1, 2)
# ## Buffer
buffer_title_lbl = QtWidgets.QLabel('<b>%s</b>' % _("Buffer"))
grid0.addWidget(buffer_title_lbl, 16, 0, 1, 2)
self.buffer_label = QtWidgets.QLabel('%s:' % _("Distance"))
self.buffer_label.setToolTip(
_("A positive value will create the effect of dilation,\n"
"while a negative value will create the effect of erosion.\n"
"Each geometry element of the object will be increased\n"
"or decreased with the 'distance'.")
)
self.buffer_entry = FCDoubleSpinner()
self.buffer_entry.set_precision(self.decimals)
self.buffer_entry.setSingleStep(0.1)
self.buffer_entry.setWrapping(True)
self.buffer_entry.set_range(-9999.9999, 9999.9999)
grid0.addWidget(self.buffer_label, 17, 0)
grid0.addWidget(self.buffer_entry, 17, 1)
self.buffer_rounded_cb = FCCheckBox()
self.buffer_rounded_cb.setText('%s' % _("Rounded"))
self.buffer_rounded_cb.setToolTip(
_("If checked then the buffer will surround the buffered shape,\n"
"every corner will be rounded.\n"
"If not checked then the buffer will follow the exact geometry\n"
"of the buffered shape.")
)
grid0.addWidget(self.buffer_rounded_cb, 18, 0, 1, 2)
grid0.addWidget(QtWidgets.QLabel(''), 19, 0, 1, 2)
self.layout.addStretch() self.layout.addStretch()

View File

@ -45,44 +45,48 @@ def _update_shape_buffers(data, triangulation='glu'):
geo, color, face_color, tolerance = data['geometry'], data['color'], data['face_color'], data['tolerance'] geo, color, face_color, tolerance = data['geometry'], data['color'], data['face_color'], data['tolerance']
if geo is not None and not geo.is_empty: if geo is not None and not geo.is_empty:
simple = geo.simplify(tolerance) if tolerance else geo # Simplified shape simplified_geo = geo.simplify(tolerance) if tolerance else geo # Simplified shape
pts = [] # Shape line points pts = [] # Shape line points
tri_pts = [] # Mesh vertices tri_pts = [] # Mesh vertices
tri_tris = [] # Mesh faces tri_tris = [] # Mesh faces
if type(geo) == LineString: if type(geo) == LineString:
# Prepare lines # Prepare lines
pts = _linestring_to_segments(list(simple.coords)) pts = _linestring_to_segments(list(simplified_geo.coords))
elif type(geo) == LinearRing: elif type(geo) == LinearRing:
# Prepare lines # Prepare lines
pts = _linearring_to_segments(list(simple.coords)) pts = _linearring_to_segments(list(simplified_geo.coords))
elif type(geo) == Polygon: elif type(geo) == Polygon:
# Prepare polygon faces # Prepare polygon faces
if face_color is not None: if face_color is not None:
if triangulation == 'glu': if triangulation == 'glu':
gt = GLUTess() gt = GLUTess()
tri_tris, tri_pts = gt.triangulate(simple) tri_tris, tri_pts = gt.triangulate(simplified_geo)
else: else:
print("Triangulation type '%s' isn't implemented. Drawing only edges." % triangulation) print("Triangulation type '%s' isn't implemented. Drawing only edges." % triangulation)
# Prepare polygon edges # Prepare polygon edges
if color is not None: if color is not None:
pts = _linearring_to_segments(list(simple.exterior.coords)) pts = _linearring_to_segments(list(simplified_geo.exterior.coords))
for ints in simple.interiors: for ints in simplified_geo.interiors:
pts += _linearring_to_segments(list(ints.coords)) pts += _linearring_to_segments(list(ints.coords))
# Appending data for mesh # Appending data for mesh
if len(tri_pts) > 0 and len(tri_tris) > 0: if len(tri_pts) > 0 and len(tri_tris) > 0:
mesh_tris += tri_tris mesh_tris += tri_tris
mesh_vertices += tri_pts mesh_vertices += tri_pts
mesh_colors += [Color(face_color).rgba] * (len(tri_tris) // 3) face_color_rgba = Color(face_color).rgba
# mesh_colors += [face_color_rgba] * (len(tri_tris) // 3)
mesh_colors += [face_color_rgba for __ in range(len(tri_tris) // 3)]
# Appending data for line # Appending data for line
if len(pts) > 0: if len(pts) > 0:
line_pts += pts line_pts += pts
line_colors += [Color(color).rgba] * len(pts) colo_rgba = Color(color).rgba
# line_colors += [colo_rgba] * len(pts)
line_colors += [colo_rgba for __ in range(len(pts))]
# Store buffers # Store buffers
data['line_pts'] = line_pts data['line_pts'] = line_pts
@ -314,12 +318,27 @@ class ShapeCollectionVisual(CompoundVisual):
self.__update() self.__update()
def update_color(self, new_mesh_color=None, new_line_color=None, indexes=None): def update_color(self, new_mesh_color=None, new_line_color=None, indexes=None):
if (new_mesh_color is None or new_mesh_color == '') and (new_line_color is None or new_line_color == ''): if new_mesh_color is None and new_line_color is None:
return return
if not self.data: if not self.data:
return return
# if a new color is empty string then make it None so it will not be updated
# if a new color is valid then transform it here in a format palatable
mesh_color_rgba = None
line_color_rgba = None
if new_mesh_color:
if new_mesh_color != '':
mesh_color_rgba = Color(new_mesh_color).rgba
else:
new_mesh_color = None
if new_line_color:
if new_line_color != '':
line_color_rgba = Color(new_line_color).rgba
else:
new_line_color = None
mesh_colors = [[] for _ in range(0, len(self._meshes))] # Face colors mesh_colors = [[] for _ in range(0, len(self._meshes))] # Face colors
line_colors = [[] for _ in range(0, len(self._meshes))] # Line colors line_colors = [[] for _ in range(0, len(self._meshes))] # Line colors
line_pts = [[] for _ in range(0, len(self._lines))] # Vertices for line line_pts = [[] for _ in range(0, len(self._lines))] # Vertices for line
@ -335,13 +354,10 @@ class ShapeCollectionVisual(CompoundVisual):
dim_mesh_tris = (len(data['mesh_tris']) // 3) dim_mesh_tris = (len(data['mesh_tris']) // 3)
if dim_mesh_tris != 0: if dim_mesh_tris != 0:
try: try:
mesh_colors[data['layer']] += [Color(new_mesh_color).rgba] * dim_mesh_tris mesh_colors[data['layer']] += [mesh_color_rgba] * dim_mesh_tris
self.data[k]['face_color'] = new_mesh_color self.data[k]['face_color'] = new_mesh_color
new_temp = list() data['mesh_colors'] = [mesh_color_rgba for __ in range(len(data['mesh_colors']))]
for i in range(len(data['mesh_colors'])):
new_temp.append(Color(new_mesh_color).rgba)
data['mesh_colors'] = new_temp
except Exception as e: except Exception as e:
print("VisPyVisuals.ShapeCollectionVisual.update_color(). " print("VisPyVisuals.ShapeCollectionVisual.update_color(). "
"Create mesh colors --> Data error. %s" % str(e)) "Create mesh colors --> Data error. %s" % str(e))
@ -351,13 +367,10 @@ class ShapeCollectionVisual(CompoundVisual):
if dim_line_pts != 0: if dim_line_pts != 0:
try: try:
line_pts[data['layer']] += data['line_pts'] line_pts[data['layer']] += data['line_pts']
line_colors[data['layer']] += [Color(new_line_color).rgba] * dim_line_pts line_colors[data['layer']] += [line_color_rgba] * dim_line_pts
self.data[k]['color'] = new_line_color self.data[k]['color'] = new_line_color
new_temp = list() data['line_colors'] = [mesh_color_rgba for __ in range(len(data['line_colors']))]
for i in range(len(data['line_colors'])):
new_temp.append(Color(new_line_color).rgba)
data['line_colors'] = new_temp
except Exception as e: except Exception as e:
print("VisPyVisuals.ShapeCollectionVisual.update_color(). " print("VisPyVisuals.ShapeCollectionVisual.update_color(). "
"Create line colors --> Data error. %s" % str(e)) "Create line colors --> Data error. %s" % str(e))
@ -371,13 +384,10 @@ class ShapeCollectionVisual(CompoundVisual):
if new_mesh_color and new_mesh_color != '': if new_mesh_color and new_mesh_color != '':
if dim_mesh_tris != 0: if dim_mesh_tris != 0:
try: try:
mesh_colors[data['layer']] += [Color(new_mesh_color).rgba] * dim_mesh_tris mesh_colors[data['layer']] += [mesh_color_rgba] * dim_mesh_tris
self.data[k]['face_color'] = new_mesh_color self.data[k]['face_color'] = new_mesh_color
new_temp = list() data['mesh_colors'] = [mesh_color_rgba for __ in range(len(data['mesh_colors']))]
for i in range(len(data['mesh_colors'])):
new_temp.append(Color(new_mesh_color).rgba)
data['mesh_colors'] = new_temp
except Exception as e: except Exception as e:
print("VisPyVisuals.ShapeCollectionVisual.update_color(). " print("VisPyVisuals.ShapeCollectionVisual.update_color(). "
"Create mesh colors --> Data error. %s" % str(e)) "Create mesh colors --> Data error. %s" % str(e))
@ -385,13 +395,10 @@ class ShapeCollectionVisual(CompoundVisual):
if dim_line_pts != 0: if dim_line_pts != 0:
try: try:
line_pts[data['layer']] += data['line_pts'] line_pts[data['layer']] += data['line_pts']
line_colors[data['layer']] += [Color(new_line_color).rgba] * dim_line_pts line_colors[data['layer']] += [line_color_rgba] * dim_line_pts
self.data[k]['color'] = new_line_color self.data[k]['color'] = new_line_color
new_temp = list() data['line_colors'] = [mesh_color_rgba for __ in range(len(data['line_colors']))]
for i in range(len(data['line_colors'])):
new_temp.append(Color(new_line_color).rgba)
data['line_colors'] = new_temp
except Exception as e: except Exception as e:
print("VisPyVisuals.ShapeCollectionVisual.update_color(). " print("VisPyVisuals.ShapeCollectionVisual.update_color(). "
"Create line colors --> Data error. %s" % str(e)) "Create line colors --> Data error. %s" % str(e))

View File

@ -1457,4 +1457,35 @@ class Excellon(Geometry):
slot['start'] = affinity.rotate(slot['start'], angle, origin=(px, py)) slot['start'] = affinity.rotate(slot['start'], angle, origin=(px, py))
self.create_geometry() self.create_geometry()
self.app.proc_container.new_text = '' self.app.proc_container.new_text = ''
def buffer(self, distance, join):
"""
:param distance:
:param join:
:return:
"""
log.debug("flatcamParsers.ParseExcellon.Excellon.buffer()")
if distance == 0:
return
def buffer_geom(obj):
if type(obj) is list:
new_obj = []
for g in obj:
new_obj.append(buffer_geom(g))
return new_obj
else:
try:
return obj.buffer(distance, resolution=self.geo_steps_per_circle)
except AttributeError:
return obj
# buffer solid_geometry
for tool, tool_dict in list(self.tools.items()):
self.tools[tool]['solid_geometry'] = buffer_geom(tool_dict['solid_geometry'])
self.tools[tool]['C'] += distance
self.create_geometry()

View File

@ -2169,6 +2169,87 @@ class Gerber(Geometry):
_("Gerber Rotate done.")) _("Gerber Rotate done."))
self.app.proc_container.new_text = '' self.app.proc_container.new_text = ''
def buffer(self, distance, join):
"""
:param distance:
:return:
"""
log.debug("parseGerber.Gerber.buffer()")
if distance == 0:
return
# variables to display the percentage of work done
self.geo_len = 0
try:
for __ in self.solid_geometry:
self.geo_len += 1
except TypeError:
self.geo_len = 1
self.old_disp_number = 0
self.el_count = 0
def buffer_geom(obj):
if type(obj) is list:
new_obj = []
for g in obj:
new_obj.append(buffer_geom(g))
return new_obj
else:
try:
self.el_count += 1
disp_number = int(np.interp(self.el_count, [0, self.geo_len], [0, 100]))
if self.old_disp_number < disp_number <= 100:
self.app.proc_container.update_view_text(' %d%%' % disp_number)
self.old_disp_number = disp_number
return obj.buffer(distance, resolution=self.steps_per_circle, join_style=join)
except AttributeError:
return obj
self.solid_geometry = buffer_geom(self.solid_geometry)
# we need to buffer the geometry stored in the Gerber apertures, too
try:
for apid in self.apertures:
new_geometry = list()
if 'geometry' in self.apertures[apid]:
for geo_el in self.apertures[apid]['geometry']:
new_geo_el = dict()
if 'solid' in geo_el:
new_geo_el['solid'] = buffer_geom(geo_el['solid'])
if 'follow' in geo_el:
new_geo_el['follow'] = buffer_geom(geo_el['follow'])
if 'clear' in geo_el:
new_geo_el['clear'] = buffer_geom(geo_el['clear'])
new_geometry.append(new_geo_el)
self.apertures[apid]['geometry'] = deepcopy(new_geometry)
try:
if str(self.apertures[apid]['type']) == 'R' or str(self.apertures[apid]['type']) == 'O':
self.apertures[apid]['width'] += (distance * 2)
self.apertures[apid]['height'] += (distance * 2)
elif str(self.apertures[apid]['type']) == 'P':
self.apertures[apid]['diam'] += (distance * 2)
self.apertures[apid]['nVertices'] += (distance * 2)
except KeyError:
pass
try:
if self.apertures[apid]['size'] is not None:
self.apertures[apid]['size'] = float(self.apertures[apid]['size'] + (distance * 2))
except KeyError:
pass
except Exception as e:
log.debug('camlib.Gerber.buffer() Exception --> %s' % str(e))
return 'fail'
self.app.inform.emit('[success] %s' % _("Gerber Buffer done."))
self.app.proc_container.new_text = ''
def parse_gerber_number(strnumber, int_digits, frac_digits, zeros): def parse_gerber_number(strnumber, int_digits, frac_digits, zeros):
""" """

View File

@ -27,6 +27,7 @@ class ToolTransform(FlatCAMTool):
scaleName = _("Scale") scaleName = _("Scale")
flipName = _("Mirror (Flip)") flipName = _("Mirror (Flip)")
offsetName = _("Offset") offsetName = _("Offset")
bufferName = _("Buffer")
def __init__(self, app): def __init__(self, app):
FlatCAMTool.__init__(self, app) FlatCAMTool.__init__(self, app)
@ -255,11 +256,11 @@ class ToolTransform(FlatCAMTool):
grid0.addWidget(self.offy_entry, 14, 1) grid0.addWidget(self.offy_entry, 14, 1)
grid0.addWidget(self.offy_button, 14, 2) grid0.addWidget(self.offy_button, 14, 2)
grid0.addWidget(QtWidgets.QLabel('')) grid0.addWidget(QtWidgets.QLabel(''), 15, 0, 1, 3)
# ## Flip Title # ## Flip Title
flip_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.flipName) flip_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.flipName)
self.transform_lay.addWidget(flip_title_label) grid0.addWidget(flip_title_label, 16, 0, 1, 3)
self.flipx_button = FCButton() self.flipx_button = FCButton()
self.flipx_button.set_value(_("Flip on X")) self.flipx_button.set_value(_("Flip on X"))
@ -274,7 +275,7 @@ class ToolTransform(FlatCAMTool):
) )
hlay0 = QtWidgets.QHBoxLayout() hlay0 = QtWidgets.QHBoxLayout()
self.transform_lay.addLayout(hlay0) grid0.addLayout(hlay0, 17, 0, 1, 3)
hlay0.addWidget(self.flipx_button) hlay0.addWidget(self.flipx_button)
hlay0.addWidget(self.flipy_button) hlay0.addWidget(self.flipy_button)
@ -293,7 +294,7 @@ class ToolTransform(FlatCAMTool):
"Or enter the coords in format (x, y) in the\n" "Or enter the coords in format (x, y) in the\n"
"Point Entry field and click Flip on X(Y)")) "Point Entry field and click Flip on X(Y)"))
self.transform_lay.addWidget(self.flip_ref_cb) grid0.addWidget(self.flip_ref_cb, 18, 0, 1, 3)
self.flip_ref_label = QtWidgets.QLabel('%s:' % _("Ref. Point")) self.flip_ref_label = QtWidgets.QLabel('%s:' % _("Ref. Point"))
self.flip_ref_label.setToolTip( self.flip_ref_label.setToolTip(
@ -315,12 +316,60 @@ class ToolTransform(FlatCAMTool):
self.ois_flip = OptionalInputSection(self.flip_ref_cb, [self.flip_ref_entry, self.flip_ref_button], logic=True) self.ois_flip = OptionalInputSection(self.flip_ref_cb, [self.flip_ref_entry, self.flip_ref_button], logic=True)
hlay1 = QtWidgets.QHBoxLayout() hlay1 = QtWidgets.QHBoxLayout()
self.transform_lay.addLayout(hlay1) grid0.addLayout(hlay1, 19, 0, 1, 3)
hlay1.addWidget(self.flip_ref_label) hlay1.addWidget(self.flip_ref_label)
hlay1.addWidget(self.flip_ref_entry) hlay1.addWidget(self.flip_ref_entry)
self.transform_lay.addWidget(self.flip_ref_button) grid0.addWidget(self.flip_ref_button, 20, 0, 1, 3)
grid0.addWidget(QtWidgets.QLabel(''), 21, 0, 1, 3)
# ## Buffer Title
buffer_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.bufferName)
grid0.addWidget(buffer_title_label, 22, 0, 1, 3)
self.buffer_label = QtWidgets.QLabel('%s:' % _("Distance"))
self.buffer_label.setToolTip(
_("A positive value will create the effect of dilation,\n"
"while a negative value will create the effect of erosion.\n"
"Each geometry element of the object will be increased\n"
"or decreased with the 'distance'.")
)
self.buffer_entry = FCDoubleSpinner()
self.buffer_entry.set_precision(self.decimals)
self.buffer_entry.setSingleStep(0.1)
self.buffer_entry.setWrapping(True)
self.buffer_entry.set_range(-9999.9999, 9999.9999)
# self.rotate_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.buffer_button = FCButton()
self.buffer_button.set_value(_("Buffer"))
self.buffer_button.setToolTip(
_("Create the buffer effect on each geometry,\n"
"element from the selected object.")
)
self.buffer_button.setMinimumWidth(90)
grid0.addWidget(self.buffer_label, 23, 0)
grid0.addWidget(self.buffer_entry, 23, 1)
grid0.addWidget(self.buffer_button, 23, 2)
self.buffer_rounded_cb = FCCheckBox()
self.buffer_rounded_cb.setText('%s' % _("Rounded"))
self.buffer_rounded_cb.setToolTip(
_("If checked then the buffer will surround the buffered shape,\n"
"every corner will be rounded.\n"
"If not checked then the buffer will follow the exact geometry\n"
"of the buffered shape.")
)
grid0.addWidget(self.buffer_rounded_cb, 24, 0, 1, 3)
grid0.addWidget(QtWidgets.QLabel(''), 25, 0, 1, 3)
self.transform_lay.addStretch() self.transform_lay.addStretch()
# ## Signals # ## Signals
@ -334,14 +383,16 @@ class ToolTransform(FlatCAMTool):
self.flipx_button.clicked.connect(self.on_flipx) self.flipx_button.clicked.connect(self.on_flipx)
self.flipy_button.clicked.connect(self.on_flipy) self.flipy_button.clicked.connect(self.on_flipy)
self.flip_ref_button.clicked.connect(self.on_flip_add_coords) self.flip_ref_button.clicked.connect(self.on_flip_add_coords)
self.buffer_button.clicked.connect(self.on_buffer)
self.rotate_entry.returnPressed.connect(self.on_rotate) # self.rotate_entry.returnPressed.connect(self.on_rotate)
self.skewx_entry.returnPressed.connect(self.on_skewx) # self.skewx_entry.returnPressed.connect(self.on_skewx)
self.skewy_entry.returnPressed.connect(self.on_skewy) # self.skewy_entry.returnPressed.connect(self.on_skewy)
self.scalex_entry.returnPressed.connect(self.on_scalex) # self.scalex_entry.returnPressed.connect(self.on_scalex)
self.scaley_entry.returnPressed.connect(self.on_scaley) # self.scaley_entry.returnPressed.connect(self.on_scaley)
self.offx_entry.returnPressed.connect(self.on_offx) # self.offx_entry.returnPressed.connect(self.on_offx)
self.offy_entry.returnPressed.connect(self.on_offy) # self.offy_entry.returnPressed.connect(self.on_offy)
# self.buffer_entry.returnPressed.connect(self.on_buffer)
def run(self, toggle=True): def run(self, toggle=True):
self.app.report_usage("ToolTransform()") self.app.report_usage("ToolTransform()")
@ -430,6 +481,16 @@ class ToolTransform(FlatCAMTool):
else: else:
self.flip_ref_entry.set_value((0, 0)) self.flip_ref_entry.set_value((0, 0))
if self.app.defaults["tools_transform_buffer_dis"]:
self.buffer_entry.set_value(self.app.defaults["tools_transform_buffer_dis"])
else:
self.buffer_entry.set_value(0.0)
if self.app.defaults["tools_transform_buffer_corner"]:
self.buffer_rounded_cb.set_value(self.app.defaults["tools_transform_buffer_corner"])
else:
self.buffer_rounded_cb.set_value(True)
def on_rotate(self): def on_rotate(self):
value = float(self.rotate_entry.get_value()) value = float(self.rotate_entry.get_value())
if value == 0: if value == 0:
@ -511,8 +572,7 @@ class ToolTransform(FlatCAMTool):
def on_offx(self): def on_offx(self):
value = float(self.offx_entry.get_value()) value = float(self.offx_entry.get_value())
if value == 0: if value == 0:
self.app.inform.emit('[WARNING_NOTCL] %s' % self.app.inform.emit('[WARNING_NOTCL] %s' % _("Offset transformation can not be done for a value of 0."))
_("Offset transformation can not be done for a value of 0."))
return return
axis = 'X' axis = 'X'
@ -522,14 +582,20 @@ class ToolTransform(FlatCAMTool):
def on_offy(self): def on_offy(self):
value = float(self.offy_entry.get_value()) value = float(self.offy_entry.get_value())
if value == 0: if value == 0:
self.app.inform.emit('[WARNING_NOTCL] %s' % self.app.inform.emit('[WARNING_NOTCL] %s' % _("Offset transformation can not be done for a value of 0."))
_("Offset transformation can not be done for a value of 0."))
return return
axis = 'Y' axis = 'Y'
self.app.worker_task.emit({'fcn': self.on_offset, 'params': [axis, value]}) self.app.worker_task.emit({'fcn': self.on_offset, 'params': [axis, value]})
return return
def on_buffer(self):
value = self.buffer_entry.get_value()
join = 1 if self.buffer_rounded_cb.get_value() else 2
self.app.worker_task.emit({'fcn': self.on_buffer_action, 'params': [value, join]})
return
def on_rotate_action(self, num): def on_rotate_action(self, num):
obj_list = self.app.collection.get_selected() obj_list = self.app.collection.get_selected()
xminlist = [] xminlist = []
@ -808,4 +874,40 @@ class ToolTransform(FlatCAMTool):
(_("Due of"), str(e), _("action was not executed."))) (_("Due of"), str(e), _("action was not executed.")))
return return
def on_buffer_action(self, value, join):
obj_list = self.app.collection.get_selected()
if not obj_list:
self.app.inform.emit('[WARNING_NOTCL] %s' % _("No object selected. Please Select an object to buffer!"))
return
else:
with self.app.proc_container.new(_("Applying Buffer")):
try:
for sel_obj in obj_list:
if isinstance(sel_obj, FlatCAMCNCjob):
self.app.inform.emit(_("CNCJob objects can't be buffered."))
elif sel_obj.kind.lower() == 'gerber':
sel_obj.buffer(value, join)
sel_obj.source_file = self.app.export_gerber(obj_name=sel_obj.options['name'],
filename=None, local_use=sel_obj,
use_thread=False)
elif sel_obj.kind.lower() == 'excellon':
sel_obj.buffer(value, join)
sel_obj.source_file = self.app.export_excellon(obj_name=sel_obj.options['name'],
filename=None, local_use=sel_obj,
use_thread=False)
elif sel_obj.kind.lower() == 'geometry':
sel_obj.buffer(value, join)
self.app.object_changed.emit(sel_obj)
sel_obj.plot()
self.app.inform.emit('[success] %s...' % _('Buffer done'))
except Exception as e:
self.app.log.debug("ToolTransform.on_buffer_action() --> %s" % str(e))
self.app.inform.emit('[ERROR_NOTCL] %s %s, %s.' %
(_("Due of"), str(e), _("action was not executed.")))
return
# end of file # end of file