commit
ac54dd9bd7
|
@ -830,6 +830,8 @@ class App(QtCore.QObject):
|
|||
"tools_transform_offset_y": 0.0,
|
||||
"tools_transform_mirror_reference": False,
|
||||
"tools_transform_mirror_point": (0, 0),
|
||||
"tools_transform_buffer_dis": 0.0,
|
||||
"tools_transform_buffer_corner": True,
|
||||
|
||||
# SolderPaste Tool
|
||||
"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_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_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
|
||||
"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
|
||||
if self.defaults['global_selection_shape'] is True:
|
||||
self.draw_selection_shape(obj)
|
||||
obj.selection_shape_drawn = True
|
||||
self.collection.set_active(obj.options['name'])
|
||||
else:
|
||||
if poly_selection.intersects(poly_obj):
|
||||
# create the selection box around the selected object
|
||||
if self.defaults['global_selection_shape'] is True:
|
||||
self.draw_selection_shape(obj)
|
||||
obj.selection_shape_drawn = True
|
||||
self.collection.set_active(obj.options['name'])
|
||||
except Exception as e:
|
||||
# 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
|
||||
if self.defaults['global_selection_shape'] is True:
|
||||
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.delete_selection_shape()
|
||||
curr_sel_obj.selection_shape_drawn = False
|
||||
|
||||
self.collection.set_active(objects_under_the_click_list[0])
|
||||
curr_sel_obj = self.collection.get_active()
|
||||
|
||||
# create the selection box around the selected object
|
||||
if self.defaults['global_selection_shape'] is True:
|
||||
self.draw_selection_shape(curr_sel_obj)
|
||||
curr_sel_obj.selection_shape_drawn = True
|
||||
|
||||
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:
|
||||
self.on_objects_selection(False)
|
||||
self.delete_selection_shape()
|
||||
|
@ -8932,6 +8944,7 @@ class App(QtCore.QObject):
|
|||
# make active the first element of the overlapped objects list
|
||||
if self.collection.get_active() is None:
|
||||
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']
|
||||
# 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()
|
||||
# delete the possible selection box around a possible selected object
|
||||
self.delete_selection_shape()
|
||||
curr_sel_obj.selection_shape_drawn = False
|
||||
|
||||
# create the selection box around the selected object
|
||||
if self.defaults['global_selection_shape'] is True:
|
||||
self.draw_selection_shape(curr_sel_obj)
|
||||
curr_sel_obj.selection_shape_drawn = True
|
||||
|
||||
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
|
||||
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
|
||||
# only when working on App
|
||||
if self.call_source == 'app':
|
||||
|
@ -11512,26 +11531,28 @@ class App(QtCore.QObject):
|
|||
App.log.debug(" **************** Started PROEJCT loading... **************** ")
|
||||
|
||||
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" %
|
||||
(obj['kind'].capitalize(), obj['options']['name']))
|
||||
App.log.debug("Recreating from opened project an %s object: %s" %
|
||||
(obj['kind'].capitalize(), obj['options']['name']))
|
||||
|
||||
# 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)
|
||||
if cli is None:
|
||||
self.set_ui_title(name="{} {}: {}".format(_("Loading Project ... restoring"),
|
||||
obj['kind'].upper(),
|
||||
obj['options']['name']
|
||||
)
|
||||
)
|
||||
# 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)
|
||||
if cli is None:
|
||||
self.set_ui_title(name="{} {}: {}".format(_("Loading Project ... restoring"),
|
||||
obj['kind'].upper(),
|
||||
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' %
|
||||
(_("Project loaded from"), filename))
|
||||
self.inform.emit('[success] %s: %s' % (_("Project loaded from"), filename))
|
||||
|
||||
self.should_we_save = False
|
||||
self.file_opened.emit("project", filename)
|
||||
|
@ -12365,7 +12386,10 @@ class App(QtCore.QObject):
|
|||
new_color = self.defaults['global_plot_fill']
|
||||
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':
|
||||
new_color = '#FF0000' + \
|
||||
|
@ -12397,22 +12421,22 @@ class App(QtCore.QObject):
|
|||
new_color = str(plot_fill_color.name()) + \
|
||||
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)
|
||||
sel_obj.fill_color = new_color
|
||||
sel_obj.outline_color = new_line_color
|
||||
new_line_color = color_variant(new_color[:7], 0.7)
|
||||
|
||||
sel_obj.shapes.redraw(
|
||||
update_colors=(new_color, new_line_color)
|
||||
)
|
||||
else:
|
||||
new_line_color = color_variant(new_color[:7], 0.7)
|
||||
for sel_obj in sel_obj_list:
|
||||
if self.is_legacy is False:
|
||||
sel_obj.fill_color = new_color
|
||||
sel_obj.outline_color = new_line_color
|
||||
|
||||
sel_obj.fill_color = new_color
|
||||
sel_obj.outline_color = new_line_color
|
||||
sel_obj.shapes.redraw(
|
||||
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):
|
||||
if state:
|
||||
|
|
|
@ -128,6 +128,9 @@ class FlatCAMObj(QtCore.QObject):
|
|||
self.isHovering = False
|
||||
self.notHovering = True
|
||||
|
||||
# Flag to show if a selection shape is drawn
|
||||
self.selection_shape_drawn = False
|
||||
|
||||
# self.units = 'IN'
|
||||
self.units = self.app.defaults['units']
|
||||
|
||||
|
@ -596,7 +599,9 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
|||
def __init__(self, name):
|
||||
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)
|
||||
|
||||
self.kind = "gerber"
|
||||
|
@ -2196,6 +2201,10 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
|||
Gerber.skew(self, angle_x=angle_x, angle_y=angle_y, point=point)
|
||||
self.replotApertures.emit()
|
||||
|
||||
def buffer(self, distance, join):
|
||||
Gerber.buffer(self, distance=distance, join=join)
|
||||
self.replotApertures.emit()
|
||||
|
||||
def serialize(self):
|
||||
return {
|
||||
"options": self.options,
|
||||
|
@ -2214,7 +2223,9 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
|
|||
def __init__(self, name):
|
||||
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)
|
||||
|
||||
self.kind = "excellon"
|
||||
|
@ -3542,8 +3553,11 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
|
|||
|
||||
def __init__(self, name):
|
||||
self.decimals = self.app.decimals
|
||||
|
||||
self.circle_steps = int(self.app.defaults["geometry_circle_steps"])
|
||||
|
||||
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"
|
||||
|
||||
|
@ -3865,15 +3879,18 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
|
|||
if def_key == opt_key:
|
||||
self.default_data[def_key] = deepcopy(opt_val)
|
||||
|
||||
try:
|
||||
temp_tools = self.options["cnctooldia"].split(",")
|
||||
tools_list = [
|
||||
float(eval(dia)) for dia in temp_tools if dia != ''
|
||||
]
|
||||
except Exception as e:
|
||||
log.error("At least one tool diameter needed. Verify in Edit -> Preferences -> Geometry General -> "
|
||||
"Tool dia. %s" % str(e))
|
||||
return
|
||||
if type(self.options["cnctooldia"]) == float:
|
||||
tools_list = [self.options["cnctooldia"]]
|
||||
else:
|
||||
try:
|
||||
temp_tools = self.options["cnctooldia"].split(",")
|
||||
tools_list = [
|
||||
float(eval(dia)) for dia in temp_tools if dia != ''
|
||||
]
|
||||
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
|
||||
|
||||
|
|
|
@ -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
|
||||
- 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
|
||||
- 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
|
||||
|
||||
|
|
63
camlib.py
63
camlib.py
|
@ -2118,6 +2118,69 @@ class Geometry(object):
|
|||
# self.solid_geometry = affinity.skew(self.solid_geometry, angle_x, angle_y,
|
||||
# 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):
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
|
|
@ -5378,7 +5378,7 @@ class ToolsTransformPrefGroupUI(OptionsGroupUI):
|
|||
grid0.addWidget(self.skewy_label, 4, 0)
|
||||
grid0.addWidget(self.skewy_entry, 4, 1)
|
||||
|
||||
# ## Scale factor on X axis
|
||||
# ## Scale
|
||||
scale_title_lbl = QtWidgets.QLabel('<b>%s</b>' % _("Scale"))
|
||||
grid0.addWidget(scale_title_lbl, 5, 0, 1, 2)
|
||||
|
||||
|
@ -5425,7 +5425,7 @@ class ToolsTransformPrefGroupUI(OptionsGroupUI):
|
|||
)
|
||||
grid0.addWidget(self.reference_cb, 8, 1)
|
||||
|
||||
# ## Offset distance on X axis
|
||||
# ## Offset
|
||||
offset_title_lbl = QtWidgets.QLabel('<b>%s</b>' % _("Offset"))
|
||||
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_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
|
||||
self.mirror_reference_cb = FCCheckBox('%s' % _("Mirror Reference"))
|
||||
self.mirror_reference_cb.setToolTip(
|
||||
|
@ -5466,9 +5470,9 @@ class ToolsTransformPrefGroupUI(OptionsGroupUI):
|
|||
"Then click Add button to insert coordinates.\n"
|
||||
"Or enter the coords in format (x, y) in the\n"
|
||||
"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(
|
||||
_("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"
|
||||
|
@ -5476,8 +5480,42 @@ class ToolsTransformPrefGroupUI(OptionsGroupUI):
|
|||
)
|
||||
self.flip_ref_entry = EvalEntry2("(0, 0)")
|
||||
|
||||
grid0.addWidget(self.flip_ref_label, 13, 0, 1, 2)
|
||||
grid0.addWidget(self.flip_ref_entry, 14, 0, 1, 2)
|
||||
grid0.addWidget(self.flip_ref_label, 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()
|
||||
|
||||
|
|
|
@ -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']
|
||||
|
||||
if geo is not None and not geo.is_empty:
|
||||
simple = geo.simplify(tolerance) if tolerance else geo # Simplified shape
|
||||
pts = [] # Shape line points
|
||||
tri_pts = [] # Mesh vertices
|
||||
tri_tris = [] # Mesh faces
|
||||
simplified_geo = geo.simplify(tolerance) if tolerance else geo # Simplified shape
|
||||
pts = [] # Shape line points
|
||||
tri_pts = [] # Mesh vertices
|
||||
tri_tris = [] # Mesh faces
|
||||
|
||||
if type(geo) == LineString:
|
||||
# Prepare lines
|
||||
pts = _linestring_to_segments(list(simple.coords))
|
||||
pts = _linestring_to_segments(list(simplified_geo.coords))
|
||||
|
||||
elif type(geo) == LinearRing:
|
||||
# Prepare lines
|
||||
pts = _linearring_to_segments(list(simple.coords))
|
||||
pts = _linearring_to_segments(list(simplified_geo.coords))
|
||||
|
||||
elif type(geo) == Polygon:
|
||||
# Prepare polygon faces
|
||||
if face_color is not None:
|
||||
if triangulation == 'glu':
|
||||
gt = GLUTess()
|
||||
tri_tris, tri_pts = gt.triangulate(simple)
|
||||
tri_tris, tri_pts = gt.triangulate(simplified_geo)
|
||||
else:
|
||||
print("Triangulation type '%s' isn't implemented. Drawing only edges." % triangulation)
|
||||
|
||||
# Prepare polygon edges
|
||||
if color is not None:
|
||||
pts = _linearring_to_segments(list(simple.exterior.coords))
|
||||
for ints in simple.interiors:
|
||||
pts = _linearring_to_segments(list(simplified_geo.exterior.coords))
|
||||
for ints in simplified_geo.interiors:
|
||||
pts += _linearring_to_segments(list(ints.coords))
|
||||
|
||||
# Appending data for mesh
|
||||
if len(tri_pts) > 0 and len(tri_tris) > 0:
|
||||
mesh_tris += tri_tris
|
||||
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
|
||||
if len(pts) > 0:
|
||||
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
|
||||
data['line_pts'] = line_pts
|
||||
|
@ -314,12 +318,27 @@ class ShapeCollectionVisual(CompoundVisual):
|
|||
self.__update()
|
||||
|
||||
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
|
||||
|
||||
if not self.data:
|
||||
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
|
||||
line_colors = [[] for _ in range(0, len(self._meshes))] # Line colors
|
||||
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)
|
||||
if dim_mesh_tris != 0:
|
||||
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
|
||||
|
||||
new_temp = list()
|
||||
for i in range(len(data['mesh_colors'])):
|
||||
new_temp.append(Color(new_mesh_color).rgba)
|
||||
data['mesh_colors'] = new_temp
|
||||
data['mesh_colors'] = [mesh_color_rgba for __ in range(len(data['mesh_colors']))]
|
||||
except Exception as e:
|
||||
print("VisPyVisuals.ShapeCollectionVisual.update_color(). "
|
||||
"Create mesh colors --> Data error. %s" % str(e))
|
||||
|
@ -351,13 +367,10 @@ class ShapeCollectionVisual(CompoundVisual):
|
|||
if dim_line_pts != 0:
|
||||
try:
|
||||
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
|
||||
|
||||
new_temp = list()
|
||||
for i in range(len(data['line_colors'])):
|
||||
new_temp.append(Color(new_line_color).rgba)
|
||||
data['line_colors'] = new_temp
|
||||
data['line_colors'] = [mesh_color_rgba for __ in range(len(data['line_colors']))]
|
||||
except Exception as e:
|
||||
print("VisPyVisuals.ShapeCollectionVisual.update_color(). "
|
||||
"Create line colors --> Data error. %s" % str(e))
|
||||
|
@ -371,13 +384,10 @@ class ShapeCollectionVisual(CompoundVisual):
|
|||
if new_mesh_color and new_mesh_color != '':
|
||||
if dim_mesh_tris != 0:
|
||||
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
|
||||
|
||||
new_temp = list()
|
||||
for i in range(len(data['mesh_colors'])):
|
||||
new_temp.append(Color(new_mesh_color).rgba)
|
||||
data['mesh_colors'] = new_temp
|
||||
data['mesh_colors'] = [mesh_color_rgba for __ in range(len(data['mesh_colors']))]
|
||||
except Exception as e:
|
||||
print("VisPyVisuals.ShapeCollectionVisual.update_color(). "
|
||||
"Create mesh colors --> Data error. %s" % str(e))
|
||||
|
@ -385,13 +395,10 @@ class ShapeCollectionVisual(CompoundVisual):
|
|||
if dim_line_pts != 0:
|
||||
try:
|
||||
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
|
||||
|
||||
new_temp = list()
|
||||
for i in range(len(data['line_colors'])):
|
||||
new_temp.append(Color(new_line_color).rgba)
|
||||
data['line_colors'] = new_temp
|
||||
data['line_colors'] = [mesh_color_rgba for __ in range(len(data['line_colors']))]
|
||||
except Exception as e:
|
||||
print("VisPyVisuals.ShapeCollectionVisual.update_color(). "
|
||||
"Create line colors --> Data error. %s" % str(e))
|
||||
|
|
|
@ -1458,3 +1458,34 @@ class Excellon(Geometry):
|
|||
|
||||
self.create_geometry()
|
||||
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()
|
||||
|
|
|
@ -2169,6 +2169,87 @@ class Gerber(Geometry):
|
|||
_("Gerber Rotate done."))
|
||||
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):
|
||||
"""
|
||||
|
|
|
@ -27,6 +27,7 @@ class ToolTransform(FlatCAMTool):
|
|||
scaleName = _("Scale")
|
||||
flipName = _("Mirror (Flip)")
|
||||
offsetName = _("Offset")
|
||||
bufferName = _("Buffer")
|
||||
|
||||
def __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_button, 14, 2)
|
||||
|
||||
grid0.addWidget(QtWidgets.QLabel(''))
|
||||
grid0.addWidget(QtWidgets.QLabel(''), 15, 0, 1, 3)
|
||||
|
||||
# ## Flip Title
|
||||
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.set_value(_("Flip on X"))
|
||||
|
@ -274,7 +275,7 @@ class ToolTransform(FlatCAMTool):
|
|||
)
|
||||
|
||||
hlay0 = QtWidgets.QHBoxLayout()
|
||||
self.transform_lay.addLayout(hlay0)
|
||||
grid0.addLayout(hlay0, 17, 0, 1, 3)
|
||||
|
||||
hlay0.addWidget(self.flipx_button)
|
||||
hlay0.addWidget(self.flipy_button)
|
||||
|
@ -293,7 +294,7 @@ class ToolTransform(FlatCAMTool):
|
|||
"Or enter the coords in format (x, y) in the\n"
|
||||
"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.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)
|
||||
|
||||
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_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()
|
||||
|
||||
# ## Signals
|
||||
|
@ -334,14 +383,16 @@ class ToolTransform(FlatCAMTool):
|
|||
self.flipx_button.clicked.connect(self.on_flipx)
|
||||
self.flipy_button.clicked.connect(self.on_flipy)
|
||||
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.skewx_entry.returnPressed.connect(self.on_skewx)
|
||||
self.skewy_entry.returnPressed.connect(self.on_skewy)
|
||||
self.scalex_entry.returnPressed.connect(self.on_scalex)
|
||||
self.scaley_entry.returnPressed.connect(self.on_scaley)
|
||||
self.offx_entry.returnPressed.connect(self.on_offx)
|
||||
self.offy_entry.returnPressed.connect(self.on_offy)
|
||||
# self.rotate_entry.returnPressed.connect(self.on_rotate)
|
||||
# self.skewx_entry.returnPressed.connect(self.on_skewx)
|
||||
# self.skewy_entry.returnPressed.connect(self.on_skewy)
|
||||
# self.scalex_entry.returnPressed.connect(self.on_scalex)
|
||||
# self.scaley_entry.returnPressed.connect(self.on_scaley)
|
||||
# self.offx_entry.returnPressed.connect(self.on_offx)
|
||||
# self.offy_entry.returnPressed.connect(self.on_offy)
|
||||
# self.buffer_entry.returnPressed.connect(self.on_buffer)
|
||||
|
||||
def run(self, toggle=True):
|
||||
self.app.report_usage("ToolTransform()")
|
||||
|
@ -430,6 +481,16 @@ class ToolTransform(FlatCAMTool):
|
|||
else:
|
||||
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):
|
||||
value = float(self.rotate_entry.get_value())
|
||||
if value == 0:
|
||||
|
@ -511,8 +572,7 @@ class ToolTransform(FlatCAMTool):
|
|||
def on_offx(self):
|
||||
value = float(self.offx_entry.get_value())
|
||||
if value == 0:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' %
|
||||
_("Offset transformation can not be done for a value of 0."))
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Offset transformation can not be done for a value of 0."))
|
||||
return
|
||||
axis = 'X'
|
||||
|
||||
|
@ -522,14 +582,20 @@ class ToolTransform(FlatCAMTool):
|
|||
def on_offy(self):
|
||||
value = float(self.offy_entry.get_value())
|
||||
if value == 0:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' %
|
||||
_("Offset transformation can not be done for a value of 0."))
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Offset transformation can not be done for a value of 0."))
|
||||
return
|
||||
axis = 'Y'
|
||||
|
||||
self.app.worker_task.emit({'fcn': self.on_offset, 'params': [axis, value]})
|
||||
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):
|
||||
obj_list = self.app.collection.get_selected()
|
||||
xminlist = []
|
||||
|
@ -808,4 +874,40 @@ class ToolTransform(FlatCAMTool):
|
|||
(_("Due of"), str(e), _("action was not executed.")))
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue