From 2e6ad354854f6ad415ebe0cbc3c3fb7b18d55c95 Mon Sep 17 00:00:00 2001 From: Joe Marshall Date: Wed, 17 Jan 2024 11:50:39 +0000 Subject: [PATCH] make UI respond immediately to operation validity etc. --- scripts/addons/cam/__init__.py | 54 ++++++++++++++-------- scripts/addons/cam/chunk.py | 5 +- scripts/addons/cam/ops.py | 36 +++++++++++++-- scripts/addons/cam/strategy.py | 12 +++-- scripts/addons/cam/ui_panels/chains.py | 18 +++++--- scripts/addons/cam/ui_panels/info.py | 8 ++-- scripts/addons/cam/ui_panels/operations.py | 8 ++-- scripts/addons/cam/utils.py | 2 + 8 files changed, 99 insertions(+), 44 deletions(-) diff --git a/scripts/addons/cam/__init__.py b/scripts/addons/cam/__init__.py index 3578f6519..69da59283 100644 --- a/scripts/addons/cam/__init__.py +++ b/scripts/addons/cam/__init__.py @@ -88,6 +88,7 @@ def updateMaterial(self, context): def updateOperation(self, context): scene = context.scene ao = scene.cam_operations[scene.cam_active_operation] + operationValid(self, context) if ao.hide_all_others: for _ao in scene.cam_operations: @@ -350,41 +351,56 @@ class import_settings(bpy.types.PropertyGroup): max_segment_size: FloatProperty(name="", description="Only Segments bigger then this value get subdivided", default=0.001, min=0.0001, max=1.0, unit="LENGTH") - -def operationValid(self, context): - o = self - o.changed = True - o.valid = True - invalidmsg = "Operation has no valid data input\n" - o.info.warnings = "" - o = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] +def isValid(o,context): + valid=True if o.geometry_source == 'OBJECT': if o.object_name not in bpy.data.objects: - o.valid = False - o.info.warnings = invalidmsg + valid = False if o.geometry_source == 'COLLECTION': if o.collection_name not in bpy.data.collections: - o.valid = False - o.info.warnings = invalidmsg + valid = False elif len(bpy.data.collections[o.collection_name].objects) == 0: - o.valid = False - o.info.warnings = invalidmsg + valid = False if o.geometry_source == 'IMAGE': if o.source_image_name not in bpy.data.images: - o.valid = False - o.info.warnings = invalidmsg + valid = False + return valid +def operationValid(self, context): + scene=context.scene + o = scene.cam_operations[scene.cam_active_operation] + o.changed = True + o.valid = isValid(o,context) + invalidmsg = "Invalid source object for operation.\n" + if o.valid: + o.info.warnings = "" + else: + o.info.warnings = invalidmsg + + if o.geometry_source == 'IMAGE': o.optimisation.use_exact = False o.update_offsetimage_tag = True o.update_zbufferimage_tag = True print('validity ') +def isChainValid(chain,context): + s = context.scene + if len(chain.operations)==0: + return (False,"") + for cho in chain.operations: + found_op = None + for so in s.cam_operations: + if so.name == cho.name: + found_op= so + if found_op == None: + return (False,f"Couldn't find operation {cho.name}") + if cam.isValid(found_op,context) is False: + return (False,f"Operation {found_op.name} is not valid") + return (True,"") -# print(o.valid) def updateOperationValid(self, context): - operationValid(self, context) updateOperation(self, context) @@ -868,7 +884,7 @@ class camOperation(bpy.types.PropertyGroup): update=updateRest) # feeds - feedrate: FloatProperty(name="Feedrate", description="Feedrate", min=0.00005, max=50.0, default=1.0, + feedrate: FloatProperty(name="Feedrate", description="Feedrate in units per minute", min=0.00005, max=50.0, default=1.0, precision=cam.constants.PRECISION, unit="LENGTH", update=updateChipload) plunge_feedrate: FloatProperty(name="Plunge speed ", description="% of feedrate", min=0.1, max=100.0, default=50.0, precision=1, subtype='PERCENTAGE', update=updateRest) diff --git a/scripts/addons/cam/chunk.py b/scripts/addons/cam/chunk.py index a59cfb8b5..18710afdb 100644 --- a/scripts/addons/cam/chunk.py +++ b/scripts/addons/cam/chunk.py @@ -25,6 +25,7 @@ from shapely import geometry as sgeometry from cam import polygon_utils_cam from cam.simple import * +from cam.exception import CamException import math @@ -970,7 +971,6 @@ def restoreVisibility(o, storage): def meshFromCurve(o, use_modifiers=False): - # print(o.name,o) activate(o) bpy.ops.object.duplicate() @@ -980,6 +980,9 @@ def meshFromCurve(o, use_modifiers=False): if co.type == 'FONT': # support for text objects is only and only here, just convert them to curves. bpy.ops.object.convert(target='CURVE', keep_original=False) + elif co.type != 'CURVE': # curve must be a curve... + bpy.ops.object.delete() # delete temporary object + raise CamException("Source curve object must be of type CURVE") co.data.dimensions = '3D' co.data.bevel_depth = 0 co.data.extrude = 0 diff --git a/scripts/addons/cam/ops.py b/scripts/addons/cam/ops.py index 4e2f2a3cb..9dfe6adf3 100644 --- a/scripts/addons/cam/ops.py +++ b/scripts/addons/cam/ops.py @@ -32,6 +32,7 @@ import shapely import mathutils import math +import textwrap import traceback import cam @@ -194,7 +195,8 @@ async def _calc_path(operator,context): print("Got path:",context) except CamException as e: traceback.print_tb(e.__traceback__) - operator.report({'ERROR'},str(e)) + error_str="\n".join(textwrap.wrap(str(e),width=80)) + operator.report({'ERROR'},error_str) return {'FINISHED',False} except AsyncCancelledException as e: return {'CANCELLED',False} @@ -215,9 +217,15 @@ class CalculatePath(bpy.types.Operator,AsyncOperatorMixin): bl_idname = "object.calculate_cam_path" bl_label = "Calculate CAM paths" bl_options = {'REGISTER', 'UNDO','BLOCKING'} - - # this property was actually ignored, so removing it in 0.3 - # operation= StringProperty(name="Operation", description="Specify the operation to calculate",default='Operation') + + @classmethod + def poll(cls,context): + s = context.scene + o = s.cam_operations[s.cam_active_operation] + if o is not None: + if cam.isValid(o,context): + return True + return False async def execute_async(self, context): (retval,success) = await _calc_path(self,context) @@ -296,8 +304,14 @@ class PathsChain(bpy.types.Operator,AsyncOperatorMixin): bl_label = "Calculate CAM paths in current chain and export chain gcode" bl_options = {'REGISTER', 'UNDO','BLOCKING'} + @classmethod + def poll(cls, context): + s = context.scene + chain = s.cam_chains[s.cam_active_chain] + return cam.isChainValid(chain,context)[0] + async def execute_async(self, context): - s = bpy.context.scene + s = context.scene bpy.ops.object.mode_set(mode='OBJECT') # force object mode chain = s.cam_chains[s.cam_active_chain] chainops = getChainOperations(chain) @@ -327,6 +341,12 @@ class PathExportChain(bpy.types.Operator): bl_label = "Export CAM paths in current chain as gcode" bl_options = {'REGISTER', 'UNDO'} + @classmethod + def poll(cls, context): + s = context.scene + chain = s.cam_chains[s.cam_active_chain] + return cam.isChainValid(chain,context)[0] + def execute(self, context): s = bpy.context.scene @@ -400,6 +420,12 @@ class CAMSimulateChain(bpy.types.Operator, AsyncOperatorMixin): bl_label = "CAM simulation" bl_options = {'REGISTER', 'UNDO','BLOCKING'} + @classmethod + def poll(cls, context): + s = context.scene + chain = s.cam_chains[s.cam_active_chain] + return cam.isChainValid(chain,context)[0] + operation: StringProperty(name="Operation", description="Specify the operation to calculate", default='Operation') diff --git a/scripts/addons/cam/strategy.py b/scripts/addons/cam/strategy.py index fe6cae2df..4ac522b50 100644 --- a/scripts/addons/cam/strategy.py +++ b/scripts/addons/cam/strategy.py @@ -209,7 +209,7 @@ async def curve(o): pathSamples = [] utils.getOperationSources(o) if not o.onlycurves: - o.info.warnings += 'at least one of assigned objects is not a curve\n' + raise CamException("All objects must be curves for this operation.") for ob in o.objects: pathSamples.extend(curveToChunks(ob)) # make the chunks from curve here @@ -257,8 +257,7 @@ async def proj_curve(s, o): from cam import chunk if targetCurve.type != 'CURVE': - o.info.warnings += 'Projection target and source have to be curve objects!\n ' - return + raise CamException('Projection target and source have to be curve objects!') if 1: extend_up = 0.1 @@ -610,8 +609,7 @@ async def medial_axis(o): elif o.cutter_type == 'BALLNOSE': maxdepth = - new_cutter_diameter / 2 - o.skin else: - o.info.warnings += 'Only Ballnose, V-carve cutters\n are supported' - return + raise CamException("Only Ballnose and V-carve cutters are supported for meial axis.") # remember resolutions of curves, to refine them, # otherwise medial axis computation yields too many branches in curved parts resolutions_before = [] @@ -771,6 +769,10 @@ def getLayers(operation, startdepth, enddepth): """returns a list of layers bounded by startdepth and enddepth uses operation.stepdown to determine number of layers. """ + if startdepth < enddepth: + raise CamException("Start depth is lower than end depth. " + "If you have set a custom depth end, it must be lower than depth start, " + "and should usually be negative. Set this in the CAM Operation Area panel.") if operation.use_layers: layers = [] n = math.ceil((startdepth - enddepth) / operation.stepdown) diff --git a/scripts/addons/cam/ui_panels/chains.py b/scripts/addons/cam/ui_panels/chains.py index 28cd2487a..663d0b247 100644 --- a/scripts/addons/cam/ui_panels/chains.py +++ b/scripts/addons/cam/ui_panels/chains.py @@ -3,6 +3,8 @@ from bpy.types import UIList from cam.ui_panels.buttons_panel import CAMButtonsPanel +import cam + class CAM_UL_operations(UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): @@ -37,6 +39,7 @@ class CAM_CHAINS_Panel(CAMButtonsPanel, bpy.types.Panel): bl_label = "CAM chains" bl_idname = "WORLD_PT_CAM_CHAINS" panel_interface_level = 1 + always_show_panel = True def draw(self, context): layout = self.layout @@ -63,13 +66,14 @@ def draw(self, context): col.operator("scene.cam_chain_operation_down", icon='TRIA_DOWN', text="") if not chain.computing: - if chain.valid: - pass - layout.operator("object.calculate_cam_paths_chain", text="Calculate chain paths & Export Gcode") - layout.operator("object.cam_export_paths_chain", text="Export chain gcode") - layout.operator("object.cam_simulate_chain", text="Simulate this chain") - else: - layout.label(text="chain invalid, can't compute") + layout.operator("object.calculate_cam_paths_chain", text="Calculate chain paths & Export Gcode") + layout.operator("object.cam_export_paths_chain", text="Export chain gcode") + layout.operator("object.cam_simulate_chain", text="Simulate this chain") + + valid,reason=cam.isChainValid(chain,context) + if not valid: + layout.label(icon="ERROR",text=f"Can't compute chain - reason:\n") + layout.label(text=reason) else: layout.label(text='chain is currently computing') diff --git a/scripts/addons/cam/ui_panels/info.py b/scripts/addons/cam/ui_panels/info.py index c6d44dce9..313640cbc 100644 --- a/scripts/addons/cam/ui_panels/info.py +++ b/scripts/addons/cam/ui_panels/info.py @@ -32,6 +32,7 @@ class CAM_INFO_Panel(CAMButtonsPanel, bpy.types.Panel): bl_label = "CAM info & warnings" bl_idname = "WORLD_PT_CAM_INFO" panel_interface_level = 0 + always_show_panel = True prop_level = { 'draw_blendercam_version': 0, @@ -115,6 +116,7 @@ def draw(self, context): self.context = context self.draw_blendercam_version() self.draw_opencamlib_version() - self.draw_op_warnings() - self.draw_op_time() - self.draw_op_money_cost() \ No newline at end of file + if self.op: + self.draw_op_warnings() + self.draw_op_time() + self.draw_op_money_cost() \ No newline at end of file diff --git a/scripts/addons/cam/ui_panels/operations.py b/scripts/addons/cam/ui_panels/operations.py index c16106a5c..11cf591c7 100644 --- a/scripts/addons/cam/ui_panels/operations.py +++ b/scripts/addons/cam/ui_panels/operations.py @@ -60,10 +60,10 @@ def draw_calculate_path(self): self.layout.label(text='!ERROR! COLLISION!') self.layout.prop(self.op.movement, 'free_height') - if self.op.valid: - self.layout.operator("object.calculate_cam_path", text="Calculate path & export Gcode") - else: - self.layout.label(text="operation invalid, can't compute") + if not self.op.valid: + self.layout.label(text="Select a valid object to calculate the path.") + # will be disable if not valid + self.layout.operator("object.calculate_cam_path", text="Calculate path & export Gcode") def draw_export_gcode(self): if not self.has_correct_level(): return diff --git a/scripts/addons/cam/utils.py b/scripts/addons/cam/utils.py index 2f3a1d69f..5a458f885 100644 --- a/scripts/addons/cam/utils.py +++ b/scripts/addons/cam/utils.py @@ -338,6 +338,8 @@ def getBounds(o): o.max.z = o.source_image_offset.z s = bpy.context.scene m = s.cam_machine + # make sure this message only shows once and goes away once fixed + o.info.warnings.replace('Operation exceeds your machine limits\n','') if o.max.x - o.min.x > m.working_area.x or o.max.y - o.min.y > m.working_area.y \ or o.max.z - o.min.z > m.working_area.z: o.info.warnings += 'Operation exceeds your machine limits\n'