Source code for fabex.strategies.cutout

from math import (
    radians,
    sqrt,
    tan,
)


import bpy

from ..bridges import use_bridges

from ..utilities.chunk_utils import (
    chunks_to_mesh,
    limit_chunks,
    sort_chunks,
)
from ..utilities.curve_utils import curve_to_chunks
from ..utilities.logging_utils import log
from ..utilities.operation_utils import check_min_z, get_layers
from ..utilities.parent_utils import parent_child_poly
from ..utilities.shapely_utils import shapely_to_chunks
from ..utilities.silhouette_utils import get_object_outline
from ..utilities.simple_utils import (
    activate,
    remove_multiple,
    subdivide_short_lines,
)


# cutout strategy is completely here:
[docs] async def cutout(o): """Perform a cutout operation based on the provided parameters. This function calculates the necessary cutter offset based on the cutter type and its parameters. It processes a list of objects to determine how to cut them based on their geometry and the specified cutting type. The function handles different cutter types such as 'VCARVE', 'CYLCONE', 'BALLCONE', and 'BALLNOSE', applying specific calculations for each. It also manages the layering and movement strategies for the cutting operation, including options for lead-ins, ramps, and bridges. Args: o (object): An object containing parameters for the cutout operation, including cutter type, diameter, depth, and other settings. Returns: None: This function does not return a value but performs operations on the provided object. """ log.info("Strategy: Cutout") max_depth = check_min_z(o) cutter_angle = radians(o.cutter_tip_angle / 2) # Cutter Offset r = o.cutter_diameter / 2 cutter_offset = r log.info(f"Cutter Type: {o.cutter_type}") log.info(f"Max Depth: {max_depth}") log.info(f"Cutter Radius: {r}") log.info(f"Skin: {o.skin}") offset_by_type = { "VCARVE": -max_depth * tan(cutter_angle), "CYLCONE": -max_depth * tan(cutter_angle) + o.cylcone_diameter / 2, "BALLCONE": -max_depth * tan(cutter_angle) + o.ball_radius, "BALLNOSE": sqrt(r**2 - (r + max_depth) ** 2) if -max_depth < r else r, } try: cutter_offset = offset_by_type[o.cutter_type] except: pass # Add Skin for Profile cutter_offset = (r if cutter_offset > r else cutter_offset) + o.skin log.info(f"Offset: {cutter_offset}") join = 2 if o.straight else 1 offset = True for ob in o.objects: if ob.type == "CURVE": activate(ob) if ob.data.splines and ob.data.splines[0].type == "BEZIER": bpy.ops.object.curve_remove_doubles(merge_distance=0.0001, keep_bezier=True) else: bpy.ops.object.curve_remove_doubles() # Ensure polylines are at least three points long subdivide_short_lines(ob) # Separate to allow open Curves :) if o.cut_type == "ONLINE" and o.onlycurves: log.info("Separate") chunks_from_curve = [] for ob in o.objects: chunks_from_curve.extend(curve_to_chunks(ob, o.use_modifiers)) else: chunks_from_curve = [] if o.cut_type == "ONLINE": path = get_object_outline(0, o, True) else: offset = True if o.cut_type == "INSIDE": offset = False path = get_object_outline(cutter_offset, o, offset) if o.outlines_count > 1: for i in range(1, o.outlines_count): chunks_from_curve.extend(shapely_to_chunks(path, -1)) path_distance = o.distance_between_paths if o.cut_type == "INSIDE": path_distance *= -1 path = path.buffer( distance=path_distance, resolution=o.optimisation.circle_detail, join_style=join, mitre_limit=2, ) chunks_from_curve.extend(shapely_to_chunks(path, -1)) if o.outlines_count > 1 and o.movement.insideout == "OUTSIDEIN": chunks_from_curve.reverse() chunks_from_curve = limit_chunks(chunks_from_curve, o) if not o.dont_merge: parent_child_poly(chunks_from_curve, chunks_from_curve, o) if o.outlines_count == 1: chunks_from_curve = await sort_chunks(chunks_from_curve, o) move_type = o.movement.type spin = o.movement.spindle_rotation climb_ccw = move_type == "CLIMB" and spin == "CCW" conventional_cw = move_type == "CONVENTIONAL" and spin == "CW" if climb_ccw or conventional_cw: [chunk.reverse() for chunk in chunks_from_curve] # For simplicity, reverse once again when Inside cutting if o.cut_type == "INSIDE": [chunk.reverse() for chunk in chunks_from_curve] layers = get_layers( o, o.max_z, check_min_z(o), ) chunk_copies = [] # If First Down is true, cut each shape from top to bottom, # if not, split shapes into layers by height, creating copies as # the same chunks will be on multiple layers if o.first_down: for chunk in chunks_from_curve: # A direction switch boolean check is needed to avoid cutter # lifting with open Chunks and "MEANDER" movement dir_switch = False for layer in layers: chunk_copy = chunk.copy() if dir_switch: chunk_copy.reverse() chunk_copies.append([chunk_copy, layer]) if (not chunk.closed) and o.movement.type == "MEANDER": dir_switch = not dir_switch else: for layer in layers: for chunk in chunks_from_curve: chunk_copies.append([chunk.copy(), layer]) # Set Z for all Chunks for i, chunk_layer in enumerate(chunk_copies): chunk = chunk_layer[0] layer = chunk_layer[1] chunk.set_z(layer[1]) log.info(f"Layer {i} Depth: {layer[1]}") chunks = [] # Add Bridges to Chunks if o.use_bridges: remove_multiple(o.name + "_cut_bridges") bridge_height = min(o.max.z, o.min.z + abs(o.bridges_height)) log.info("-") log.info("Using Bridges") log.info("Old Briddge Cut Removed") for chunk_layer in chunk_copies: chunk = chunk_layer[0] layer = chunk_layer[1] if layer[1] < bridge_height: use_bridges(chunk, o) if o.profile_start > 0: log.info("Cutout Change Profile Start") for chunk_layer in chunk_copies: chunk = chunk_layer[0] if chunk.closed: chunk.change_path_start(o) # Lead in if o.lead_in > 0.0 or o.lead_out > 0: log.info("Cutout Lead-in") for chunk_layer in chunk_copies: chunk = chunk_layer[0] if chunk.closed: chunk.break_path_for_leadin_leadout(o) chunk.lead_contour(o) # Add Ramps or just Chunks if o.movement.ramp: for chunk_layer in chunk_copies: chunk = chunk_layer[0] layer = chunk_layer[1] if o.movement.zig_zag_ramp: chunk.ramp_zig_zag( layer[0], layer[1], o, ) chunks.append(chunk) else: if chunk.closed: chunk.ramp_contour( layer[0], layer[1], o, ) chunks.append(chunk) else: chunk.ramp_zig_zag( layer[0], layer[1], o, ) chunks.append(chunk) else: for chunk_layer in chunk_copies: chunks.append(chunk_layer[0]) chunks_to_mesh(chunks, o)