"""Fabex 'ops.py' © 2012 Vilem Novak
Blender Operator definitions are in this file.
They mostly call the functions from 'utils.py'
"""
from math import pi
import os
import subprocess
import textwrap
import threading
import time
import traceback
import bpy
from bpy.props import (
BoolProperty,
EnumProperty,
StringProperty,
FloatProperty,
)
from bpy.types import Operator
from .async_op import (
AsyncCancelledException,
AsyncOperatorMixin,
)
from ..constants import was_hidden_dict
from ..exception import CamException
from ..gcode_path import (
get_path,
export_gcode_path,
)
from ..utilities.async_utils import progress_async
from ..utilities.chunk_utils import chunks_to_shapely
from ..utilities.shapely_utils import shapely_to_curve
from ..utilities.simple_utils import activate, add_to_group
from ..utilities.thread_utils import threadCom, thread_read, timer_update
from ..utilities.machine_utils import add_machine_area_object
from ..utilities.bounds_utils import get_bounds_worldspace
from ..utilities.operation_utils import (
chain_valid,
source_valid,
reload_paths,
get_chain_operations,
)
[docs]
class PathsBackground(Operator):
"""Calculate CAM Paths in Background. File Has to Be Saved Before."""
[docs]
bl_idname = "object.calculate_cam_paths_background"
[docs]
bl_label = "Calculate CAM Paths in Background"
[docs]
bl_options = {"REGISTER", "UNDO"}
[docs]
def execute(self, context):
"""Execute the camera operation in the background.
This method initiates a background process to perform camera operations
based on the current scene and active camera operation. It sets up the
necessary paths for the script and starts a subprocess to handle the
camera computations. Additionally, it manages threading to ensure that
the main thread remains responsive while the background operation is
executed.
Args:
context: The context in which the operation is executed.
Returns:
dict: A dictionary indicating the completion status of the operation.
"""
s = bpy.context.scene
o = s.cam_operations[s.cam_active_operation]
self.operation = o
o.computing = True
bpath = bpy.app.binary_path
fpath = bpy.data.filepath
for p in bpy.utils.script_paths():
scriptpath = p + os.sep + "addons" + os.sep + "cam" + os.sep + "backgroundop.py"
print(scriptpath)
if os.path.isfile(scriptpath):
break
proc = subprocess.Popen(
[bpath, "-b", fpath, "-P", scriptpath, "--", "-o=" + str(s.cam_active_operation)],
bufsize=1,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
)
tcom = threadCom(o, proc)
readthread = threading.Thread(target=thread_read, args=([tcom]), daemon=True)
readthread.start()
# self.__class__.cam_processes=[]
if not hasattr(bpy.ops.object.calculate_cam_paths_background.__class__, "cam_processes"):
bpy.ops.object.calculate_cam_paths_background.__class__.cam_processes = []
bpy.ops.object.calculate_cam_paths_background.__class__.cam_processes.append(
[readthread, tcom]
)
return {"FINISHED"}
[docs]
class KillPathsBackground(Operator):
"""Remove CAM Path Processes in Background."""
[docs]
bl_idname = "object.kill_calculate_cam_paths_background"
[docs]
bl_label = "Kill Background Computation of an Operation"
[docs]
bl_options = {"REGISTER", "UNDO"}
[docs]
def execute(self, context):
"""Execute the camera operation in the given context.
This method retrieves the active camera operation from the scene and
checks if there are any ongoing processes related to camera path
calculations. If such processes exist and match the current operation,
they are terminated. The method then marks the operation as not
computing and returns a status indicating that the execution has
finished.
Args:
context: The context in which the operation is executed.
Returns:
dict: A dictionary with a status key indicating the result of the execution.
"""
s = bpy.context.scene
o = s.cam_operations[s.cam_active_operation]
self.operation = o
if hasattr(bpy.ops.object.calculate_cam_paths_background.__class__, "cam_processes"):
processes = bpy.ops.object.calculate_cam_paths_background.__class__.cam_processes
for p in processes:
tcom = p[1]
if tcom.opname == o.name:
processes.remove(p)
tcom.proc.kill()
o.computing = False
return {"FINISHED"}
[docs]
async def _calc_path(operator, context):
"""Calculate the path for a given operator and context.
This function processes the current scene's camera operations based on
the specified operator and context. It handles different geometry
sources, checks for valid operation parameters, and manages the
visibility of objects and collections. The function also retrieves the
path using an asynchronous operation and handles any exceptions that may
arise during this process. If the operation is invalid or if certain
conditions are not met, appropriate error messages are reported to the
operator.
Args:
operator (bpy.types.Operator): The operator that initiated the path calculation.
context (bpy.types.Context): The context in which the operation is executed.
Returns:
tuple: A tuple indicating the status of the operation.
Returns {'FINISHED', True} if successful,
{'FINISHED', False} if there was an error,
or {'CANCELLED', False} if the operation was cancelled.
"""
s = bpy.context.scene
o = s.cam_operations[s.cam_active_operation]
if o.geometry_source == "OBJECT":
ob = bpy.data.objects[o.object_name]
ob.hide_set(False)
if o.geometry_source == "COLLECTION":
obc = bpy.data.collections[o.collection_name]
for ob in obc.objects:
ob.hide_set(False)
if o.strategy == "CARVE":
curvob = bpy.data.objects[o.curve_source]
curvob.hide_set(False)
"""if o.strategy == 'WATERLINE':
ob = bpy.data.objects[o.object_name]
ob.select_set(True)
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)"""
mesh = bpy.data.meshes.get(f"cam_path_{o.name}")
if mesh:
bpy.data.meshes.remove(mesh)
text = "Operation can't be performed, see Warnings for info"
if not o.valid:
operator.report(
{"ERROR_INVALID_INPUT"},
text,
)
progress_async(text)
# bpy.ops.cam.popup(text=text)
return {"FINISHED", False}
# check for free movement height < maxz and return with error
if o.movement.free_height < o.max_z:
operator.report(
{"ERROR_INVALID_INPUT"},
"Free Movement Height Is Less than Operation Depth Start \n Correct and Try Again.",
)
progress_async("Operation Can't Be Performed, See Warnings for Info")
# bpy.ops.cam.popup(text=text)
return {"FINISHED", False}
if o.computing:
return {"FINISHED", False}
o.operator = operator
if o.use_layers:
o.movement.parallel_step_back = False
try:
await get_path(context, o)
print("Got Path Okay")
except CamException as e:
traceback.print_tb(e.__traceback__)
error_str = "\n".join(textwrap.wrap(str(e), width=80))
operator.report({"ERROR"}, error_str)
# bpy.ops.cam.popup(text=error_str)
return {"FINISHED", False}
except AsyncCancelledException as e:
# bpy.ops.cam.popup(text=str(e))
return {"CANCELLED", False}
except Exception as e:
print("FAIL", e)
traceback.print_tb(e.__traceback__)
operator.report({"ERROR"}, str(e))
# bpy.ops.cam.popup(text=str(e))
return {"FINISHED", False}
coll = bpy.data.collections.get("RigidBodyWorld")
if coll:
bpy.data.collections.remove(coll)
return {"FINISHED", True}
[docs]
class CalculatePath(Operator, AsyncOperatorMixin):
"""Calculate CAM Paths"""
[docs]
bl_idname = "object.calculate_cam_path"
[docs]
bl_label = "Calculate CAM Paths"
[docs]
bl_options = {"REGISTER", "UNDO", "BLOCKING"}
@classmethod
[docs]
def poll(cls, context):
"""Check if the current camera operation is valid.
This method checks the active camera operation in the given context and
determines if it is valid. It retrieves the active operation from the
scene's camera operations and validates it using the `isValid` function.
If the operation is valid, it returns True; otherwise, it returns False.
Args:
context (Context): The context containing the scene and camera operations.
Returns:
bool: True if the active camera operation is valid, False otherwise.
"""
s = context.scene
o = s.cam_operations[s.cam_active_operation]
if o is not None:
if source_valid(o, context):
return True
return False
[docs]
async def execute_async(self, context):
"""Execute an asynchronous calculation of a path.
This method performs an asynchronous operation to calculate a path based
on the provided context. It awaits the result of the calculation and
prints the success status along with the return value. The return value
can be used for further processing or analysis.
Args:
context (Any): The context in which the path calculation is to be executed.
Returns:
Any: The result of the path calculation.
"""
(retval, success) = await _calc_path(self, context)
print(f"CALCULATED PATH (success={success},retval={retval})")
return retval
[docs]
class PathsAll(Operator):
"""Calculate All CAM Paths"""
[docs]
bl_idname = "object.calculate_cam_paths_all"
[docs]
bl_label = "Calculate All CAM Paths"
[docs]
bl_options = {"REGISTER", "UNDO"}
[docs]
def execute(self, context):
"""Execute camera operations in the current Blender context.
This function iterates through the camera operations defined in the
current scene and executes the background calculation for each
operation. It sets the active camera operation index and prints the name
of each operation being processed. This is typically used in a Blender
add-on or script to automate camera path calculations.
Args:
context (bpy.context): The current Blender context.
Returns:
dict: A dictionary indicating the completion status of the operation,
typically {'FINISHED'}.
"""
i = 0
for o in bpy.context.scene.cam_operations:
bpy.context.scene.cam_active_operation = i
print("\nCalculating Path :" + o.name)
print("\n")
bpy.ops.object.calculate_cam_paths_background()
i += 1
return {"FINISHED"}
[docs]
def draw(self, context):
"""Draws the user interface elements for the operation selection.
This method utilizes the Blender layout system to create a property
search interface for selecting operations related to camera
functionalities. It links the current instance's operation property to
the available camera operations defined in the Blender scene.
Args:
context (bpy.context): The context in which the drawing occurs,
"""
layout = self.layout
layout.prop_search(self, "operation", bpy.context.scene, "cam_operations")
[docs]
class PathsChain(Operator, AsyncOperatorMixin):
"""Calculate a Chain and Export the G-code Alltogether."""
[docs]
bl_idname = "object.calculate_cam_paths_chain"
[docs]
bl_label = "Calculate CAM Paths in Current Chain and Export Chain G-code"
[docs]
bl_options = {"REGISTER", "UNDO", "BLOCKING"}
@classmethod
[docs]
def poll(cls, context):
"""Check the validity of the active camera chain in the given context.
This method retrieves the active camera chain from the scene and checks
its validity using the `isChainValid` function. It returns a boolean
value indicating whether the camera chain is valid or not.
Args:
context (Context): The context containing the scene and camera chain information.
Returns:
bool: True if the active camera chain is valid, False otherwise.
"""
s = context.scene
if len(s.cam_chains) > 0:
chain = s.cam_chains[s.cam_active_chain]
return chain_valid(chain, context)[0]
else:
return False
[docs]
async def execute_async(self, context):
"""Execute asynchronous operations for camera path calculations.
This method sets the object mode for the Blender scene and processes a
series of camera operations defined in the active camera chain. It
reports the progress of each operation and handles any exceptions that
may occur during the path calculation. After successful calculations, it
exports the resulting mesh data to a specified G-code file.
Args:
context (bpy.context): The Blender context containing scene and
Returns:
dict: A dictionary indicating the result of the operation,
typically {'FINISHED'}.
"""
s = context.scene
bpy.ops.object.mode_set(mode="OBJECT") # force object mode
chain = s.cam_chains[s.cam_active_chain]
chainops = get_chain_operations(chain)
meshes = []
try:
for i in range(0, len(chainops)):
s.cam_active_operation = s.cam_operations.find(chainops[i].name)
self.report({"INFO"}, f"Calculating Path: {chainops[i].name}")
result, success = await _calc_path(self, context)
if not success and "FINISHED" in result:
self.report({"ERROR"}, f"Couldn't Calculate Path: {chainops[i].name}")
except Exception as e:
print("FAIL", e)
traceback.print_tb(e.__traceback__)
self.report({"ERROR"}, str(e))
return {"FINISHED"}
for o in chainops:
meshes.append(bpy.data.objects["cam_path_{}".format(o.name)].data)
export_gcode_path(chain.filename, meshes, chainops)
return {"FINISHED"}
[docs]
class PathExportChain(Operator):
"""Calculate a Chain and Export the G-code Together."""
[docs]
bl_idname = "object.cam_export_paths_chain"
[docs]
bl_label = "Export CAM Paths in Current Chain as G-code"
[docs]
bl_options = {"REGISTER", "UNDO"}
@classmethod
[docs]
def poll(cls, context):
"""Check the validity of the active camera chain in the given context.
This method retrieves the currently active camera chain from the scene
context and checks its validity using the `isChainValid` function. It
returns a boolean indicating whether the active camera chain is valid or
not.
Args:
context (object): The context containing the scene and camera chain information.
Returns:
bool: True if the active camera chain is valid, False otherwise.
"""
s = context.scene
chain = s.cam_chains[s.cam_active_chain]
return chain_valid(chain, context)[0]
[docs]
def execute(self, context):
"""Execute the camera path export process.
This function retrieves the active camera chain from the current scene
and gathers the mesh data associated with the operations of that chain.
It then exports the G-code path using the specified filename and the
collected mesh data. The function is designed to be called within the
context of a Blender operator.
Args:
context (bpy.context): The context in which the operator is executed.
Returns:
dict: A dictionary indicating the completion status of the operation,
typically {'FINISHED'}.
"""
s = bpy.context.scene
chain = s.cam_chains[s.cam_active_chain]
chainops = get_chain_operations(chain)
meshes = []
# if len(chainops)<4:
for o in chainops:
# bpy.ops.object.calculate_cam_paths_background()
meshes.append(bpy.data.objects["cam_path_{}".format(o.name)].data)
export_gcode_path(chain.filename, meshes, chainops)
return {"FINISHED"}
[docs]
class PathExport(Operator):
"""Export G-code. Can Be Used only when the Path Object Is Present"""
[docs]
bl_idname = "object.cam_export"
[docs]
bl_label = "Export Operation G-code"
[docs]
bl_options = {"REGISTER", "UNDO"}
[docs]
def execute(self, context):
"""Execute the camera operation and export the G-code path.
This method retrieves the active camera operation from the current scene
and exports the corresponding G-code path to a specified filename. It
prints the filename and relevant operation details to the console for
debugging purposes. The G-code path is generated based on the camera
path data associated with the active operation.
Args:
context: The context in which the operation is executed.
Returns:
dict: A dictionary indicating the completion status of the operation,
typically {'FINISHED'}.
"""
s = bpy.context.scene
operation = s.cam_operations[s.cam_active_operation]
print(
"EXPORTING",
operation.filename,
bpy.data.objects["cam_path_{}".format(operation.name)].data,
operation,
)
export_gcode_path(
operation.filename,
[bpy.data.objects["cam_path_{}".format(operation.name)].data],
[operation],
)
return {"FINISHED"}