Source code for cam.utilities.bounds_utils

"""Fabex 'bounds_utils.py' © 2012 Vilem Novak
"""

import time

import bpy
from mathutils import Vector

from .shapely_utils import shapely_to_curve, shapely_to_multipolygon
from .simple_utils import (
    activate,
    progress,
    unit_value_to_string,
)
from ..exception import CamException


[docs] def get_bounds_worldspace(obs, use_modifiers=False): """Get the bounding box of a list of objects in world space. This function calculates the minimum and maximum coordinates that encompass all the specified objects in the 3D world space. It iterates through each object, taking into account their transformations and modifiers if specified. The function supports different object types, including meshes and fonts, and handles the conversion of font objects to mesh format for accurate bounding box calculations. Args: obs (list): A list of Blender objects to calculate bounds for. use_modifiers (bool): If True, apply modifiers to the objects before calculating bounds. Defaults to False. Returns: tuple: A tuple containing the minimum and maximum coordinates in the format (minx, miny, minz, maxx, maxy, maxz). Raises: CamException: If an object type does not support CAM operations. """ # progress('getting bounds of object(s)') t = time.time() maxx = maxy = maxz = -10000000 minx = miny = minz = 10000000 for ob in obs: # bb=ob.bound_box mw = ob.matrix_world if ob.type == "MESH": if use_modifiers: depsgraph = bpy.context.evaluated_depsgraph_get() mesh_owner = ob.evaluated_get(depsgraph) mesh = mesh_owner.to_mesh() else: mesh = ob.data for c in mesh.vertices: coord = c.co worldCoord = mw @ Vector((coord[0], coord[1], coord[2])) minx = min(minx, worldCoord.x) miny = min(miny, worldCoord.y) minz = min(minz, worldCoord.z) maxx = max(maxx, worldCoord.x) maxy = max(maxy, worldCoord.y) maxz = max(maxz, worldCoord.z) if use_modifiers: mesh_owner.to_mesh_clear() elif ob.type == "FONT": activate(ob) bpy.ops.object.duplicate() co = bpy.context.active_object bpy.ops.object.parent_clear(type="CLEAR_KEEP_TRANSFORM") bpy.ops.object.convert(target="MESH", keep_original=False) mesh = co.data for c in mesh.vertices: coord = c.co worldCoord = mw @ Vector((coord[0], coord[1], coord[2])) minx = min(minx, worldCoord.x) miny = min(miny, worldCoord.y) minz = min(minz, worldCoord.z) maxx = max(maxx, worldCoord.x) maxy = max(maxy, worldCoord.y) maxz = max(maxz, worldCoord.z) bpy.ops.object.delete() bpy.ops.outliner.orphans_purge() else: if not hasattr(ob.data, "splines"): raise CamException("Can't do CAM operation on the selected object type") # for coord in bb: for c in ob.data.splines: for p in c.bezier_points: coord = p.co # this can work badly with some imported curves, don't know why... # worldCoord = mw * Vector((coord[0]/ob.scale.x, coord[1]/ob.scale.y, coord[2]/ob.scale.z)) worldCoord = mw @ Vector((coord[0], coord[1], coord[2])) minx = min(minx, worldCoord.x) miny = min(miny, worldCoord.y) minz = min(minz, worldCoord.z) maxx = max(maxx, worldCoord.x) maxy = max(maxy, worldCoord.y) maxz = max(maxz, worldCoord.z) for p in c.points: coord = p.co # this can work badly with some imported curves, don't know why... # worldCoord = mw * Vector((coord[0]/ob.scale.x, coord[1]/ob.scale.y, coord[2]/ob.scale.z)) worldCoord = mw @ Vector((coord[0], coord[1], coord[2])) minx = min(minx, worldCoord.x) miny = min(miny, worldCoord.y) minz = min(minz, worldCoord.z) maxx = max(maxx, worldCoord.x) maxy = max(maxy, worldCoord.y) maxz = max(maxz, worldCoord.z) # progress(time.time()-t) return minx, miny, minz, maxx, maxy, maxz
[docs] def get_spline_bounds(ob, curve): """Get the bounding box of a spline object. This function calculates the minimum and maximum coordinates (x, y, z) of the given spline object by iterating through its bezier points and regular points. It transforms the local coordinates to world coordinates using the object's transformation matrix. The resulting bounds can be used for various purposes, such as collision detection or rendering. Args: ob (Object): The object containing the spline whose bounds are to be calculated. curve (Curve): The curve object that contains the bezier points and regular points. Returns: tuple: A tuple containing the minimum and maximum coordinates in the format (minx, miny, minz, maxx, maxy, maxz). """ # progress('getting bounds of object(s)') maxx = maxy = maxz = -10000000 minx = miny = minz = 10000000 mw = ob.matrix_world for p in curve.bezier_points: coord = p.co # this can work badly with some imported curves, don't know why... # worldCoord = mw * Vector((coord[0]/ob.scale.x, coord[1]/ob.scale.y, coord[2]/ob.scale.z)) worldCoord = mw @ Vector((coord[0], coord[1], coord[2])) minx = min(minx, worldCoord.x) miny = min(miny, worldCoord.y) minz = min(minz, worldCoord.z) maxx = max(maxx, worldCoord.x) maxy = max(maxy, worldCoord.y) maxz = max(maxz, worldCoord.z) for p in curve.points: coord = p.co # this can work badly with some imported curves, don't know why... # worldCoord = mw * Vector((coord[0]/ob.scale.x, coord[1]/ob.scale.y, coord[2]/ob.scale.z)) worldCoord = mw @ Vector((coord[0], coord[1], coord[2])) minx = min(minx, worldCoord.x) miny = min(miny, worldCoord.y) minz = min(minz, worldCoord.z) maxx = max(maxx, worldCoord.x) maxy = max(maxy, worldCoord.y) maxz = max(maxz, worldCoord.z) # progress(time.time()-t) return minx, miny, minz, maxx, maxy, maxz
[docs] def get_bounds(o): """Calculate the bounding box for a given object. This function determines the minimum and maximum coordinates of an object's bounding box based on its geometry source. It handles different geometry types such as OBJECT, COLLECTION, and CURVE. The function also considers material properties and image cropping if applicable. The bounding box is adjusted according to the object's material settings and the optimization parameters defined in the object. Args: o (object): An object containing geometry and material properties, as well as optimization settings. Returns: None: This function modifies the input object in place and does not return a value. """ # print('kolikrat sem rpijde') if ( o.geometry_source == "OBJECT" or o.geometry_source == "COLLECTION" or o.geometry_source == "CURVE" ): print("Valid Geometry") minx, miny, minz, maxx, maxy, maxz = get_bounds_worldspace(o.objects, o.use_modifiers) if o.min_z_from == "OBJECT": if minz == 10000000: minz = 0 print("Min Z from Object:" + str(minz)) o.min.z = minz o.min_z = o.min.z else: o.min.z = o.min_z # max(bb[0][2]+l.z,o.min_z)# print("Not Min Z from Object") if o.material.estimate_from_model: print("Estimate Material from Model") o.min.x = minx - o.material.radius_around_model o.min.y = miny - o.material.radius_around_model o.max.z = max(o.max_z, maxz) o.max.x = maxx + o.material.radius_around_model o.max.y = maxy + o.material.radius_around_model else: print("Not Material from Model") o.min.x = o.material.origin.x o.min.y = o.material.origin.y o.min.z = o.material.origin.z - o.material.size.z o.max.x = o.min.x + o.material.size.x o.max.y = o.min.y + o.material.size.y o.max.z = o.material.origin.z else: i = bpy.data.images[o.source_image_name] if o.source_image_crop: sx = int(i.size[0] * o.source_image_crop_start_x / 100) ex = int(i.size[0] * o.source_image_crop_end_x / 100) sy = int(i.size[1] * o.source_image_crop_start_y / 100) ey = int(i.size[1] * o.source_image_crop_end_y / 100) else: sx = 0 ex = i.size[0] sy = 0 ey = i.size[1] o.optimisation.pixsize = o.source_image_size_x / i.size[0] o.min.x = o.source_image_offset.x + sx * o.optimisation.pixsize o.max.x = o.source_image_offset.x + ex * o.optimisation.pixsize o.min.y = o.source_image_offset.y + sy * o.optimisation.pixsize o.max.y = o.source_image_offset.y + ey * o.optimisation.pixsize o.min.z = o.source_image_offset.z + o.min_z o.max.z = o.source_image_offset.z s = bpy.context.scene m = s.cam_machine x_delta_range = o.max.x - o.min.x y_delta_range = o.max.y - o.min.y z_delta_range = o.max.z - o.min.z x_is_exceeded = x_delta_range > m.working_area.x y_is_exceeded = y_delta_range > m.working_area.y z_is_exceeded = z_delta_range > m.working_area.z if x_is_exceeded or y_is_exceeded or z_is_exceeded: exceed_msg = "Operation Exceeds Your Machine Limits (range > working area)\n" # Do not append more than one such a warning if exceed_msg not in o.info.warnings: o.info.warnings += exceed_msg if x_is_exceeded: o.info.warnings += ( f"Axis X[ range:{unit_value_to_string(x_delta_range)}" + f", working area:{unit_value_to_string(m.working_area.x)}]\n" ) if y_is_exceeded: o.info.warnings += ( f"Axis Y[ range:{unit_value_to_string(y_delta_range)}" + f", working area:{unit_value_to_string(m.working_area.y)}]\n" ) if z_is_exceeded: o.info.warnings += ( f"Axis Z[ range:{unit_value_to_string(z_delta_range)}" + f", working area:{unit_value_to_string(m.working_area.z)}]\n" ) if not o.info.warnings == "": addon_prefs = bpy.context.preferences.addons["bl_ext.user_default.fabex"].preferences if addon_prefs.show_popups: bpy.ops.cam.popup("INVOKE_DEFAULT")
[docs] def get_bounds_multiple(operations): """Gets bounds of multiple operations for simulations or rest milling. This function iterates through a list of operations to determine the minimum and maximum bounds in three-dimensional space (x, y, z). It initializes the bounds to extreme values and updates them based on the bounds of each operation. The function is primarily intended for use in simulations or rest milling processes, although it is noted that the implementation may not be optimal. Args: operations (list): A list of operation objects, each containing 'min' and 'max' attributes with 'x', 'y', and 'z' coordinates. Returns: tuple: A tuple containing the minimum and maximum bounds in the order (minx, miny, minz, maxx, maxy, maxz). """ maxx = maxy = maxz = -10000000 minx = miny = minz = 10000000 for o in operations: get_bounds(o) maxx = max(maxx, o.max.x) maxy = max(maxy, o.max.y) maxz = max(maxz, o.max.z) minx = min(minx, o.min.x) miny = min(miny, o.min.y) minz = min(minz, o.min.z) return minx, miny, minz, maxx, maxy, maxz
[docs] def position_object(operation): """Position an object based on specified operation parameters. This function adjusts the location of a Blender object according to the provided operation settings. It calculates the bounding box of the object in world space and modifies its position based on the material's center settings and specified z-positioning (BELOW, ABOVE, or CENTERED). The function also applies transformations to the object if it is not of type 'CURVE'. Args: operation (OperationType): An object containing parameters for positioning, including object_name, use_modifiers, and material settings. """ ob = bpy.data.objects[operation.object_name] bpy.ops.object.origin_set(type="ORIGIN_GEOMETRY", center="BOUNDS") ob.select_set(True) bpy.context.view_layer.objects.active = ob minx, miny, minz, maxx, maxy, maxz = get_bounds_worldspace([ob], operation.use_modifiers) totx = maxx - minx toty = maxy - miny totz = maxz - minz if operation.material.center_x: ob.location.x -= minx + totx / 2 else: ob.location.x -= minx if operation.material.center_y: ob.location.y -= miny + toty / 2 else: ob.location.y -= miny if operation.material.z_position == "BELOW": ob.location.z -= maxz elif operation.material.z_position == "ABOVE": ob.location.z -= minz elif operation.material.z_position == "CENTERED": ob.location.z -= minz + totz / 2 if ob.type != "CURVE": bpy.ops.object.transform_apply(location=True, rotation=False, scale=False)