- updated the Geometry Editor such that loading objects into Editor and updating the edited object is done now multithreaded

- most of the functions in the Geometry Editor are now multithreaded
- fixed the Subtract sub-tool in the Geometry Editor
- added an alternative Subtract sub-tool in the Geometry Editor, one that do not delete the subtracting shapes
- added some not-so-good icons for the Subtract Alt sub-tool in Geometry Editor
This commit is contained in:
Marius Stanciu
2020-11-13 03:42:27 +02:00
committed by Marius
parent 21db5d9528
commit 04de960ecb
5 changed files with 542 additions and 435 deletions

View File

@@ -7,6 +7,14 @@ CHANGELOG for FlatCAM beta
=================================================
13.11.2020
- updated the Geometry Editor such that loading objects into Editor and updating the edited object is done now multithreaded
- most of the functions in the Geometry Editor are now multithreaded
- fixed the Subtract sub-tool in the Geometry Editor
- added an alternative Subtract sub-tool in the Geometry Editor, one that do not delete the subtracting shapes
- added some not-so-good icons for the Subtract Alt sub-tool in Geometry Editor
12.11.2020
- some fixes in the app_Main class

View File

@@ -2709,6 +2709,7 @@ class FCMove(FCShapeTool):
return "Done."
def make(self):
def worker_task():
with self.draw_app.app.proc_container.new(_("Moving ...")):
# Create new geometry
dx = self.destination[0] - self.origin[0]
@@ -2725,6 +2726,8 @@ class FCMove(FCShapeTool):
except TypeError:
pass
self.draw_app.app.worker_task.emit({'fcn': worker_task, 'params': []})
def selection_bbox(self):
geo_list = []
for select_shape in self.draw_app.get_selected():
@@ -3247,6 +3250,8 @@ class AppGeoEditor(QtCore.QObject):
transform_complete = QtCore.pyqtSignal()
build_ui_sig = QtCore.pyqtSignal()
draw_shape_idx = -1
def __init__(self, app, disabled=False):
@@ -3321,8 +3326,6 @@ class AppGeoEditor(QtCore.QObject):
""")
layout.addWidget(self.exit_editor_button)
self.exit_editor_button.clicked.connect(lambda: self.app.editor2object())
# ## Toolbar events and properties
self.tools = {}
@@ -3342,8 +3345,6 @@ class AppGeoEditor(QtCore.QObject):
self.shapes = ShapeCollectionLegacy(obj=self, app=self.app, name='shapes_geo_editor')
self.tool_shape = ShapeCollectionLegacy(obj=self, app=self.app, name='tool_shapes_geo_editor')
self.app.pool_recreated.connect(self.pool_recreated)
# Remove from scene
self.shapes.enabled = False
self.tool_shape.enabled = False
@@ -3380,10 +3381,6 @@ class AppGeoEditor(QtCore.QObject):
# this will flag if the Editor "tools" are launched from key shortcuts (True) or from menu toolbar (False)
self.launched_from_shortcuts = False
self.app.ui.grid_snap_btn.triggered.connect(self.on_grid_toggled)
self.app.ui.corner_snap_btn.setCheckable(True)
self.app.ui.corner_snap_btn.triggered.connect(lambda: self.toolbar_tool_toggle("corner_snap"))
self.options = {
"global_gridx": 0.1,
"global_gridy": 0.1,
@@ -3406,14 +3403,8 @@ class AppGeoEditor(QtCore.QObject):
self.rtree_index = rtindex.Index()
self.app.ui.grid_gap_x_entry.setValidator(QtGui.QDoubleValidator())
self.app.ui.grid_gap_x_entry.textChanged.connect(self.on_gridx_val_changed)
self.app.ui.grid_gap_y_entry.setValidator(QtGui.QDoubleValidator())
self.app.ui.grid_gap_y_entry.textChanged.connect(self.on_gridy_val_changed)
self.app.ui.snap_max_dist_entry.setValidator(QtGui.QDoubleValidator())
self.app.ui.snap_max_dist_entry.textChanged.connect(
lambda: self.entry2option("snap_max", self.app.ui.snap_max_dist_entry))
# if using Paint store here the tool diameter used
self.paint_tooldia = None
@@ -3424,6 +3415,19 @@ class AppGeoEditor(QtCore.QObject):
# #############################################################################################################
# ####################### GEOMETRY Editor Signals #############################################################
# #############################################################################################################
self.build_ui_sig.connect(self.build_ui)
self.app.ui.grid_gap_x_entry.textChanged.connect(self.on_gridx_val_changed)
self.app.ui.grid_gap_y_entry.textChanged.connect(self.on_gridy_val_changed)
self.app.ui.snap_max_dist_entry.textChanged.connect(
lambda: self.entry2option("snap_max", self.app.ui.snap_max_dist_entry))
self.app.ui.grid_snap_btn.triggered.connect(self.on_grid_toggled)
self.app.ui.corner_snap_btn.setCheckable(True)
self.app.ui.corner_snap_btn.triggered.connect(lambda: self.toolbar_tool_toggle("corner_snap"))
self.app.pool_recreated.connect(self.pool_recreated)
self.exit_editor_button.clicked.connect(lambda: self.app.editor2object())
# connect the toolbar signals
self.connect_geo_toolbar_signals()
@@ -3443,12 +3447,16 @@ class AppGeoEditor(QtCore.QObject):
self.app.ui.geo_union_menuitem.triggered.connect(self.union)
self.app.ui.geo_intersection_menuitem.triggered.connect(self.intersection)
self.app.ui.geo_subtract_menuitem.triggered.connect(self.subtract)
self.app.ui.geo_subtract_alt_menuitem.triggered.connect(self.subtract_2)
self.app.ui.geo_cutpath_menuitem.triggered.connect(self.cutpath)
self.app.ui.geo_copy_menuitem.triggered.connect(lambda: self.select_tool('copy'))
self.app.ui.geo_union_btn.triggered.connect(self.union)
self.app.ui.geo_intersection_btn.triggered.connect(self.intersection)
self.app.ui.geo_subtract_btn.triggered.connect(self.subtract)
self.app.ui.geo_alt_subtract_btn.triggered.connect(self.subtract_2)
self.app.ui.geo_cutpath_btn.triggered.connect(self.cutpath)
self.app.ui.geo_delete_btn.triggered.connect(self.on_delete_btn)
@@ -3803,6 +3811,8 @@ class AppGeoEditor(QtCore.QObject):
self.app.ui.draw_union.triggered.connect(self.union)
self.app.ui.draw_intersect.triggered.connect(self.intersection)
self.app.ui.draw_substract.triggered.connect(self.subtract)
self.app.ui.draw_substract_alt.triggered.connect(self.subtract_2)
self.app.ui.draw_cut.triggered.connect(self.cutpath)
self.app.ui.draw_transform.triggered.connect(lambda: self.select_tool('transform'))
@@ -3917,6 +3927,11 @@ class AppGeoEditor(QtCore.QObject):
except (TypeError, AttributeError):
pass
try:
self.app.ui.draw_substract_alt.triggered.disconnect(self.subtract_2)
except (TypeError, AttributeError):
pass
try:
self.app.ui.draw_transform.triggered.disconnect()
except (TypeError, AttributeError):
@@ -3927,12 +3942,14 @@ class AppGeoEditor(QtCore.QObject):
except (TypeError, AttributeError):
pass
def add_shape(self, shape):
def add_shape(self, shape, build_ui=True):
"""
Adds a shape to the shape storage.
:param shape: Shape to be added.
:type shape: DrawToolShape
:param build_ui: If to trigger a build of the UI
:type build_ui: bool
:return: None
"""
@@ -3953,8 +3970,12 @@ class AppGeoEditor(QtCore.QObject):
if isinstance(shape, DrawToolUtilityShape):
self.utility.append(shape)
else:
self.storage.insert(shape) # TODO: Check performance
self.build_ui()
try:
self.storage.insert(shape)
except Exception as err:
self.app.inform_shell.emit('%s\n%s' % ( _("Error on inserting shapes into storage."), str(err)))
if build_ui is True:
self.build_ui_sig.emit() # Build UI
def delete_utility_geometry(self):
"""
@@ -4391,7 +4412,7 @@ class AppGeoEditor(QtCore.QObject):
self.storage.remove(shape)
if shape in self.selected:
self.selected.remove(shape) # TODO: Check performance
self.selected.remove(shape)
def on_move(self):
# if not self.selected:
@@ -4527,6 +4548,10 @@ class AppGeoEditor(QtCore.QObject):
pl.append(Polygon(p.coords[::-1]))
elif isinstance(p, LineString):
pl.append(LineString(p.coords[::-1]))
elif isinstance(p, MultiPolygon):
for poly in p.geoms:
pl.append(Polygon(poly.exterior.coords[::-1], poly.interiors))
try:
geom = MultiPolygon(pl)
except TypeError:
@@ -4692,12 +4717,42 @@ class AppGeoEditor(QtCore.QObject):
else:
milling_type = 1 # CW motion = conventional milling (spindle is rotating CCW)
def task_job(editor_obj):
# Link shapes into editor.
with editor_obj.app.proc_container.new(_("Working...")):
editor_obj.app.inform.emit(_("Loading the Geometry into the Editor..."))
if multigeo_tool:
self.multigeo_tool = multigeo_tool
geo_to_edit = self.flatten(geometry=fcgeometry.tools[self.multigeo_tool]['solid_geometry'],
editor_obj.multigeo_tool = multigeo_tool
geo_to_edit = editor_obj.flatten(geometry=fcgeometry.tools[self.multigeo_tool]['solid_geometry'],
orient_val=milling_type)
self.app.inform.emit(
else:
geo_to_edit = editor_obj.flatten(geometry=fcgeometry.solid_geometry, orient_val=milling_type)
for shape in geo_to_edit:
if shape is not None:
if type(shape) == Polygon:
editor_obj.add_shape(DrawToolShape(shape.exterior), build_ui=False)
for inter in shape.interiors:
editor_obj.add_shape(DrawToolShape(inter), build_ui=False)
else:
editor_obj.add_shape(DrawToolShape(shape), build_ui=False)
editor_obj.replot()
# updated units
editor_obj.units = self.app.defaults['units'].upper()
editor_obj.decimals = self.app.decimals
# start with GRID toolbar activated
if editor_obj.app.ui.grid_snap_btn.isChecked() is False:
editor_obj.app.ui.grid_snap_btn.trigger()
# trigger a build of the UI
self.build_ui_sig.emit()
if multigeo_tool:
editor_obj.app.inform.emit(
'[WARNING_NOTCL] %s: %s %s: %s' % (
_("Editing MultiGeo Geometry, tool"),
str(self.multigeo_tool),
@@ -4705,27 +4760,8 @@ class AppGeoEditor(QtCore.QObject):
str(fcgeometry.tools[self.multigeo_tool]['tooldia'])
)
)
else:
geo_to_edit = self.flatten(geometry=fcgeometry.solid_geometry, orient_val=milling_type)
for shape in geo_to_edit:
if shape is not None:
if type(shape) == Polygon:
self.add_shape(DrawToolShape(shape.exterior))
for inter in shape.interiors:
self.add_shape(DrawToolShape(inter))
else:
self.add_shape(DrawToolShape(shape))
self.replot()
# updated units
self.units = self.app.defaults['units'].upper()
self.decimals = self.app.decimals
# start with GRID toolbar activated
if self.app.ui.grid_snap_btn.isChecked() is False:
self.app.ui.grid_snap_btn.trigger()
self.app.worker_task.emit({'fcn': task_job, 'params': [self]})
def update_fcgeometry(self, fcgeometry):
"""
@@ -4735,10 +4771,16 @@ class AppGeoEditor(QtCore.QObject):
:param fcgeometry: GeometryObject
:return: None
"""
if self.multigeo_tool:
def task_job(editor_obj):
# Link shapes into editor.
with editor_obj.app.proc_container.new(_("Working...")):
editor_obj.app.inform.emit(_("Updating the Geometry object..."))
if editor_obj.multigeo_tool:
fcgeometry.tools[self.multigeo_tool]['solid_geometry'] = []
# for shape in self.shape_buffer:
for shape in self.storage.get_objects():
for shape in editor_obj.storage.get_objects():
new_geo = shape.geo
# simplify the MultiLineString
@@ -4746,11 +4788,11 @@ class AppGeoEditor(QtCore.QObject):
new_geo = linemerge(new_geo)
fcgeometry.tools[self.multigeo_tool]['solid_geometry'].append(new_geo)
self.multigeo_tool = None
editor_obj.multigeo_tool = None
fcgeometry.solid_geometry = []
# for shape in self.shape_buffer:
for shape in self.storage.get_objects():
for shape in editor_obj.storage.get_objects():
new_geo = shape.geo
# simplify the MultiLineString
@@ -4758,8 +4800,16 @@ class AppGeoEditor(QtCore.QObject):
new_geo = linemerge(new_geo)
fcgeometry.solid_geometry.append(new_geo)
bounds = fcgeometry.bounds()
fcgeometry.options['xmin'] = bounds[0]
fcgeometry.options['ymin'] = bounds[1]
fcgeometry.options['xmax'] = bounds[2]
fcgeometry.options['ymax'] = bounds[3]
self.deactivate()
self.app.worker_task.emit({'fcn': task_job, 'params': [self]})
def update_options(self, obj):
if self.paint_tooldia:
obj.options['cnctooldia'] = deepcopy(str(self.paint_tooldia))
@@ -4776,6 +4826,8 @@ class AppGeoEditor(QtCore.QObject):
:return: None.
"""
def work_task():
with self.app.proc_container.new(_("Working...")):
results = unary_union([t.geo for t in self.get_selected()])
# Delete originals.
@@ -4790,13 +4842,16 @@ class AppGeoEditor(QtCore.QObject):
self.replot()
self.app.worker_task.emit({'fcn': work_task, 'params': []})
def intersection_2(self):
"""
Makes intersection of selected polygons. Original polygons are deleted.
:return: None
"""
def work_task():
with self.app.proc_container.new(_("Working...")):
geo_shapes = self.get_selected()
try:
@@ -4823,6 +4878,8 @@ class AppGeoEditor(QtCore.QObject):
self.replot()
self.app.worker_task.emit({'fcn': work_task, 'params': []})
def intersection(self):
"""
Makes intersection of selected polygons. Original polygons are deleted.
@@ -4830,6 +4887,8 @@ class AppGeoEditor(QtCore.QObject):
:return: None
"""
def work_task():
with self.app.proc_container.new(_("Working...")):
geo_shapes = self.get_selected()
results = []
intact = []
@@ -4863,40 +4922,51 @@ class AppGeoEditor(QtCore.QObject):
self.selected = []
self.replot()
self.app.worker_task.emit({'fcn': work_task, 'params': []})
def subtract(self):
def work_task():
with self.app.proc_container.new(_("Working...")):
selected = self.get_selected()
try:
tools = selected[1:]
toolgeo = unary_union([shp.geo for shp in tools]).buffer(0.0000001)
target = selected[0].geo
target = target.buffer(0.0000001)
result = target.difference(toolgeo)
toolgeo = unary_union([deepcopy(shp.geo) for shp in tools]).buffer(0.0000001)
target = deepcopy(selected[0].geo)
result = DrawToolShape(target.difference(toolgeo))
self.add_shape(result)
for_deletion = [s for s in self.get_selected()]
for shape in for_deletion:
self.delete_shape(shape)
self.add_shape(DrawToolShape(result))
self.replot()
except Exception as e:
log.debug(str(e))
self.app.worker_task.emit({'fcn': work_task, 'params': []})
def subtract_2(self):
def work_task():
with self.app.proc_container.new(_("Working...")):
selected = self.get_selected()
try:
tools = selected[1:]
toolgeo = unary_union([shp.geo for shp in tools])
result = selected[0].geo.difference(toolgeo)
toolgeo = unary_union([shp.geo for shp in tools]).buffer(0.0000001)
target = deepcopy(selected[0].geo)
result = DrawToolShape(target.difference(toolgeo))
self.add_shape(result)
self.delete_shape(selected[0])
self.add_shape(DrawToolShape(result))
self.replot()
except Exception as e:
log.debug(str(e))
self.app.worker_task.emit({'fcn': work_task, 'params': []})
def cutpath(self):
def work_task():
with self.app.proc_container.new(_("Working...")):
selected = self.get_selected()
tools = selected[1:]
toolgeo = unary_union([shp.geo for shp in tools])
@@ -4920,7 +4990,11 @@ class AppGeoEditor(QtCore.QObject):
self.delete_shape(target)
self.replot()
self.app.worker_task.emit({'fcn': work_task, 'params': []})
def buffer(self, buf_distance, join_style):
def work_task():
with self.app.proc_container.new(_("Working...")):
selected = self.get_selected()
if buf_distance < 0:
@@ -4972,10 +5046,13 @@ class AppGeoEditor(QtCore.QObject):
self.add_shape(DrawToolShape(sha))
self.replot()
self.app.inform.emit('[success] %s' %
_("Full buffer geometry created."))
self.app.inform.emit('[success] %s' % _("Full buffer geometry created."))
self.app.worker_task.emit({'fcn': work_task, 'params': []})
def buffer_int(self, buf_distance, join_style):
def work_task():
with self.app.proc_container.new(_("Working...")):
selected = self.get_selected()
if buf_distance < 0:
@@ -5022,7 +5099,11 @@ class AppGeoEditor(QtCore.QObject):
self.replot()
self.app.inform.emit('[success] %s' % _("Interior buffer geometry created."))
self.app.worker_task.emit({'fcn': work_task, 'params': []})
def buffer_ext(self, buf_distance, join_style):
def work_task():
with self.app.proc_container.new(_("Working...")):
selected = self.get_selected()
if buf_distance < 0:
@@ -5071,8 +5152,11 @@ class AppGeoEditor(QtCore.QObject):
self.replot()
self.app.inform.emit('[success] %s' % _("Exterior buffer geometry created."))
def paint(self, tooldia, overlap, margin, connect, contour, method):
self.app.worker_task.emit({'fcn': work_task, 'params': []})
def paint(self, tooldia, overlap, margin, connect, contour, method):
def work_task():
with self.app.proc_container.new(_("Working...")):
if overlap >= 100:
self.app.inform.emit('[ERROR_NOTCL] %s' %
_("Could not do Paint. Overlap value has to be less than 100%%."))
@@ -5130,15 +5214,18 @@ class AppGeoEditor(QtCore.QObject):
poly_buf = Polygon(geo_obj).buffer(-margin)
if method == _("Seed"):
cp = Geometry.clear_polygon2(self, polygon_to_clear=poly_buf, tooldia=tooldia,
cp = Geometry.clear_polygon2(
self, polygon_to_clear=poly_buf, tooldia=tooldia,
steps_per_circle=self.app.defaults["geometry_circle_steps"],
overlap=overlap, contour=contour, connect=connect)
elif method == _("Lines"):
cp = Geometry.clear_polygon3(self, polygon=poly_buf, tooldia=tooldia,
cp = Geometry.clear_polygon3(
self, polygon=poly_buf, tooldia=tooldia,
steps_per_circle=self.app.defaults["geometry_circle_steps"],
overlap=overlap, contour=contour, connect=connect)
else:
cp = Geometry.clear_polygon(self, polygon=poly_buf, tooldia=tooldia,
cp = Geometry.clear_polygon(
self, polygon=poly_buf, tooldia=tooldia,
steps_per_circle=self.app.defaults["geometry_circle_steps"],
overlap=overlap, contour=contour, connect=connect)
@@ -5161,6 +5248,8 @@ class AppGeoEditor(QtCore.QObject):
self.app.inform.emit('[success] %s' % _("Done."))
self.replot()
self.app.worker_task.emit({'fcn': work_task, 'params': []})
def flatten(self, geometry, orient_val=1, reset=True, pathonly=False):
"""
Creates a list of non-iterable linear geometry objects.

View File

@@ -665,6 +665,10 @@ class MainGUI(QtWidgets.QMainWindow):
QtGui.QIcon(self.app.resource_location + '/subtract16.png'),
'%s\t%s' % (_('Polygon Subtraction'), _('S'))
)
self.geo_subtract_alt_menuitem = self.geo_editor_menu.addAction(
QtGui.QIcon(self.app.resource_location + '/subtract16.png'),
'%s\t%s' % (_('Alt Subtraction'), _(''))
)
self.geo_editor_menu.addSeparator()
self.geo_cutpath_menuitem = self.geo_editor_menu.addAction(
QtGui.QIcon(self.app.resource_location + '/cutpath16.png'),
@@ -1156,6 +1160,8 @@ class MainGUI(QtWidgets.QMainWindow):
QtGui.QIcon(self.app.resource_location + '/intersection32.png'), _('Polygon Intersection'))
self.geo_subtract_btn = self.geo_edit_toolbar.addAction(
QtGui.QIcon(self.app.resource_location + '/subtract32.png'), _('Polygon Subtraction'))
self.geo_alt_subtract_btn = self.geo_edit_toolbar.addAction(
QtGui.QIcon(self.app.resource_location + '/subtract_alt32.png'), _('Alt Subtraction'))
self.geo_edit_toolbar.addSeparator()
self.geo_cutpath_btn = self.geo_edit_toolbar.addAction(
@@ -1631,6 +1637,8 @@ class MainGUI(QtWidgets.QMainWindow):
QtGui.QIcon(self.app.resource_location + '/intersection32.png'), _("Intersection"))
self.draw_substract = self.g_editor_cmenu.addAction(
QtGui.QIcon(self.app.resource_location + '/subtract32.png'), _("Subtraction"))
self.draw_substract_alt = self.g_editor_cmenu.addAction(
QtGui.QIcon(self.app.resource_location + '/subtract_alt32.png'), _("Alt Subtraction"))
self.draw_cut = self.g_editor_cmenu.addAction(
QtGui.QIcon(self.app.resource_location + '/cutpath32.png'), _("Cut"))
self.draw_transform = self.g_editor_cmenu.addAction(
@@ -2327,6 +2335,8 @@ class MainGUI(QtWidgets.QMainWindow):
QtGui.QIcon(self.app.resource_location + '/intersection32.png'), _('Polygon Intersection'))
self.geo_subtract_btn = self.geo_edit_toolbar.addAction(
QtGui.QIcon(self.app.resource_location + '/subtract32.png'), _('Polygon Subtraction'))
self.geo_alt_subtract_btn = self.geo_edit_toolbar.addAction(
QtGui.QIcon(self.app.resource_location + '/subtract_alt32.png'), _('Alt Subtraction'))
self.geo_edit_toolbar.addSeparator()
self.geo_cutpath_btn = self.geo_edit_toolbar.addAction(

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 604 B