"""Fabex 'toolpath.py' © 2012 Vilem Novak
Generate toolpaths from path chunks.
"""
# Path Generaton
import time
import bpy
from .constants import USE_PROFILER
from .exception import CamException
from .gcode.gcode_export import export_gcode_path
# 3 Axis Strategies
from .strategies.block import block
from .strategies.carve import carve
from .strategies.circles import circles
from .strategies.crazy import crazy
from .strategies.cross import cross
from .strategies.cutout import cutout
from .strategies.curve_to_path import curve
from .strategies.drill import drill
from .strategies.medial_axis import medial_axis
from .strategies.outline_fill import outline_fill
from .strategies.pencil import pencil
from .strategies.project_curve import projected_curve
from .strategies.pocket import pocket
from .strategies.parallel import parallel
from .strategies.spiral import spiral
from .strategies.waterline import waterline
# 4 Axis Strategies
from .strategies.parallel_4_axis import parallel_four_axis
from .strategies.helix_4_axis import helix_four_axis
from .utilities.async_utils import progress_async
from .utilities.bounds_utils import get_bounds
from .utilities.index_utils import (
cleanup_indexed,
prepare_indexed,
)
from .utilities.logging_utils import log
from .utilities.operation_utils import (
get_operation_axes,
get_operation_sources,
get_change_data,
check_memory_limit,
)
from .utilities.simple_utils import (
progress,
safe_filename,
)
[docs]
async def path_profiler(strategy):
import cProfile
pr = cProfile.Profile()
pr.enable()
await strategy
pr.disable()
pr.dump_stats(time.strftime("Fabex_%Y%m%d_%H%M.prof"))
[docs]
async def get_path(context, operation):
"""Calculate the path for a given operation in a specified context.
This function performs various calculations to determine the path based
on the operation's parameters and context. It checks for changes in the
operation's data and updates relevant tags accordingly. Depending on the
number of machine axes specified in the operation, it calls different
functions to handle 3-axis, 4-axis, or 5-axis operations. Additionally,
if automatic export is enabled, it exports the generated G-code path.
Args:
context: The context in which the operation is being performed.
operation: An object representing the operation with various
attributes such as machine_axes, strategy, and
auto_export.
"""
if operation.feedrate > context.scene.cam_machine.feedrate_max:
raise CamException("Operation Feedrate is greater than Machine Maximum!")
t = time.process_time()
# these tags are for caching of some of the results. Not working well still
# - although it can save a lot of time during calculation...
chd = get_change_data(operation)
if operation.change_data != chd: # or 1:
operation.update_offset_image_tag = True
operation.update_z_buffer_image_tag = True
operation.change_data = chd
operation.update_silhouette_tag = True
operation.update_ambient_tag = True
operation.update_bullet_collision_tag = True
three_axis, four_axis, five_axis, indexed_four_axis, indexed_five_axis = get_operation_axes(
o=operation
)
get_operation_sources(operation)
operation.info.warnings = ""
check_memory_limit(operation)
log.info(f"Operation Axes: {operation.machine_axes}")
if three_axis:
if USE_PROFILER: # profiler
path_profiler(get_path_3_axis(context, operation))
else:
await get_path_3_axis(context, operation)
elif four_axis:
if USE_PROFILER:
path_profiler(get_path_4_axis(context, operation))
else:
await get_path_4_axis(context, operation)
# 5 axis operations are now only 3 axis operations that get rotated...
elif indexed_five_axis or indexed_four_axis:
operation.orientation = prepare_indexed(operation) # TODO RENAME THIS
await get_path_3_axis(context, operation) # TODO RENAME THIS
cleanup_indexed(operation) # TODO RENAME THIS
# export gcode if automatic.
if operation.auto_export:
path_name = context.scene.cam_names.path_name_full
if bpy.data.objects.get(path_name) is None:
return
p = bpy.data.objects[path_name]
name_raw = operation.name if operation.link_operation_file_names else operation.filename
name = safe_filename(name_raw)
export_gcode_path(name, [p.data], [operation])
operation.changed = False
t1 = time.process_time() - t
progress("Total Time: ", t1)
# this is the main function.
[docs]
async def get_path_3_axis(context, operation):
"""Generate a machining path based on the specified operation strategy.
This function evaluates the provided operation's strategy and generates
the corresponding machining path. It supports various strategies such as
'CUTOUT', 'CURVE', 'PROJECTED_CURVE', 'POCKET', and others. Depending on
the strategy, it performs specific calculations and manipulations on the
input data to create a path that can be used for machining operations.
The function handles different strategies by calling appropriate methods
from the `strategy` module and processes the path samples accordingly.
It also manages the generation of chunks, which represent segments of
the machining path, and applies any necessary transformations based on
the operation's parameters.
Args:
context (bpy.context): The Blender context containing scene information.
operation (Operation): An object representing the machining operation,
which includes strategy and other relevant parameters.
Returns:
None: This function does not return a value but modifies the state of
the operation and context directly.
"""
s = bpy.context.scene
o = operation
get_bounds(o)
tw = time.time()
strategy_from_operation = {
"BLOCK": block,
"CARVE": carve,
"CIRCLES": circles,
"CRAZY": crazy,
"CROSS": cross,
"CUTOUT": cutout,
"CURVE": curve,
"DRILL": drill,
"MEDIAL_AXIS": medial_axis,
"OUTLINEFILL": outline_fill,
"PENCIL": pencil,
"PROJECTED_CURVE": projected_curve,
"POCKET": pocket,
"PARALLEL": parallel,
"SPIRAL": spiral,
"WATERLINE": waterline,
}
await strategy_from_operation[o.strategy](o)
await progress_async(f"Done", time.time() - tw, "s")
[docs]
async def get_path_4_axis(context, operation):
"""Generate a path for a specified axis based on the given operation.
This function retrieves the bounds of the operation and checks the
strategy associated with the axis. If the strategy is one of the
specified types ('PARALLELR', 'PARALLEL', 'HELIX', 'CROSS'), it
generates path samples and processes them into chunks for meshing. The
function utilizes various helper functions to achieve this, including
obtaining layers and sampling chunks.
Args:
context: The context in which the operation is executed.
operation: An object that contains the strategy and other
necessary parameters for generating the path.
Returns:
None: This function does not return a value but modifies
the state of the operation by processing chunks for meshing.
"""
s = bpy.context.scene
o = operation
get_bounds(o)
tw = time.time()
strategy_from_operation = {
"PARALLEL": parallel_four_axis,
"PARALLELR": parallel_four_axis,
"HELIX": helix_four_axis,
}
await strategy_from_operation[o.strategy_4_axis](o)
await progress_async(f"Done", time.time() - tw, "s")