- wip in adding Exclusion areas in Geometry object; each Geometry object has now a storage for shapes (exclusion shapes, should I make them more general?)

This commit is contained in:
Marius Stanciu 2020-05-06 02:10:18 +03:00 committed by Marius
parent ed105eecad
commit 91493b2fbc
6 changed files with 152 additions and 40 deletions

View File

@ -7,6 +7,10 @@ CHANGELOG for FlatCAM beta
================================================= =================================================
6.05.2020
- wip in adding Exclusion areas in Geometry object; each Geometry object has now a storage for shapes (exclusion shapes, should I make them more general?)
5.05.2020 5.05.2020
- fixed an issue that made the preprocessors comboxes in Preferences not to load and display the saved value fro the file - fixed an issue that made the preprocessors comboxes in Preferences not to load and display the saved value fro the file

View File

@ -5119,12 +5119,17 @@ class App(QtCore.QObject):
for obj_active in self.collection.get_selected(): for obj_active in self.collection.get_selected():
# if the deleted object is GerberObject then make sure to delete the possible mark shapes # if the deleted object is GerberObject then make sure to delete the possible mark shapes
if isinstance(obj_active, GerberObject): if obj_active.kind == 'gerber':
for el in obj_active.mark_shapes: for el in obj_active.mark_shapes:
obj_active.mark_shapes[el].clear(update=True) obj_active.mark_shapes[el].clear(update=True)
obj_active.mark_shapes[el].enabled = False obj_active.mark_shapes[el].enabled = False
# obj_active.mark_shapes[el] = None # obj_active.mark_shapes[el] = None
del el del el
# if the deleted object is GerberObject then make sure to delete the possible mark shapes
if obj_active.kind == 'geometry':
obj_active.exclusion_shapes.clear(update=True)
obj_active.exclusion_shapes.enabled = False
del obj_active.exclusion_shapes
elif isinstance(obj_active, CNCJobObject): elif isinstance(obj_active, CNCJobObject):
try: try:
obj_active.text_col.enabled = False obj_active.text_col.enabled = False

View File

@ -110,6 +110,11 @@ class FlatCAMTool(QtWidgets.QWidget):
:return: :return:
""" """
if 'shapes_storage' in kwargs:
s_storage = kwargs['shapes_storage']
else:
s_storage = self.app.tool_shapes
if 'color' in kwargs: if 'color' in kwargs:
color = kwargs['color'] color = kwargs['color']
else: else:
@ -139,10 +144,9 @@ class FlatCAMTool(QtWidgets.QWidget):
color_t = face_color[:-2] + str(hex(int(face_alpha * 255)))[2:] color_t = face_color[:-2] + str(hex(int(face_alpha * 255)))[2:]
self.app.tool_shapes.add(sel_rect, color=color, face_color=color_t, update=True, s_storage.add(sel_rect, color=color, face_color=color_t, update=True, layer=0, tolerance=None)
layer=0, tolerance=None)
if self.app.is_legacy is True: if self.app.is_legacy is True:
self.app.tool_shapes.redraw() s_storage.redraw()
def draw_selection_shape_polygon(self, points, **kwargs): def draw_selection_shape_polygon(self, points, **kwargs):
""" """
@ -151,6 +155,12 @@ class FlatCAMTool(QtWidgets.QWidget):
:param kwargs: :param kwargs:
:return: :return:
""" """
if 'shapes_storage' in kwargs:
s_storage = kwargs['shapes_storage']
else:
s_storage = self.app.tool_shapes
if 'color' in kwargs: if 'color' in kwargs:
color = kwargs['color'] color = kwargs['color']
else: else:
@ -165,6 +175,7 @@ class FlatCAMTool(QtWidgets.QWidget):
face_alpha = kwargs['face_alpha'] face_alpha = kwargs['face_alpha']
else: else:
face_alpha = 0.3 face_alpha = 0.3
if len(points) < 3: if len(points) < 3:
sel_rect = LineString(points) sel_rect = LineString(points)
else: else:
@ -175,14 +186,24 @@ class FlatCAMTool(QtWidgets.QWidget):
color_t = face_color[:-2] + str(hex(int(face_alpha * 255)))[2:] color_t = face_color[:-2] + str(hex(int(face_alpha * 255)))[2:]
self.app.tool_shapes.add(sel_rect, color=color, face_color=color_t, update=True, s_storage.add(sel_rect, color=color, face_color=color_t, update=True, layer=0, tolerance=None)
layer=0, tolerance=None)
if self.app.is_legacy is True: if self.app.is_legacy is True:
self.app.tool_shapes.redraw() s_storage.redraw()
def delete_tool_selection_shape(self): def delete_tool_selection_shape(self, **kwargs):
self.app.tool_shapes.clear() """
self.app.tool_shapes.redraw()
:param kwargs:
:return:
"""
if 'shapes_storage' in kwargs:
s_storage = kwargs['shapes_storage']
else:
s_storage = self.app.tool_shapes
s_storage.clear()
s_storage.redraw()
def draw_moving_selection_shape_poly(self, points, data, **kwargs): def draw_moving_selection_shape_poly(self, points, data, **kwargs):
""" """
@ -192,6 +213,12 @@ class FlatCAMTool(QtWidgets.QWidget):
:param kwargs: :param kwargs:
:return: :return:
""" """
if 'shapes_storage' in kwargs:
s_storage = kwargs['shapes_storage']
else:
s_storage = self.app.move_tool.sel_shapes
if 'color' in kwargs: if 'color' in kwargs:
color = kwargs['color'] color = kwargs['color']
else: else:
@ -226,18 +253,27 @@ class FlatCAMTool(QtWidgets.QWidget):
color_t_error = "#00000000" color_t_error = "#00000000"
if geo.is_valid and not geo.is_empty: if geo.is_valid and not geo.is_empty:
self.app.move_tool.sel_shapes.add(geo, color=color, face_color=color_t, update=True, s_storage.add(geo, color=color, face_color=color_t, update=True, layer=0, tolerance=None)
layer=0, tolerance=None)
elif not geo.is_valid: elif not geo.is_valid:
self.app.move_tool.sel_shapes.add(geo, color="red", face_color=color_t_error, update=True, s_storage.add(geo, color="red", face_color=color_t_error, update=True, layer=0, tolerance=None)
layer=0, tolerance=None)
if self.app.is_legacy is True: if self.app.is_legacy is True:
self.app.move_tool.sel_shapes.redraw() s_storage.redraw()
def delete_moving_selection_shape(self): def delete_moving_selection_shape(self, **kwargs):
self.app.move_tool.sel_shapes.clear() """
self.app.move_tool.sel_shapes.redraw()
:param kwargs:
:return:
"""
if 'shapes_storage' in kwargs:
s_storage = kwargs['shapes_storage']
else:
s_storage = self.app.move_tool.sel_shapes
s_storage.clear()
s_storage.redraw()
def confirmation_message(self, accepted, minval, maxval): def confirmation_message(self, accepted, minval, maxval):
if accepted is False: if accepted is False:

View File

@ -948,10 +948,10 @@ class ShapeCollectionLegacy:
""" """
:param obj: This is the object to which the shapes collection is attached and for :param obj: This is the object to which the shapes collection is attached and for
which it will have to draw shapes which it will have to draw shapes
:param app: This is the FLatCAM.App usually, needed because we have to access attributes there :param app: This is the FLatCAM.App usually, needed because we have to access attributes there
:param name: This is the name given to the Matplotlib axes; it needs to be unique due of :param name: This is the name given to the Matplotlib axes; it needs to be unique due of
Matplotlib requurements Matplotlib requurements
:param annotation_job: Make this True if the job needed is just for annotation :param annotation_job: Make this True if the job needed is just for annotation
:param linewidth: THe width of the line (outline where is the case) :param linewidth: THe width of the line (outline where is the case)
""" """

View File

@ -16,6 +16,7 @@ import shapely.affinity as affinity
from camlib import Geometry from camlib import Geometry
from flatcamObjects.FlatCAMObj import * from flatcamObjects.FlatCAMObj import *
from flatcamGUI.VisPyVisuals import ShapeCollection
import FlatCAMTool import FlatCAMTool
import ezdxf import ezdxf
@ -163,6 +164,18 @@ class GeometryObject(FlatCAMObj, Geometry):
self.points = [] self.points = []
self.poly_drawn = False self.poly_drawn = False
# Storage for shapes, storage that can be used by FlatCAm tools for utility geometry
# VisPy visuals
if self.app.is_legacy is False:
try:
self.exclusion_shapes = ShapeCollection(parent=self.app.plotcanvas.view.scene, layers=1)
except AttributeError:
self.exclusion_shapes = None
else:
from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy
self.exclusion_shapes = ShapeCollectionLegacy(
obj=self, app=self.app, name="exclusion" + self.options['name'])
# Attributes to be included in serialization # Attributes to be included in serialization
# Always append to it because it carries contents # Always append to it because it carries contents
# from predecessors. # from predecessors.
@ -363,7 +376,7 @@ class GeometryObject(FlatCAMObj, Geometry):
"endxy": self.ui.endxy_entry, "endxy": self.ui.endxy_entry,
"cnctooldia": self.ui.addtool_entry, "cnctooldia": self.ui.addtool_entry,
"area_exclusion": self.ui.exclusion_cb, "area_exclusion": self.ui.exclusion_cb,
"area_shape":self.ui.area_shape_radio, "area_shape": self.ui.area_shape_radio,
"area_strategy": self.ui.strategy_radio, "area_strategy": self.ui.strategy_radio,
"area_overz": self.ui.over_z_entry, "area_overz": self.ui.over_z_entry,
}) })
@ -1149,8 +1162,7 @@ class GeometryObject(FlatCAMObj, Geometry):
"- 'V-tip Angle' -> angle at the tip of the tool\n" "- 'V-tip Angle' -> angle at the tip of the tool\n"
"- 'V-tip Dia' -> diameter at the tip of the tool \n" "- 'V-tip Dia' -> diameter at the tip of the tool \n"
"- Tool Dia -> 'Dia' column found in the Tool Table\n" "- Tool Dia -> 'Dia' column found in the Tool Table\n"
"NB: a value of zero means that Tool Dia = 'V-tip Dia'" "NB: a value of zero means that Tool Dia = 'V-tip Dia'")
)
) )
self.ui.cutz_entry.setToolTip( self.ui.cutz_entry.setToolTip(
_("Disabled because the tool is V-shape.\n" _("Disabled because the tool is V-shape.\n"
@ -1159,8 +1171,7 @@ class GeometryObject(FlatCAMObj, Geometry):
"- 'V-tip Angle' -> angle at the tip of the tool\n" "- 'V-tip Angle' -> angle at the tip of the tool\n"
"- 'V-tip Dia' -> diameter at the tip of the tool \n" "- 'V-tip Dia' -> diameter at the tip of the tool \n"
"- Tool Dia -> 'Dia' column found in the Tool Table\n" "- Tool Dia -> 'Dia' column found in the Tool Table\n"
"NB: a value of zero means that Tool Dia = 'V-tip Dia'" "NB: a value of zero means that Tool Dia = 'V-tip Dia'")
)
) )
self.update_cutz() self.update_cutz()
@ -1172,8 +1183,7 @@ class GeometryObject(FlatCAMObj, Geometry):
self.ui.cutz_entry.setDisabled(False) self.ui.cutz_entry.setDisabled(False)
self.ui.cutzlabel.setToolTip( self.ui.cutzlabel.setToolTip(
_("Cutting depth (negative)\n" _("Cutting depth (negative)\n"
"below the copper surface." "below the copper surface.")
)
) )
self.ui.cutz_entry.setToolTip('') self.ui.cutz_entry.setToolTip('')
@ -2636,7 +2646,11 @@ class GeometryObject(FlatCAMObj, Geometry):
self.exclusion_areas_list.append(new_rectangle) self.exclusion_areas_list.append(new_rectangle)
# add a temporary shape on canvas # add a temporary shape on canvas
FlatCAMTool.FlatCAMTool.draw_tool_selection_shape(self, old_coords=(x0, y0), coords=(x1, y1)) FlatCAMTool.FlatCAMTool.draw_tool_selection_shape(
self, old_coords=(x0, y0), coords=(x1, y1),
color="#FF7400",
face_color="#FF7400BF",
shapes_storage=self.exclusion_shapes)
self.first_click = False self.first_click = False
return return
@ -2672,7 +2686,11 @@ class GeometryObject(FlatCAMObj, Geometry):
# do not add invalid polygons even if they are drawn by utility geometry # do not add invalid polygons even if they are drawn by utility geometry
if pol.is_valid: if pol.is_valid:
self.exclusion_areas_list.append(pol) self.exclusion_areas_list.append(pol)
FlatCAMTool.FlatCAMTool.draw_selection_shape_polygon(self, points=self.points) FlatCAMTool.FlatCAMTool.draw_selection_shape_polygon(
self, points=self.points,
color="#FF7400",
face_color="#FF7400BF",
shapes_storage=self.exclusion_shapes)
self.app.inform.emit( self.app.inform.emit(
_("Zone added. Click to start adding next zone or right click to finish.")) _("Zone added. Click to start adding next zone or right click to finish."))
@ -2680,7 +2698,7 @@ class GeometryObject(FlatCAMObj, Geometry):
self.poly_drawn = False self.poly_drawn = False
return return
FlatCAMTool.FlatCAMTool.delete_tool_selection_shape(self) # FlatCAMTool.FlatCAMTool.delete_tool_selection_shape(self, shapes_storage=self.exclusion_shapes)
if self.app.is_legacy is False: if self.app.is_legacy is False:
self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_release) self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_release)
@ -2702,6 +2720,29 @@ class GeometryObject(FlatCAMObj, Geometry):
if len(self.exclusion_areas_list) == 0: if len(self.exclusion_areas_list) == 0:
return return
else:
self.exclusion_areas_list = MultiPolygon(self.exclusion_areas_list)
self.app.inform.emit(
"[success] %s" % _("Exclusion areas added. Checking overlap with the object geometry ..."))
if self.exclusion_areas_list.intersects(MultiPolygon(self.solid_geometry)):
self.exclusion_areas_list = []
self.app.inform.emit(
"[ERROR_NOTCL] %s" % _("Failed. Exclusion areas intersects the object geometry ..."))
return
else:
self.app.inform.emit(
"[success] %s" % _("Exclusion areas added."))
self.ui.generate_cnc_button.setStyleSheet("""
QPushButton
{
font-weight: bold;
color: orange;
}
""")
self.ui.generate_cnc_button.setToolTip(
'%s %s' % (_("Generate the CNC Job object."), _("With Exclusion areas."))
)
def area_disconnect(self): def area_disconnect(self):
if self.app.is_legacy is False: if self.app.is_legacy is False:
@ -2723,7 +2764,7 @@ class GeometryObject(FlatCAMObj, Geometry):
self.exclusion_areas_list = [] self.exclusion_areas_list = []
FlatCAMTool.FlatCAMTool.delete_moving_selection_shape(self) FlatCAMTool.FlatCAMTool.delete_moving_selection_shape(self)
FlatCAMTool.FlatCAMTool.delete_tool_selection_shape(self) # FlatCAMTool.FlatCAMTool.delete_tool_selection_shape(self, shapes_storage=self.exclusion_shapes)
self.app.call_source = "app" self.app.call_source = "app"
self.app.inform.emit("[WARNING_NOTCL] %s" % _("Cancelled. Area exclusion drawing was interrupted.")) self.app.inform.emit("[WARNING_NOTCL] %s" % _("Cancelled. Area exclusion drawing was interrupted."))
@ -2775,16 +2816,33 @@ class GeometryObject(FlatCAMObj, Geometry):
if self.first_click: if self.first_click:
self.app.delete_selection_shape() self.app.delete_selection_shape()
self.app.draw_moving_selection_shape(old_coords=(self.cursor_pos[0], self.cursor_pos[1]), self.app.draw_moving_selection_shape(old_coords=(self.cursor_pos[0], self.cursor_pos[1]),
color="#FF7400",
face_color="#FF7400BF",
coords=(curr_pos[0], curr_pos[1])) coords=(curr_pos[0], curr_pos[1]))
else: else:
FlatCAMTool.FlatCAMTool.delete_moving_selection_shape(self) FlatCAMTool.FlatCAMTool.delete_moving_selection_shape(self)
FlatCAMTool.FlatCAMTool.draw_moving_selection_shape_poly( FlatCAMTool.FlatCAMTool.draw_moving_selection_shape_poly(
self, points=self.points, data=(curr_pos[0], curr_pos[1])) self, points=self.points,
color="#FF7400",
face_color="#FF7400BF",
data=(curr_pos[0], curr_pos[1]))
def on_clear_area_click(self): def on_clear_area_click(self):
self.exclusion_areas_list = [] self.exclusion_areas_list = []
FlatCAMTool.FlatCAMTool.delete_moving_selection_shape(self) FlatCAMTool.FlatCAMTool.delete_moving_selection_shape(self)
self.app.delete_selection_shape() self.app.delete_selection_shape()
FlatCAMTool.FlatCAMTool.delete_tool_selection_shape(self, shapes_storage=self.exclusion_shapes)
# restore the default StyleSheet
self.ui.generate_cnc_button.setStyleSheet("")
# update the StyleSheet
self.ui.generate_cnc_button.setStyleSheet("""
QPushButton
{
font-weight: bold;
}
""")
self.ui.generate_cnc_button.setToolTip('%s' % _("Generate the CNC Job object."))
@staticmethod @staticmethod
def merge(geo_list, geo_final, multigeo=None): def merge(geo_list, geo_final, multigeo=None):

View File

@ -2066,6 +2066,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
# unfortunately for this function to work time efficient, # unfortunately for this function to work time efficient,
# if the Gerber was loaded without buffering then it require the buffering now. # if the Gerber was loaded without buffering then it require the buffering now.
# TODO 'buffering status' should be a property of the object not the project property
if self.app.defaults['gerber_buffering'] == 'no': if self.app.defaults['gerber_buffering'] == 'no':
self.solid_geometry = ncc_obj.solid_geometry.buffer(0) self.solid_geometry = ncc_obj.solid_geometry.buffer(0)
else: else:
@ -2158,6 +2159,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
self.app.inform.emit('[WARNING_NOTCL] %s ...' % _("Buffering")) self.app.inform.emit('[WARNING_NOTCL] %s ...' % _("Buffering"))
sol_geo = sol_geo.buffer(distance=ncc_offset) sol_geo = sol_geo.buffer(distance=ncc_offset)
self.app.inform.emit('[success] %s ...' % _("Buffering finished")) self.app.inform.emit('[success] %s ...' % _("Buffering finished"))
empty = self.get_ncc_empty_area(target=sol_geo, boundary=bounding_box) empty = self.get_ncc_empty_area(target=sol_geo, boundary=bounding_box)
if empty == 'fail': if empty == 'fail':
return 'fail' return 'fail'
@ -2203,14 +2205,15 @@ class NonCopperClear(FlatCAMTool, Gerber):
""" """
Clear the excess copper from the entire object. Clear the excess copper from the entire object.
:param ncc_obj: ncc cleared object :param ncc_obj: ncc cleared object
:param sel_obj: :param sel_obj:
:param ncctooldia: a tuple or single element made out of diameters of the tools to be used to ncc clear :param ncctooldia: a tuple or single element made out of diameters of the tools to be used to ncc clear
:param isotooldia: a tuple or single element made out of diameters of the tools to be used for isolation :param isotooldia: a tuple or single element made out of diameters of the tools to be used for isolation
:param outname: name of the resulting object :param outname: name of the resulting object
:param order: :param order: Tools order
:param tools_storage: whether to use the current tools_storage self.ncc_tools or a different one. :param tools_storage: whether to use the current tools_storage self.ncc_tools or a different one.
Usage of the different one is related to when this function is called from a TcL command. Usage of the different one is related to when this function is called
from a TcL command.
:param run_threaded: If True the method will be run in a threaded way suitable for GUI usage; if False it will :param run_threaded: If True the method will be run in a threaded way suitable for GUI usage; if False it will
run non-threaded for TclShell usage run non-threaded for TclShell usage
@ -3870,6 +3873,11 @@ class NonCopperClear(FlatCAMTool, Gerber):
Returns the complement of target geometry within Returns the complement of target geometry within
the given boundary polygon. If not specified, it defaults to the given boundary polygon. If not specified, it defaults to
the rectangular bounding box of target geometry. the rectangular bounding box of target geometry.
:param target: The geometry that is to be 'inverted'
:param boundary: A polygon that surrounds the entire solid geometry and from which we subtract in order to
create a "negative" geometry (geometry to be emptied of copper)
:return:
""" """
if isinstance(target, Polygon): if isinstance(target, Polygon):
geo_len = 1 geo_len = 1
@ -3882,6 +3890,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
boundary = target.envelope boundary = target.envelope
else: else:
boundary = boundary boundary = boundary
try: try:
ret_val = boundary.difference(target) ret_val = boundary.difference(target)
except Exception: except Exception:
@ -3889,10 +3898,10 @@ class NonCopperClear(FlatCAMTool, Gerber):
for el in target: for el in target:
# provide the app with a way to process the GUI events when in a blocking loop # provide the app with a way to process the GUI events when in a blocking loop
QtWidgets.QApplication.processEvents() QtWidgets.QApplication.processEvents()
if self.app.abort_flag: if self.app.abort_flag:
# graceful abort requested by the user # graceful abort requested by the user
raise grace raise grace
boundary = boundary.difference(el) boundary = boundary.difference(el)
pol_nr += 1 pol_nr += 1
disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100])) disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))