kopia lustrzana https://github.com/vilemduha/blendercam
commit
a1ca039b96
|
@ -12,7 +12,23 @@ import bpy
|
|||
|
||||
@types.coroutine
|
||||
def progress_async(text, n=None, value_type='%'):
|
||||
"""Function for Reporting During the Script, Works for Background Operations in the Header."""
|
||||
"""Report progress during script execution for background operations.
|
||||
|
||||
This function is designed to provide progress updates while a script is
|
||||
running, particularly for background operations. It yields a dictionary
|
||||
containing the progress information, which includes the text description
|
||||
of the progress, an optional numeric value, and the type of value being
|
||||
reported. If an exception is thrown during the operation, it will be
|
||||
raised for handling.
|
||||
|
||||
Args:
|
||||
text (str): A message indicating the current progress.
|
||||
n (optional): An optional numeric value representing the progress.
|
||||
value_type (str?): A string indicating the type of value being reported (default is '%').
|
||||
|
||||
Raises:
|
||||
Exception: If an exception is thrown during the operation.
|
||||
"""
|
||||
throw_exception = yield ('progress', {'text': text, 'n': n, "value_type": value_type})
|
||||
if throw_exception is not None:
|
||||
raise throw_exception
|
||||
|
@ -30,6 +46,22 @@ class AsyncOperatorMixin:
|
|||
self._is_cancelled = False
|
||||
|
||||
def modal(self, context, event):
|
||||
"""Handle modal operations for a Blender event.
|
||||
|
||||
This function processes events in a modal operator. It checks for
|
||||
specific event types, such as TIMER and ESC, and performs actions
|
||||
accordingly. If the event type is TIMER, it attempts to execute a tick
|
||||
function, managing the timer and status text in the Blender workspace.
|
||||
If an exception occurs during the tick execution, it handles the error
|
||||
gracefully by removing the timer and reporting the error. The function
|
||||
also allows for cancellation of the operation when the ESC key is
|
||||
pressed.
|
||||
|
||||
Args:
|
||||
context (bpy.context): The current Blender context.
|
||||
event (bpy.types.Event): The event being processed.
|
||||
"""
|
||||
|
||||
if bpy.app.background:
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
|
@ -58,6 +90,24 @@ class AsyncOperatorMixin:
|
|||
return {'PASS_THROUGH'}
|
||||
|
||||
def show_progress(self, context, text, n, value_type):
|
||||
"""Display the progress of a task in the workspace and console.
|
||||
|
||||
This function updates the status text in the Blender workspace to show
|
||||
the current progress of a task. It formats the progress message based on
|
||||
the provided parameters and outputs it to both the Blender interface and
|
||||
the standard output. If the value of `n` is not None, it includes the
|
||||
formatted number and value type in the progress message; otherwise, it
|
||||
simply displays the provided text.
|
||||
|
||||
Args:
|
||||
context: The context in which the progress is displayed (typically
|
||||
the Blender context).
|
||||
text (str): A message indicating the task being performed.
|
||||
n (float or None): The current progress value to be displayed.
|
||||
value_type (str): A string representing the type of value (e.g.,
|
||||
percentage, units).
|
||||
"""
|
||||
|
||||
if n is not None:
|
||||
progress_text = f"{text}: {n:.2f}{value_type}"
|
||||
else:
|
||||
|
@ -67,6 +117,27 @@ class AsyncOperatorMixin:
|
|||
sys.stdout.flush()
|
||||
|
||||
def tick(self, context):
|
||||
"""Execute a tick of the coroutine and handle its progress.
|
||||
|
||||
This method checks if the coroutine is initialized; if not, it
|
||||
initializes it by calling `execute_async` with the provided context. It
|
||||
then attempts to send a signal to the coroutine to either continue its
|
||||
execution or handle cancellation. If the coroutine is cancelled, it
|
||||
raises a `StopIteration` exception. The method also processes messages
|
||||
from the coroutine, displaying progress or other messages as needed.
|
||||
|
||||
Args:
|
||||
context: The context in which the coroutine is executed.
|
||||
|
||||
Returns:
|
||||
bool: True if the tick was processed successfully, False if the coroutine has
|
||||
completed.
|
||||
|
||||
Raises:
|
||||
StopIteration: If the coroutine has completed its execution.
|
||||
Exception: If an unexpected error occurs during the execution of the tick.
|
||||
"""
|
||||
|
||||
if self.coroutine == None:
|
||||
self.coroutine = self.execute_async(context)
|
||||
try:
|
||||
|
@ -86,6 +157,21 @@ class AsyncOperatorMixin:
|
|||
print("Exception Thrown in Tick:", e)
|
||||
|
||||
def execute(self, context):
|
||||
"""Execute the modal operation based on the context.
|
||||
|
||||
This function checks if the application is running in the background. If
|
||||
it is, it continuously ticks until the operation is complete. If not, it
|
||||
sets up a timer for the modal operation and adds the modal handler to
|
||||
the window manager, allowing the operation to run in a modal state.
|
||||
|
||||
Args:
|
||||
context (bpy.types.Context): The context in which the operation is executed.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the status of the operation, either
|
||||
{'FINISHED'} if completed or {'RUNNING_MODAL'} if running in modal.
|
||||
"""
|
||||
|
||||
if bpy.app.background:
|
||||
# running in background - don't run as modal,
|
||||
# otherwise tests all fail
|
||||
|
@ -105,6 +191,17 @@ class AsyncTestOperator(bpy.types.Operator, AsyncOperatorMixin):
|
|||
bl_options = {'REGISTER', 'UNDO', 'BLOCKING'}
|
||||
|
||||
async def execute_async(self, context):
|
||||
"""Execute an asynchronous operation with a progress indicator.
|
||||
|
||||
This function runs a loop 100 times, calling an asynchronous function to
|
||||
report progress for each iteration. It is designed to be used in an
|
||||
asynchronous context where the progress of a task needs to be tracked
|
||||
and reported.
|
||||
|
||||
Args:
|
||||
context: The context in which the asynchronous operation is executed.
|
||||
"""
|
||||
|
||||
for x in range(100):
|
||||
await progress_async("Async test:", x)
|
||||
|
||||
|
|
|
@ -47,7 +47,26 @@ def copy_compbuf_data(inbuf, outbuf):
|
|||
outbuf[:] = inbuf[:]
|
||||
|
||||
|
||||
def restrictbuf(inbuf, outbuf): # scale down array....
|
||||
def restrictbuf(inbuf, outbuf):
|
||||
"""Restrict the resolution of an input buffer to match an output buffer.
|
||||
|
||||
This function scales down the input buffer `inbuf` to fit the dimensions
|
||||
of the output buffer `outbuf`. It computes the average of the
|
||||
neighboring pixels in the input buffer to create a downsampled version
|
||||
in the output buffer. The method used for downsampling can vary based on
|
||||
the dimensions of the input and output buffers, utilizing either a
|
||||
simple averaging method or a more complex numpy-based approach.
|
||||
|
||||
Args:
|
||||
inbuf (numpy.ndarray): The input buffer to be downsampled, expected to be
|
||||
a 2D array.
|
||||
outbuf (numpy.ndarray): The output buffer where the downsampled result will
|
||||
be stored, also expected to be a 2D array.
|
||||
|
||||
Returns:
|
||||
None: The function modifies `outbuf` in place.
|
||||
"""
|
||||
# scale down array....
|
||||
|
||||
inx = inbuf.shape[0]
|
||||
iny = inbuf.shape[1]
|
||||
|
@ -132,6 +151,21 @@ def restrictbuf(inbuf, outbuf): # scale down array....
|
|||
|
||||
|
||||
def prolongate(inbuf, outbuf):
|
||||
"""Prolongate an input buffer to a larger output buffer.
|
||||
|
||||
This function takes an input buffer and enlarges it to fit the
|
||||
dimensions of the output buffer. It uses different methods to achieve
|
||||
this based on the scaling factors derived from the input and output
|
||||
dimensions. The function can handle specific cases where the scaling
|
||||
factors are exactly 0.5, as well as a general case that applies a
|
||||
bilinear interpolation technique for resizing.
|
||||
|
||||
Args:
|
||||
inbuf (numpy.ndarray): The input buffer to be enlarged, expected to be a 2D array.
|
||||
outbuf (numpy.ndarray): The output buffer where the enlarged data will be stored,
|
||||
expected to be a 2D array of larger dimensions than inbuf.
|
||||
"""
|
||||
|
||||
|
||||
inx = inbuf.shape[0]
|
||||
iny = inbuf.shape[1]
|
||||
|
@ -221,6 +255,24 @@ def idx(r, c, cols):
|
|||
|
||||
# smooth u using f at level
|
||||
def smooth(U, F, linbcgiterations, planar):
|
||||
"""Smooth a matrix U using a filter F at a specified level.
|
||||
|
||||
This function applies a smoothing operation on the input matrix U using
|
||||
the filter F. It utilizes the linear Biconjugate Gradient method for the
|
||||
smoothing process. The number of iterations for the linear BCG method is
|
||||
specified by linbcgiterations, and the planar parameter indicates
|
||||
whether the operation is to be performed in a planar manner.
|
||||
|
||||
Args:
|
||||
U (numpy.ndarray): The input matrix to be smoothed.
|
||||
F (numpy.ndarray): The filter used for smoothing.
|
||||
linbcgiterations (int): The number of iterations for the linear BCG method.
|
||||
planar (bool): A flag indicating whether to perform the operation in a planar manner.
|
||||
|
||||
Returns:
|
||||
None: This function modifies the input matrix U in place.
|
||||
"""
|
||||
|
||||
|
||||
iter = 0
|
||||
err = 0
|
||||
|
@ -234,6 +286,24 @@ def smooth(U, F, linbcgiterations, planar):
|
|||
|
||||
|
||||
def calculate_defect(D, U, F):
|
||||
"""Calculate the defect of a grid based on the input fields.
|
||||
|
||||
This function computes the defect values for a grid by comparing the
|
||||
input field `F` with the values in the grid `U`. The defect is
|
||||
calculated using finite difference approximations, taking into account
|
||||
the neighboring values in the grid. The results are stored in the output
|
||||
array `D`, which is modified in place.
|
||||
|
||||
Args:
|
||||
D (ndarray): A 2D array where the defect values will be stored.
|
||||
U (ndarray): A 2D array representing the current state of the grid.
|
||||
F (ndarray): A 2D array representing the target field to compare against.
|
||||
|
||||
Returns:
|
||||
None: The function modifies the array `D` in place and does not return a
|
||||
value.
|
||||
"""
|
||||
|
||||
|
||||
sx = F.shape[0]
|
||||
sy = F.shape[1]
|
||||
|
@ -277,6 +347,40 @@ def add_correction(U, C):
|
|||
|
||||
|
||||
def solve_pde_multigrid(F, U, vcycleiterations, linbcgiterations, smoothiterations, mins, levels, useplanar, planar):
|
||||
"""Solve a partial differential equation using a multigrid method.
|
||||
|
||||
This function implements a multigrid algorithm to solve a given partial
|
||||
differential equation (PDE). It operates on a grid of varying
|
||||
resolutions, applying smoothing and correction steps iteratively to
|
||||
converge towards the solution. The algorithm consists of several key
|
||||
phases: restriction of the right-hand side to coarser grids, solving on
|
||||
the coarsest grid, and then interpolating corrections back to finer
|
||||
grids. The process is repeated for a specified number of V-cycle
|
||||
iterations.
|
||||
|
||||
Args:
|
||||
F (numpy.ndarray): The right-hand side of the PDE represented as a 2D array.
|
||||
U (numpy.ndarray): The initial guess for the solution, which will be updated in place.
|
||||
vcycleiterations (int): The number of V-cycle iterations to perform.
|
||||
linbcgiterations (int): The number of iterations for the linear solver used in smoothing.
|
||||
smoothiterations (int): The number of smoothing iterations to apply at each level.
|
||||
mins (int): Minimum grid size (not used in the current implementation).
|
||||
levels (int): The number of levels in the multigrid hierarchy.
|
||||
useplanar (bool): A flag indicating whether to use planar information during the solution
|
||||
process.
|
||||
planar (numpy.ndarray): A 2D array indicating planar information for the grid.
|
||||
|
||||
Returns:
|
||||
None: The function modifies the input array U in place to contain the final
|
||||
solution.
|
||||
|
||||
Note:
|
||||
The function assumes that the input arrays F and U have compatible
|
||||
shapes
|
||||
and that the planar array is appropriately defined for the problem
|
||||
context.
|
||||
"""
|
||||
|
||||
|
||||
xmax = F.shape[0]
|
||||
ymax = F.shape[1]
|
||||
|
@ -422,6 +526,23 @@ def asolve(b, x):
|
|||
|
||||
|
||||
def atimes(x, res):
|
||||
"""Apply a discrete Laplacian operator to a 2D array.
|
||||
|
||||
This function computes the discrete Laplacian of a given 2D array `x`
|
||||
and stores the result in the `res` array. The Laplacian is calculated
|
||||
using finite difference methods, which involve summing the values of
|
||||
neighboring elements and applying specific boundary conditions for the
|
||||
edges and corners of the array.
|
||||
|
||||
Args:
|
||||
x (numpy.ndarray): A 2D array representing the input values.
|
||||
res (numpy.ndarray): A 2D array where the result will be stored. It must have the same shape
|
||||
as `x`.
|
||||
|
||||
Returns:
|
||||
None: The result is stored directly in the `res` array.
|
||||
"""
|
||||
|
||||
res[1:-1, 1:-1] = x[:-2, 1:-1]+x[2:, 1:-1] + \
|
||||
x[1:-1, :-2]+x[1:-1, 2:] - 4*x[1:-1, 1:-1]
|
||||
# sides
|
||||
|
@ -437,6 +558,26 @@ def atimes(x, res):
|
|||
|
||||
|
||||
def snrm(n, sx, itol):
|
||||
"""Calculate the square root of the sum of squares or the maximum absolute
|
||||
value.
|
||||
|
||||
This function computes a value based on the input parameters. If the
|
||||
tolerance level (itol) is less than or equal to 3, it calculates the
|
||||
square root of the sum of squares of the input array (sx). If the
|
||||
tolerance level is greater than 3, it returns the maximum absolute value
|
||||
from the input array.
|
||||
|
||||
Args:
|
||||
n (int): An integer parameter, though it is not used in the current
|
||||
implementation.
|
||||
sx (numpy.ndarray): A numpy array of numeric values.
|
||||
itol (int): An integer that determines which calculation to perform.
|
||||
|
||||
Returns:
|
||||
float: The square root of the sum of squares if itol <= 3, otherwise the
|
||||
maximum absolute value.
|
||||
"""
|
||||
|
||||
|
||||
if (itol <= 3):
|
||||
temp = sx*sx
|
||||
|
@ -453,6 +594,32 @@ def snrm(n, sx, itol):
|
|||
|
||||
|
||||
def linbcg(n, b, x, itol, tol, itmax, iter, err, rows, cols, planar):
|
||||
"""Solve a linear system using the Biconjugate Gradient Method.
|
||||
|
||||
This function implements the Biconjugate Gradient Method as described in
|
||||
Numerical Recipes in C. It iteratively refines the solution to a linear
|
||||
system of equations defined by the matrix-vector product. The method is
|
||||
particularly useful for large, sparse systems where direct methods are
|
||||
inefficient. The function takes various parameters to control the
|
||||
iteration process and convergence criteria.
|
||||
|
||||
Args:
|
||||
n (int): The size of the linear system.
|
||||
b (numpy.ndarray): The right-hand side vector of the linear system.
|
||||
x (numpy.ndarray): The initial guess for the solution vector.
|
||||
itol (int): The type of norm to use for convergence checks.
|
||||
tol (float): The tolerance for convergence.
|
||||
itmax (int): The maximum number of iterations allowed.
|
||||
iter (int): The current iteration count (should be initialized to 0).
|
||||
err (float): The error estimate (should be initialized).
|
||||
rows (int): The number of rows in the matrix.
|
||||
cols (int): The number of columns in the matrix.
|
||||
planar (bool): A flag indicating if the problem is planar.
|
||||
|
||||
Returns:
|
||||
None: The solution is stored in the input array `x`.
|
||||
"""
|
||||
|
||||
|
||||
p = numpy.zeros((cols, rows))
|
||||
pp = numpy.zeros((cols, rows))
|
||||
|
@ -548,6 +715,18 @@ def linbcg(n, b, x, itol, tol, itmax, iter, err, rows, cols, planar):
|
|||
|
||||
|
||||
def numpysave(a, iname):
|
||||
"""Save a NumPy array as an image file in OpenEXR format.
|
||||
|
||||
This function takes a NumPy array and saves it as an image file using
|
||||
Blender's rendering capabilities. It configures the image settings to
|
||||
use the OpenEXR format with black and white color mode and a color depth
|
||||
of 32 bits. The rendered image is saved to the specified filename.
|
||||
|
||||
Args:
|
||||
a (numpy.ndarray): The NumPy array to be saved as an image.
|
||||
iname (str): The filename (including path) where the image will be saved.
|
||||
"""
|
||||
|
||||
inamebase = bpy.path.basename(iname)
|
||||
|
||||
i = numpytoimage(a, inamebase)
|
||||
|
@ -562,6 +741,24 @@ def numpysave(a, iname):
|
|||
|
||||
|
||||
def numpytoimage(a, iname):
|
||||
"""Convert a NumPy array to a Blender image.
|
||||
|
||||
This function takes a NumPy array and converts it into a Blender image.
|
||||
It first checks if an image with the specified name and dimensions
|
||||
already exists in Blender. If it does, that image is used; otherwise, a
|
||||
new image is created with the specified name and dimensions. The
|
||||
function then reshapes the NumPy array to match the image format and
|
||||
assigns the pixel data to the image.
|
||||
|
||||
Args:
|
||||
a (numpy.ndarray): A 2D NumPy array representing the pixel data of the image.
|
||||
iname (str): The name to assign to the Blender image.
|
||||
|
||||
Returns:
|
||||
bpy.types.Image: The Blender image created or modified with the pixel data from the NumPy
|
||||
array.
|
||||
"""
|
||||
|
||||
t = time.time()
|
||||
print('Numpy to Image - Here')
|
||||
t = time.time()
|
||||
|
@ -592,6 +789,25 @@ def numpytoimage(a, iname):
|
|||
|
||||
|
||||
def imagetonumpy(i):
|
||||
"""Convert an image to a NumPy array.
|
||||
|
||||
This function takes an image object and converts its pixel data into a
|
||||
NumPy array. It first retrieves the pixel data from the image, then
|
||||
reshapes and rearranges it to match the image's dimensions. The
|
||||
resulting array is structured such that the height and width of the
|
||||
image are preserved, and the color channels are appropriately ordered.
|
||||
|
||||
Args:
|
||||
i (Image): An image object that contains pixel data.
|
||||
|
||||
Returns:
|
||||
numpy.ndarray: A 2D NumPy array representing the pixel data of the image.
|
||||
|
||||
Note:
|
||||
The function optimizes performance by directly accessing pixel data
|
||||
instead of using slower methods.
|
||||
"""
|
||||
|
||||
t = time.time()
|
||||
inc = 0
|
||||
|
||||
|
@ -618,6 +834,23 @@ def imagetonumpy(i):
|
|||
|
||||
|
||||
def tonemap(i, exponent):
|
||||
"""Apply tone mapping to an image array.
|
||||
|
||||
This function performs tone mapping on the input image array by first
|
||||
filtering out values that are excessively high, which may indicate that
|
||||
the depth buffer was not written correctly. It then normalizes the
|
||||
values between the minimum and maximum heights, and finally applies an
|
||||
exponentiation to adjust the brightness of the image.
|
||||
|
||||
Args:
|
||||
i (numpy.ndarray): A numpy array representing the image data.
|
||||
exponent (float): The exponent used for adjusting the brightness
|
||||
of the normalized image.
|
||||
|
||||
Returns:
|
||||
None: The function modifies the input array in place.
|
||||
"""
|
||||
|
||||
# if depth buffer never got written it gets set
|
||||
# to a great big value (10000000000.0)
|
||||
# filter out anything within an order of magnitude of it
|
||||
|
@ -631,11 +864,43 @@ def tonemap(i, exponent):
|
|||
|
||||
|
||||
def vert(column, row, z, XYscaling, Zscaling):
|
||||
""" Create a Single Vert """
|
||||
"""Create a single vertex in 3D space.
|
||||
|
||||
This function calculates the 3D coordinates of a vertex based on the
|
||||
provided column and row values, as well as scaling factors for the X-Y
|
||||
and Z dimensions. The resulting coordinates are scaled accordingly to
|
||||
fit within a specified 3D space.
|
||||
|
||||
Args:
|
||||
column (float): The column value representing the X coordinate.
|
||||
row (float): The row value representing the Y coordinate.
|
||||
z (float): The Z coordinate value.
|
||||
XYscaling (float): The scaling factor for the X and Y coordinates.
|
||||
Zscaling (float): The scaling factor for the Z coordinate.
|
||||
|
||||
Returns:
|
||||
tuple: A tuple containing the scaled X, Y, and Z coordinates.
|
||||
"""
|
||||
return column * XYscaling, row * XYscaling, z * Zscaling
|
||||
|
||||
|
||||
def buildMesh(mesh_z, br):
|
||||
"""Build a 3D mesh from a height map and apply transformations.
|
||||
|
||||
This function constructs a 3D mesh based on the provided height map
|
||||
(mesh_z) and applies various transformations such as scaling and
|
||||
positioning based on the parameters defined in the br object. It first
|
||||
removes any existing BasReliefMesh objects from the scene, then creates
|
||||
a new mesh from the height data, and finally applies decimation if the
|
||||
specified ratio is within acceptable limits.
|
||||
|
||||
Args:
|
||||
mesh_z (numpy.ndarray): A 2D array representing the height values
|
||||
for the mesh vertices.
|
||||
br (object): An object containing properties for width, height,
|
||||
thickness, justification, and decimation ratio.
|
||||
"""
|
||||
|
||||
global rows
|
||||
global size
|
||||
scale = 1
|
||||
|
@ -710,6 +975,26 @@ def buildMesh(mesh_z, br):
|
|||
|
||||
|
||||
def renderScene(width, height, bit_diameter, passes_per_radius, make_nodes, view_layer):
|
||||
"""Render a scene using Blender's Cycles engine.
|
||||
|
||||
This function switches the rendering engine to Cycles, sets up the
|
||||
necessary nodes for depth rendering if specified, and configures the
|
||||
render resolution based on the provided parameters. It ensures that the
|
||||
scene is in object mode before rendering and restores the original
|
||||
rendering engine after the process is complete.
|
||||
|
||||
Args:
|
||||
width (int): The width of the render in pixels.
|
||||
height (int): The height of the render in pixels.
|
||||
bit_diameter (float): The diameter used to calculate the number of passes.
|
||||
passes_per_radius (int): The number of passes per radius for rendering.
|
||||
make_nodes (bool): A flag indicating whether to create render nodes.
|
||||
view_layer (str): The name of the view layer to be rendered.
|
||||
|
||||
Returns:
|
||||
None: This function does not return any value.
|
||||
"""
|
||||
|
||||
print("Rendering Scene")
|
||||
scene = bpy.context.scene
|
||||
# make sure we're in object mode or else bad things happen
|
||||
|
@ -755,6 +1040,47 @@ def renderScene(width, height, bit_diameter, passes_per_radius, make_nodes, view
|
|||
|
||||
|
||||
def problemAreas(br):
|
||||
"""Process image data to identify problem areas based on silhouette
|
||||
thresholds.
|
||||
|
||||
This function analyzes an image and computes gradients to detect and
|
||||
recover silhouettes based on specified parameters. It utilizes various
|
||||
settings from the provided `br` object to adjust the processing,
|
||||
including silhouette thresholds, scaling factors, and iterations for
|
||||
smoothing and recovery. The function also handles image scaling and
|
||||
applies a gradient mask if specified. The resulting data is then
|
||||
converted back into an image format for further use.
|
||||
|
||||
Args:
|
||||
br (object): An object containing various parameters for processing, including:
|
||||
- use_image_source (bool): Flag to determine if a specific image source
|
||||
should be used.
|
||||
- source_image_name (str): Name of the source image if
|
||||
`use_image_source` is True.
|
||||
- silhouette_threshold (float): Threshold for silhouette detection.
|
||||
- recover_silhouettes (bool): Flag to indicate if silhouettes should be
|
||||
recovered.
|
||||
- silhouette_scale (float): Scaling factor for silhouette recovery.
|
||||
- min_gridsize (int): Minimum grid size for processing.
|
||||
- smooth_iterations (int): Number of iterations for smoothing.
|
||||
- vcycle_iterations (int): Number of iterations for V-cycle processing.
|
||||
- linbcg_iterations (int): Number of iterations for linear BCG
|
||||
processing.
|
||||
- use_planar (bool): Flag to indicate if planar processing should be
|
||||
used.
|
||||
- gradient_scaling_mask_use (bool): Flag to indicate if a gradient
|
||||
scaling mask should be used.
|
||||
- gradient_scaling_mask_name (str): Name of the gradient scaling mask
|
||||
image.
|
||||
- depth_exponent (float): Exponent for depth adjustment.
|
||||
- silhouette_exponent (int): Exponent for silhouette recovery.
|
||||
- attenuation (float): Attenuation factor for processing.
|
||||
|
||||
Returns:
|
||||
None: The function does not return a value but processes the image data and
|
||||
saves the result.
|
||||
"""
|
||||
|
||||
t = time.time()
|
||||
if br.use_image_source:
|
||||
i = bpy.data.images[br.source_image_name]
|
||||
|
@ -841,6 +1167,50 @@ def problemAreas(br):
|
|||
|
||||
|
||||
def relief(br):
|
||||
"""Process an image to enhance relief features.
|
||||
|
||||
This function takes an input image and applies various processing
|
||||
techniques to enhance the relief features based on the provided
|
||||
parameters. It utilizes gradient calculations, silhouette recovery, and
|
||||
optional detail enhancement through Fourier transforms. The processed
|
||||
image is then used to build a mesh representation.
|
||||
|
||||
Args:
|
||||
br (object): An object containing various parameters for the relief processing,
|
||||
including:
|
||||
- use_image_source (bool): Whether to use a specified image source.
|
||||
- source_image_name (str): The name of the source image.
|
||||
- silhouette_threshold (float): Threshold for silhouette detection.
|
||||
- recover_silhouettes (bool): Flag to indicate if silhouettes should be
|
||||
recovered.
|
||||
- silhouette_scale (float): Scale factor for silhouette recovery.
|
||||
- min_gridsize (int): Minimum grid size for processing.
|
||||
- smooth_iterations (int): Number of iterations for smoothing.
|
||||
- vcycle_iterations (int): Number of iterations for V-cycle processing.
|
||||
- linbcg_iterations (int): Number of iterations for linear BCG
|
||||
processing.
|
||||
- use_planar (bool): Flag to indicate if planar processing should be
|
||||
used.
|
||||
- gradient_scaling_mask_use (bool): Flag to indicate if a gradient
|
||||
scaling mask should be used.
|
||||
- gradient_scaling_mask_name (str): Name of the gradient scaling mask
|
||||
image.
|
||||
- depth_exponent (float): Exponent for depth adjustment.
|
||||
- attenuation (float): Attenuation factor for the processing.
|
||||
- detail_enhancement_use (bool): Flag to indicate if detail enhancement
|
||||
should be applied.
|
||||
- detail_enhancement_freq (float): Frequency for detail enhancement.
|
||||
- detail_enhancement_amount (float): Amount of detail enhancement to
|
||||
apply.
|
||||
|
||||
Returns:
|
||||
None: The function processes the image and builds a mesh but does not return a
|
||||
value.
|
||||
|
||||
Raises:
|
||||
ReliefError: If the input image is blank or invalid.
|
||||
"""
|
||||
|
||||
t = time.time()
|
||||
|
||||
if br.use_image_source:
|
||||
|
@ -1223,10 +1593,41 @@ class BASRELIEF_Panel(bpy.types.Panel):
|
|||
# self.layout.menu("CAM_CUTTER_MT_presets", text="CAM Cutter")
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
"""Check if the current render engine is compatible.
|
||||
|
||||
This class method checks whether the render engine specified in the
|
||||
provided context is included in the list of compatible engines. It
|
||||
accesses the render settings from the context and verifies if the engine
|
||||
is part of the predefined compatible engines.
|
||||
|
||||
Args:
|
||||
context (Context): The context containing the scene and render settings.
|
||||
|
||||
Returns:
|
||||
bool: True if the render engine is compatible, False otherwise.
|
||||
"""
|
||||
|
||||
rd = context.scene.render
|
||||
return rd.engine in cls.COMPAT_ENGINES
|
||||
|
||||
def draw(self, context):
|
||||
"""Draw the user interface for the bas relief settings.
|
||||
|
||||
This method constructs the layout for the bas relief settings in the
|
||||
Blender user interface. It includes various properties and options that
|
||||
allow users to configure the bas relief calculations, such as selecting
|
||||
images, adjusting parameters, and setting justification options. The
|
||||
layout is dynamically updated based on user selections, providing a
|
||||
comprehensive interface for manipulating bas relief settings.
|
||||
|
||||
Args:
|
||||
context (bpy.context): The context in which the UI is being drawn.
|
||||
|
||||
Returns:
|
||||
None: This method does not return any value; it modifies the layout
|
||||
directly.
|
||||
"""
|
||||
|
||||
layout = self.layout
|
||||
# print(dir(layout))
|
||||
s = bpy.context.scene
|
||||
|
@ -1307,6 +1708,21 @@ class DoBasRelief(bpy.types.Operator):
|
|||
processes = []
|
||||
|
||||
def execute(self, context):
|
||||
"""Execute the relief rendering process based on the provided context.
|
||||
|
||||
This function retrieves the scene and its associated bas relief
|
||||
settings. It checks if an image source is being used and sets the view
|
||||
layer name accordingly. The function then attempts to render the scene
|
||||
and generate the relief. If any errors occur during these processes,
|
||||
they are reported, and the operation is canceled.
|
||||
|
||||
Args:
|
||||
context: The context in which the function is executed.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of the operation, either
|
||||
"""
|
||||
|
||||
s = bpy.context.scene
|
||||
br = s.basreliefsettings
|
||||
if not br.use_image_source and br.view_layer_name == "":
|
||||
|
@ -1340,6 +1756,23 @@ class ProblemAreas(bpy.types.Operator):
|
|||
# return context.active_object is not None
|
||||
|
||||
def execute(self, context):
|
||||
"""Execute the operation related to the bas relief settings in the current
|
||||
scene.
|
||||
|
||||
This method retrieves the current scene from the Blender context and
|
||||
accesses the bas relief settings. It then calls the `problemAreas`
|
||||
function to perform operations related to those settings. The method
|
||||
concludes by returning a status indicating that the operation has
|
||||
finished successfully.
|
||||
|
||||
Args:
|
||||
context (bpy.context): The current Blender context, which provides access
|
||||
|
||||
Returns:
|
||||
dict: A dictionary with a status key indicating the operation result,
|
||||
specifically {'FINISHED'}.
|
||||
"""
|
||||
|
||||
s = bpy.context.scene
|
||||
br = s.basreliefsettings
|
||||
problemAreas(br)
|
||||
|
@ -1347,6 +1780,19 @@ class ProblemAreas(bpy.types.Operator):
|
|||
|
||||
|
||||
def get_panels():
|
||||
"""Retrieve a tuple of panel settings and related components.
|
||||
|
||||
This function returns a tuple containing various components related to
|
||||
Bas Relief settings. The components include BasReliefsettings,
|
||||
BASRELIEF_Panel, DoBasRelief, and ProblemAreas, which are likely used in
|
||||
the context of a graphical user interface or a specific application
|
||||
domain.
|
||||
|
||||
Returns:
|
||||
tuple: A tuple containing the BasReliefsettings, BASRELIEF_Panel,
|
||||
DoBasRelief, and ProblemAreas components.
|
||||
"""
|
||||
|
||||
return(
|
||||
BasReliefsettings,
|
||||
BASRELIEF_Panel,
|
||||
|
@ -1356,6 +1802,17 @@ def get_panels():
|
|||
|
||||
|
||||
def register():
|
||||
"""Register the necessary classes and properties for the add-on.
|
||||
|
||||
This function registers all the panels defined in the add-on by
|
||||
iterating through the list of panels returned by the `get_panels()`
|
||||
function. It also adds a new property, `basreliefsettings`, to the
|
||||
`Scene` type, which is a pointer property that references the
|
||||
`BasReliefsettings` class. This setup is essential for the proper
|
||||
functioning of the add-on, allowing users to access and modify the
|
||||
settings related to bas relief.
|
||||
"""
|
||||
|
||||
for p in get_panels():
|
||||
bpy.utils.register_class(p)
|
||||
s = bpy.types.Scene
|
||||
|
@ -1365,6 +1822,15 @@ def register():
|
|||
|
||||
|
||||
def unregister():
|
||||
"""Unregister all panels and remove basreliefsettings from the Scene type.
|
||||
|
||||
This function iterates through all registered panels and unregisters
|
||||
each one using Blender's utility functions. Additionally, it removes the
|
||||
basreliefsettings attribute from the Scene type, ensuring that any
|
||||
settings related to bas relief are no longer accessible in the current
|
||||
Blender session.
|
||||
"""
|
||||
|
||||
for p in get_panels():
|
||||
bpy.utils.unregister_class(p)
|
||||
s = bpy.types.Scene
|
||||
|
|
|
@ -22,6 +22,25 @@ from . import simple
|
|||
|
||||
|
||||
def addBridge(x, y, rot, sizex, sizey):
|
||||
"""Add a bridge mesh object to the scene.
|
||||
|
||||
This function creates a bridge by adding a primitive plane to the
|
||||
Blender scene, adjusting its dimensions, and then converting it into a
|
||||
curve. The bridge is positioned based on the provided coordinates and
|
||||
rotation. The size of the bridge is determined by the `sizex` and
|
||||
`sizey` parameters.
|
||||
|
||||
Args:
|
||||
x (float): The x-coordinate for the bridge's location.
|
||||
y (float): The y-coordinate for the bridge's location.
|
||||
rot (float): The rotation angle around the z-axis in radians.
|
||||
sizex (float): The width of the bridge.
|
||||
sizey (float): The height of the bridge.
|
||||
|
||||
Returns:
|
||||
bpy.types.Object: The created bridge object.
|
||||
"""
|
||||
|
||||
bpy.ops.mesh.primitive_plane_add(size=sizey*2, calc_uvs=True, enter_editmode=False, align='WORLD',
|
||||
location=(0, 0, 0), rotation=(0, 0, 0))
|
||||
b = bpy.context.active_object
|
||||
|
@ -43,7 +62,25 @@ def addBridge(x, y, rot, sizex, sizey):
|
|||
|
||||
|
||||
def addAutoBridges(o):
|
||||
"""Attempt to Add Auto Bridges as Set of Curves"""
|
||||
"""Attempt to add auto bridges as a set of curves.
|
||||
|
||||
This function creates a collection of bridges based on the provided
|
||||
object. It checks if a collection for bridges already exists; if not, it
|
||||
creates a new one. The function then iterates through the objects in the
|
||||
input object, processing curves and meshes to generate bridge
|
||||
geometries. For each geometry, it calculates the necessary points and
|
||||
adds bridges at various orientations based on the geometry's bounds.
|
||||
|
||||
Args:
|
||||
o (object): An object containing properties such as
|
||||
bridges_collection_name, bridges_width, and cutter_diameter,
|
||||
along with a list of objects to process.
|
||||
|
||||
Returns:
|
||||
None: This function does not return a value but modifies the
|
||||
Blender context by adding bridge objects to the specified
|
||||
collection.
|
||||
"""
|
||||
utils.getOperationSources(o)
|
||||
bridgecollectionname = o.bridges_collection_name
|
||||
if bridgecollectionname == '' or bpy.data.collections.get(bridgecollectionname) is None:
|
||||
|
@ -84,6 +121,20 @@ def addAutoBridges(o):
|
|||
|
||||
|
||||
def getBridgesPoly(o):
|
||||
"""Generate and prepare bridge polygons from a Blender object.
|
||||
|
||||
This function checks if the provided object has an attribute for bridge
|
||||
polygons. If not, it retrieves the bridge collection, selects all curve
|
||||
objects within that collection, duplicates them, and joins them into a
|
||||
single object. The resulting shape is then converted to a Shapely
|
||||
geometry. The function buffers the resulting polygon to account for the
|
||||
cutter diameter and prepares the boundary and polygon for further
|
||||
processing.
|
||||
|
||||
Args:
|
||||
o (object): An object containing properties related to bridge
|
||||
"""
|
||||
|
||||
if not hasattr(o, 'bridgespolyorig'):
|
||||
bridgecollectionname = o.bridges_collection_name
|
||||
bridgecollection = bpy.data.collections[bridgecollectionname]
|
||||
|
@ -109,7 +160,25 @@ def getBridgesPoly(o):
|
|||
|
||||
|
||||
def useBridges(ch, o):
|
||||
"""This Adds Bridges to Chunks, Takes the Bridge-objects Collection and Uses the Curves Inside It as Bridges."""
|
||||
"""Add bridges to chunks using a collection of bridge objects.
|
||||
|
||||
This function takes a collection of bridge objects and uses the curves
|
||||
within it to create bridges over the specified chunks. It calculates the
|
||||
necessary points for the bridges based on the height and geometry of the
|
||||
chunks and the bridge objects. The function also handles intersections
|
||||
with the bridge polygon and adjusts the points accordingly. Finally, it
|
||||
generates a mesh for the bridges and converts it into a curve object in
|
||||
Blender.
|
||||
|
||||
Args:
|
||||
ch (Chunk): The chunk object to which bridges will be added.
|
||||
o (ObjectOptions): An object containing options such as bridge height,
|
||||
collection name, and other parameters.
|
||||
|
||||
Returns:
|
||||
None: The function modifies the chunk object in place and does not return a
|
||||
value.
|
||||
"""
|
||||
bridgecollectionname = o.bridges_collection_name
|
||||
bridgecollection = bpy.data.collections[bridgecollectionname]
|
||||
if len(bridgecollection.objects) > 0:
|
||||
|
@ -263,6 +332,21 @@ def useBridges(ch, o):
|
|||
|
||||
|
||||
def auto_cut_bridge(o):
|
||||
"""Automatically processes a bridge collection.
|
||||
|
||||
This function retrieves a bridge collection by its name from the
|
||||
provided object and checks if there are any objects within that
|
||||
collection. If there are objects present, it prints "bridges" to the
|
||||
console. This function is useful for managing and processing bridge
|
||||
collections in a 3D environment.
|
||||
|
||||
Args:
|
||||
o (object): An object that contains the attribute
|
||||
|
||||
Returns:
|
||||
None: This function does not return any value.
|
||||
"""
|
||||
|
||||
bridgecollectionname = o.bridges_collection_name
|
||||
bridgecollection = bpy.data.collections[bridgecollectionname]
|
||||
if len(bridgecollection.objects) > 0:
|
||||
|
|
|
@ -30,8 +30,21 @@ from .simple import (
|
|||
|
||||
|
||||
def getCutterBullet(o):
|
||||
"""Cutter for Rigidbody Simulation Collisions
|
||||
Note that Everything Is 100x Bigger for Simulation Precision."""
|
||||
"""Create a cutter for Rigidbody simulation collisions.
|
||||
|
||||
This function generates a 3D cutter object based on the specified cutter
|
||||
type and parameters. It supports various cutter types including 'END',
|
||||
'BALLNOSE', 'VCARVE', 'CYLCONE', 'BALLCONE', and 'CUSTOM'. The function
|
||||
also applies rigid body physics to the created cutter for realistic
|
||||
simulation in Blender.
|
||||
|
||||
Args:
|
||||
o (object): An object containing properties such as cutter_type, cutter_diameter,
|
||||
cutter_tip_angle, ball_radius, and cutter_object_name.
|
||||
|
||||
Returns:
|
||||
bpy.types.Object: The created cutter object with rigid body properties applied.
|
||||
"""
|
||||
|
||||
s = bpy.context.scene
|
||||
if s.objects.get('cutter') is not None:
|
||||
|
@ -161,6 +174,21 @@ def getCutterBullet(o):
|
|||
|
||||
|
||||
def subdivideLongEdges(ob, threshold):
|
||||
"""Subdivide edges of a mesh object that exceed a specified length.
|
||||
|
||||
This function iteratively checks the edges of a given mesh object and
|
||||
subdivides those that are longer than a specified threshold. The process
|
||||
involves toggling the edit mode of the object, selecting the long edges,
|
||||
and applying a subdivision operation. The function continues to
|
||||
subdivide until no edges exceed the threshold.
|
||||
|
||||
Args:
|
||||
ob (bpy.types.Object): The Blender object containing the mesh to be
|
||||
subdivided.
|
||||
threshold (float): The length threshold above which edges will be
|
||||
subdivided.
|
||||
"""
|
||||
|
||||
print('Subdividing Long Edges')
|
||||
m = ob.data
|
||||
scale = (ob.scale.x + ob.scale.y + ob.scale.z) / 3
|
||||
|
@ -205,7 +233,21 @@ def subdivideLongEdges(ob, threshold):
|
|||
#
|
||||
|
||||
def prepareBulletCollision(o):
|
||||
"""Prepares All Objects Needed for Sampling with Bullet Collision"""
|
||||
"""Prepares all objects needed for sampling with Bullet collision.
|
||||
|
||||
This function sets up the Bullet physics simulation by preparing the
|
||||
specified objects for collision detection. It begins by cleaning up any
|
||||
existing rigid bodies that are not part of the 'machine' object. Then,
|
||||
it duplicates the collision objects, converts them to mesh if they are
|
||||
curves or fonts, and applies necessary modifiers. The function also
|
||||
handles the subdivision of long edges and configures the rigid body
|
||||
properties for each object. Finally, it scales the 'machine' objects to
|
||||
the simulation scale and steps through the simulation frames to ensure
|
||||
that all objects are up to date.
|
||||
|
||||
Args:
|
||||
o (Object): An object containing properties and settings for
|
||||
"""
|
||||
progress('Preparing Collisions')
|
||||
|
||||
print(o.name)
|
||||
|
@ -283,6 +325,22 @@ def prepareBulletCollision(o):
|
|||
|
||||
|
||||
def cleanupBulletCollision(o):
|
||||
"""Clean up bullet collision objects in the scene.
|
||||
|
||||
This function checks for the presence of a 'machine' object in the
|
||||
Blender scene and removes any rigid body objects that are not part of
|
||||
the 'machine'. If the 'machine' object is present, it scales the machine
|
||||
objects up to the simulation scale and adjusts their locations
|
||||
accordingly.
|
||||
|
||||
Args:
|
||||
o: An object that may be used in the cleanup process (specific usage not
|
||||
detailed).
|
||||
|
||||
Returns:
|
||||
None: This function does not return a value.
|
||||
"""
|
||||
|
||||
if bpy.data.objects.find('machine') > -1:
|
||||
machinepresent = True
|
||||
else:
|
||||
|
@ -303,7 +361,27 @@ def cleanupBulletCollision(o):
|
|||
|
||||
|
||||
def getSampleBullet(cutter, x, y, radius, startz, endz):
|
||||
"""Collision Test for 3 Axis Milling. Is Simplified Compared to the Full 3D Test"""
|
||||
"""Perform a collision test for a 3-axis milling cutter.
|
||||
|
||||
This function simplifies the collision detection process compared to a
|
||||
full 3D test. It utilizes the Blender Python API to perform a convex
|
||||
sweep test on the cutter's position within a specified 3D space. The
|
||||
function checks for collisions between the cutter and other objects in
|
||||
the scene, adjusting for the cutter's radius to determine the effective
|
||||
position of the cutter tip.
|
||||
|
||||
Args:
|
||||
cutter (object): The milling cutter object used for the collision test.
|
||||
x (float): The x-coordinate of the cutter's position.
|
||||
y (float): The y-coordinate of the cutter's position.
|
||||
radius (float): The radius of the cutter, used to adjust the collision detection.
|
||||
startz (float): The starting z-coordinate for the collision test.
|
||||
endz (float): The ending z-coordinate for the collision test.
|
||||
|
||||
Returns:
|
||||
float: The adjusted z-coordinate of the cutter tip if a collision is detected;
|
||||
otherwise, returns a value 10 units below the specified endz.
|
||||
"""
|
||||
scene = bpy.context.scene
|
||||
pos = scene.rigidbody_world.convex_sweep_test(cutter, (x * BULLET_SCALE, y * BULLET_SCALE, startz * BULLET_SCALE),
|
||||
(x * BULLET_SCALE, y * BULLET_SCALE, endz * BULLET_SCALE))
|
||||
|
@ -317,7 +395,27 @@ def getSampleBullet(cutter, x, y, radius, startz, endz):
|
|||
|
||||
|
||||
def getSampleBulletNAxis(cutter, startpoint, endpoint, rotation, cutter_compensation):
|
||||
"""Fully 3D Collision Test for N-Axis Milling"""
|
||||
"""Perform a fully 3D collision test for N-Axis milling.
|
||||
|
||||
This function computes the collision detection between a cutter and a
|
||||
specified path in a 3D space. It takes into account the cutter's
|
||||
rotation and compensation to accurately determine if a collision occurs
|
||||
during the milling process. The function uses Bullet physics for the
|
||||
collision detection and returns the adjusted position of the cutter if a
|
||||
collision is detected.
|
||||
|
||||
Args:
|
||||
cutter (object): The cutter object used in the milling operation.
|
||||
startpoint (Vector): The starting point of the milling path.
|
||||
endpoint (Vector): The ending point of the milling path.
|
||||
rotation (Euler): The rotation applied to the cutter.
|
||||
cutter_compensation (float): The compensation factor for the cutter's position.
|
||||
|
||||
Returns:
|
||||
Vector or None: The adjusted position of the cutter if a collision is
|
||||
detected;
|
||||
otherwise, returns None.
|
||||
"""
|
||||
cutterVec = Vector((0, 0, 1)) * cutter_compensation
|
||||
# cutter compensation vector - cutter physics object has center in the middle, while cam needs the tip position.
|
||||
cutterVec.rotate(Euler(rotation))
|
||||
|
|
|
@ -104,6 +104,19 @@ class CamCurveHatch(Operator):
|
|||
return context.active_object is not None and context.active_object.type in ['CURVE', 'FONT']
|
||||
|
||||
def draw(self, context):
|
||||
"""Draw the layout properties for the given context.
|
||||
|
||||
This method sets up the user interface layout by adding various
|
||||
properties such as angle, distance, offset, height, and pocket type.
|
||||
Depending on the selected pocket type, it conditionally adds additional
|
||||
properties like hull and contour. This allows for a dynamic and
|
||||
customizable interface based on user selections.
|
||||
|
||||
Args:
|
||||
context: The context in which the layout is drawn, typically
|
||||
provided by the calling environment.
|
||||
"""
|
||||
|
||||
layout = self.layout
|
||||
layout.prop(self, 'angle')
|
||||
layout.prop(self, 'distance')
|
||||
|
@ -123,6 +136,25 @@ class CamCurveHatch(Operator):
|
|||
layout.prop(self, 'contour')
|
||||
|
||||
def execute(self, context):
|
||||
"""Execute the crosshatch generation process based on the provided context.
|
||||
|
||||
This method performs a series of operations to create a crosshatch
|
||||
pattern from the active object in the given context. It begins by
|
||||
removing any existing crosshatch elements, setting the object's origin,
|
||||
and determining its dimensions. Depending on the specified parameters,
|
||||
it generates a convex hull, calculates the necessary coordinates for the
|
||||
crosshatch lines, and applies transformations such as rotation and
|
||||
translation. The method also handles intersections with specified bounds
|
||||
or curves and can create contours based on additional settings.
|
||||
|
||||
Args:
|
||||
context (bpy.context): The Blender context containing the active object
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the completion status of the operation,
|
||||
typically {'FINISHED'}.
|
||||
"""
|
||||
|
||||
simple.remove_multiple("crosshatch")
|
||||
ob = context.active_object
|
||||
ob.select_set(True)
|
||||
|
@ -289,6 +321,18 @@ class CamCurvePlate(Operator):
|
|||
)
|
||||
|
||||
def draw(self, context):
|
||||
"""Draw the UI layout for plate properties.
|
||||
|
||||
This method creates a user interface layout for configuring various
|
||||
properties of a plate, including its type, dimensions, hole
|
||||
specifications, and resolution. It dynamically adds properties to the
|
||||
layout based on the selected plate type, allowing users to input
|
||||
relevant parameters.
|
||||
|
||||
Args:
|
||||
context: The context in which the UI is being drawn.
|
||||
"""
|
||||
|
||||
layout = self.layout
|
||||
layout.prop(self, 'plate_type')
|
||||
layout.prop(self, 'width')
|
||||
|
@ -304,6 +348,23 @@ class CamCurvePlate(Operator):
|
|||
layout.prop(self, 'radius')
|
||||
|
||||
def execute(self, context):
|
||||
"""Execute the creation of a plate based on specified parameters.
|
||||
|
||||
This function generates a plate shape in Blender based on the defined
|
||||
attributes such as width, height, radius, and plate type. It supports
|
||||
different plate types including rounded, oval, cove, and bevel. The
|
||||
function also handles the creation of holes in the plate if specified.
|
||||
It utilizes Blender's curve operations to create the geometry and
|
||||
applies various transformations to achieve the desired shape.
|
||||
|
||||
Args:
|
||||
context (bpy.context): The Blender context in which the operation is performed.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of the operation, typically
|
||||
{'FINISHED'} if successful.
|
||||
"""
|
||||
|
||||
left = -self.width / 2 + self.radius
|
||||
bottom = -self.height / 2 + self.radius
|
||||
right = -left
|
||||
|
@ -536,6 +597,25 @@ class CamCurveFlatCone(Operator):
|
|||
)
|
||||
|
||||
def execute(self, context):
|
||||
"""Execute the construction of a geometric shape in Blender.
|
||||
|
||||
This method performs a series of operations to create a geometric shape
|
||||
based on specified dimensions and parameters. It calculates various
|
||||
dimensions needed for the shape, including height and angles, and then
|
||||
uses Blender's operations to create segments, rectangles, and ellipses.
|
||||
The function also handles the positioning and rotation of these shapes
|
||||
within the 3D space of Blender.
|
||||
|
||||
Args:
|
||||
context: The context in which the operation is executed, typically containing
|
||||
information about the current
|
||||
scene and active objects in Blender.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the completion status of the operation,
|
||||
typically {'FINISHED'}.
|
||||
"""
|
||||
|
||||
y = self.small_d / 2
|
||||
z = self.large_d / 2
|
||||
x = self.height
|
||||
|
@ -654,6 +734,22 @@ class CamCurveMortise(Operator):
|
|||
return context.active_object is not None and (context.active_object.type in ['CURVE', 'FONT'])
|
||||
|
||||
def execute(self, context):
|
||||
"""Execute the joinery process based on the provided context.
|
||||
|
||||
This function performs a series of operations to duplicate the active
|
||||
object, convert it to a mesh, and then process its geometry to create
|
||||
joinery features. It extracts vertex coordinates, converts them into a
|
||||
LineString data structure, and applies either variable or fixed finger
|
||||
joinery based on the specified parameters. The function also handles the
|
||||
creation of flexible sides and pockets if required.
|
||||
|
||||
Args:
|
||||
context (bpy.context): The context in which the operation is executed.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the completion status of the operation.
|
||||
"""
|
||||
|
||||
o1 = bpy.context.active_object
|
||||
|
||||
bpy.context.object.data.resolution_u = 60
|
||||
|
@ -783,6 +879,25 @@ class CamCurveInterlock(Operator):
|
|||
)
|
||||
|
||||
def execute(self, context):
|
||||
"""Execute the joinery operation based on the selected objects in the
|
||||
context.
|
||||
|
||||
This function checks the selected objects in the provided context and
|
||||
performs different operations depending on the type of the active
|
||||
object. If the active object is a curve or font and there are selected
|
||||
objects, it duplicates the object, converts it to a mesh, and processes
|
||||
its vertices to create a LineString representation. The function then
|
||||
calculates lengths and applies distributed interlock joinery based on
|
||||
the specified parameters. If no valid objects are selected, it defaults
|
||||
to a single interlock operation at the cursor's location.
|
||||
|
||||
Args:
|
||||
context (bpy.context): The context containing selected objects and active object.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the operation's completion status.
|
||||
"""
|
||||
|
||||
print(len(context.selected_objects),
|
||||
"selected object", context.selected_objects)
|
||||
if len(context.selected_objects) > 0 and (context.active_object.type in ['CURVE', 'FONT']):
|
||||
|
@ -928,6 +1043,20 @@ class CamCurveDrawer(Operator):
|
|||
)
|
||||
|
||||
def draw(self, context):
|
||||
"""Draw the user interface properties for the object.
|
||||
|
||||
This method is responsible for rendering the layout of various
|
||||
properties related to the object's dimensions and specifications. It
|
||||
adds properties such as depth, width, height, finger size, finger
|
||||
tolerance, finger inset, drawer plate thickness, drawer hole diameter,
|
||||
drawer hole offset, and overcut diameter to the layout. The overcut
|
||||
diameter property is only added if the overcut option is enabled.
|
||||
|
||||
Args:
|
||||
context: The context in which the drawing occurs, typically containing
|
||||
information about the current state and environment.
|
||||
"""
|
||||
|
||||
layout = self.layout
|
||||
layout.prop(self, 'depth')
|
||||
layout.prop(self, 'width')
|
||||
|
@ -943,6 +1072,25 @@ class CamCurveDrawer(Operator):
|
|||
layout.prop(self, 'overcut_diameter')
|
||||
|
||||
def execute(self, context):
|
||||
"""Execute the drawer creation process in Blender.
|
||||
|
||||
This method orchestrates the creation of a drawer by calculating the
|
||||
necessary dimensions for the finger joints, creating the base plate, and
|
||||
generating the drawer components such as the back, front, sides, and
|
||||
bottom. It utilizes various helper functions to perform operations like
|
||||
boolean differences and transformations to achieve the desired geometry.
|
||||
The method also handles the placement of the drawer components in the 3D
|
||||
space.
|
||||
|
||||
Args:
|
||||
context (bpy.context): The Blender context that provides access to the current scene and
|
||||
objects.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the completion status of the operation,
|
||||
typically {'FINISHED'}.
|
||||
"""
|
||||
|
||||
height_finger_amt = int(joinery.finger_amount(
|
||||
self.height, self.finger_size))
|
||||
height_finger = (self.height + 0.0004) / height_finger_amt
|
||||
|
@ -1271,6 +1419,24 @@ class CamCurvePuzzle(Operator):
|
|||
)
|
||||
|
||||
def draw(self, context):
|
||||
"""Draws the user interface layout for interlock type properties.
|
||||
|
||||
This method is responsible for creating and displaying the layout of
|
||||
various properties related to different interlock types in the user
|
||||
interface. It dynamically adjusts the layout based on the selected
|
||||
interlock type, allowing users to input relevant parameters such as
|
||||
dimensions, tolerances, and other characteristics specific to the chosen
|
||||
interlock type.
|
||||
|
||||
Args:
|
||||
context: The context in which the layout is being drawn, typically
|
||||
provided by the user interface framework.
|
||||
|
||||
Returns:
|
||||
None: This method does not return any value; it modifies the layout
|
||||
directly.
|
||||
"""
|
||||
|
||||
layout = self.layout
|
||||
layout.prop(self, 'interlock_type')
|
||||
layout.label(text='Puzzle Joint Definition')
|
||||
|
@ -1330,6 +1496,25 @@ class CamCurvePuzzle(Operator):
|
|||
layout.prop(self, 'overcut_diameter')
|
||||
|
||||
def execute(self, context):
|
||||
"""Execute the puzzle joinery process based on the provided context.
|
||||
|
||||
This method processes the selected objects in the given context to
|
||||
perform various types of puzzle joinery operations. It first checks if
|
||||
there are any selected objects and if the active object is a curve. If
|
||||
so, it duplicates the object, applies transformations, and converts it
|
||||
to a mesh. The method then extracts vertex coordinates and performs
|
||||
different joinery operations based on the specified interlock type.
|
||||
Supported interlock types include 'FINGER', 'JOINT', 'BAR', 'ARC',
|
||||
'CURVEBARCURVE', 'CURVEBAR', 'MULTIANGLE', 'T', 'CURVET', 'CORNER',
|
||||
'TILE', and 'OPENCURVE'.
|
||||
|
||||
Args:
|
||||
context (Context): The context containing selected objects and the active object.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the completion status of the operation.
|
||||
"""
|
||||
|
||||
curve_detected = False
|
||||
print(len(context.selected_objects),
|
||||
"selected object", context.selected_objects)
|
||||
|
@ -1544,6 +1729,19 @@ class CamCurveGear(Operator):
|
|||
)
|
||||
|
||||
def draw(self, context):
|
||||
"""Draw the user interface properties for gear settings.
|
||||
|
||||
This method sets up the layout for various gear parameters based on the
|
||||
selected gear type. It dynamically adds properties to the layout for
|
||||
different gear types, allowing users to input specific values for gear
|
||||
design. The properties include gear type, tooth spacing, tooth amount,
|
||||
hole diameter, pressure angle, and backlash. Additional properties are
|
||||
displayed if the gear type is 'PINION' or 'RACK'.
|
||||
|
||||
Args:
|
||||
context: The context in which the layout is being drawn.
|
||||
"""
|
||||
|
||||
layout = self.layout
|
||||
layout.prop(self, 'gear_type')
|
||||
layout.prop(self, 'tooth_spacing')
|
||||
|
@ -1561,6 +1759,23 @@ class CamCurveGear(Operator):
|
|||
layout.prop(self, 'rack_tooth_per_hole')
|
||||
|
||||
def execute(self, context):
|
||||
"""Execute the gear generation process based on the specified gear type.
|
||||
|
||||
This method checks the type of gear to be generated (either 'PINION' or
|
||||
'RACK') and calls the appropriate function from the `involute_gear`
|
||||
module to create the gear or rack with the specified parameters. The
|
||||
parameters include tooth spacing, number of teeth, hole diameter,
|
||||
pressure angle, clearance, backlash, rim size, hub diameter, and spoke
|
||||
amount for pinion gears, and additional parameters for rack gears.
|
||||
|
||||
Args:
|
||||
context: The context in which the execution is taking place.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating that the operation has finished with a key
|
||||
'FINISHED'.
|
||||
"""
|
||||
|
||||
if self.gear_type == 'PINION':
|
||||
involute_gear.gear(mm_per_tooth=self.tooth_spacing, number_of_teeth=self.tooth_amount,
|
||||
hole_diameter=self.hole_diameter, pressure_angle=self.pressure_angle,
|
||||
|
|
|
@ -35,6 +35,20 @@ class CNCCAM_ENGINE(RenderEngine):
|
|||
|
||||
|
||||
def get_panels():
|
||||
"""Retrieve a list of panels for the Blender UI.
|
||||
|
||||
This function compiles a list of UI panels that are compatible with the
|
||||
Blender rendering engine. It excludes certain predefined panels that are
|
||||
not relevant for the current context. The function checks all subclasses
|
||||
of the `bpy.types.Panel` and includes those that have the
|
||||
`COMPAT_ENGINES` attribute set to include 'BLENDER_RENDER', provided
|
||||
they are not in the exclusion list.
|
||||
|
||||
Returns:
|
||||
list: A list of panel classes that are compatible with the
|
||||
Blender rendering engine, excluding specified panels.
|
||||
"""
|
||||
|
||||
exclude_panels = {
|
||||
'RENDER_PT_eevee_performance',
|
||||
'RENDER_PT_opengl_sampling',
|
||||
|
|
|
@ -14,6 +14,24 @@ np.set_printoptions(suppress=True) # suppress scientific notation in subdivide
|
|||
|
||||
|
||||
def import_gcode(context, filepath):
|
||||
"""Import G-code data into the scene.
|
||||
|
||||
This function reads G-code from a specified file and processes it
|
||||
according to the settings defined in the context. It utilizes the
|
||||
GcodeParser to parse the file and classify segments of the model.
|
||||
Depending on the options set in the scene, it may subdivide the model
|
||||
and draw it with or without layer splitting. The time taken for the
|
||||
import process is printed to the console.
|
||||
|
||||
Args:
|
||||
context (Context): The context containing the scene and tool settings.
|
||||
filepath (str): The path to the G-code file to be imported.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the import status, typically
|
||||
{'FINISHED'}.
|
||||
"""
|
||||
|
||||
print("Running read_some_data...")
|
||||
|
||||
scene = context.scene
|
||||
|
@ -38,7 +56,28 @@ def import_gcode(context, filepath):
|
|||
return {'FINISHED'}
|
||||
|
||||
|
||||
def segments_to_meshdata(segments): # edges only on extrusion
|
||||
def segments_to_meshdata(segments):
|
||||
"""Convert a list of segments into mesh data consisting of vertices and
|
||||
edges.
|
||||
|
||||
This function processes a list of segment objects, extracting the
|
||||
coordinates of vertices and defining edges based on the styles of the
|
||||
segments. It identifies when to add vertices and edges based on whether
|
||||
the segments are in 'extrude' or 'travel' styles. The resulting mesh
|
||||
data can be used for 3D modeling or rendering applications.
|
||||
|
||||
Args:
|
||||
segments (list): A list of segment objects, each containing 'style' and
|
||||
'coords' attributes.
|
||||
|
||||
Returns:
|
||||
tuple: A tuple containing two elements:
|
||||
- list: A list of vertices, where each vertex is represented as a
|
||||
list of coordinates [X, Y, Z].
|
||||
- list: A list of edges, where each edge is represented as a list
|
||||
of indices corresponding to the vertices.
|
||||
"""
|
||||
# edges only on extrusion
|
||||
segs = segments
|
||||
verts = []
|
||||
edges = []
|
||||
|
@ -70,6 +109,31 @@ def segments_to_meshdata(segments): # edges only on extrusion
|
|||
|
||||
|
||||
def obj_from_pydata(name, verts, edges=None, close=True, collection_name=None):
|
||||
"""Create a Blender object from provided vertex and edge data.
|
||||
|
||||
This function generates a mesh object in Blender using the specified
|
||||
vertices and edges. If edges are not provided, it automatically creates
|
||||
a chain of edges connecting the vertices. The function also allows for
|
||||
the option to close the mesh by connecting the last vertex back to the
|
||||
first. Additionally, it can place the created object into a specified
|
||||
collection within the Blender scene. The object is scaled down to a
|
||||
smaller size for better visibility in the Blender environment.
|
||||
|
||||
Args:
|
||||
name (str): The name of the object to be created.
|
||||
verts (list): A list of vertex coordinates, where each vertex is represented as a
|
||||
tuple of (x, y, z).
|
||||
edges (list?): A list of edges defined by pairs of vertex indices. Defaults to None.
|
||||
close (bool?): Whether to close the mesh by connecting the last vertex to the first.
|
||||
Defaults to True.
|
||||
collection_name (str?): The name of the collection to which the object should be added. Defaults
|
||||
to None.
|
||||
|
||||
Returns:
|
||||
None: The function does not return a value; it creates an object in the
|
||||
Blender scene.
|
||||
"""
|
||||
|
||||
if edges is None:
|
||||
# join vertices into one uninterrupted chain of edges.
|
||||
edges = [[i, i + 1] for i in range(len(verts) - 1)]
|
||||
|
@ -109,6 +173,21 @@ class GcodeParser:
|
|||
self.model = GcodeModel(self)
|
||||
|
||||
def parseFile(self, path):
|
||||
"""Parse a G-code file and update the model.
|
||||
|
||||
This function reads a G-code file line by line, increments a line
|
||||
counter for each line, and processes each line using the `parseLine`
|
||||
method. The function assumes that the file is well-formed and that each
|
||||
line can be parsed without errors. After processing all lines, it
|
||||
returns the updated model.
|
||||
|
||||
Args:
|
||||
path (str): The file path to the G-code file to be parsed.
|
||||
|
||||
Returns:
|
||||
model: The updated model after parsing the G-code file.
|
||||
"""
|
||||
|
||||
# read the gcode file
|
||||
with open(path, 'r') as f:
|
||||
# init line counter
|
||||
|
@ -124,6 +203,16 @@ class GcodeParser:
|
|||
return self.model
|
||||
|
||||
def parseLine(self):
|
||||
"""Parse a line of G-code and execute the corresponding command.
|
||||
|
||||
This method processes a line of G-code by stripping comments, cleaning
|
||||
the command, and identifying the command code and its arguments. It
|
||||
handles specific G-code commands and invokes the appropriate parsing
|
||||
method if available. If the command is unsupported, it prints an error
|
||||
message. The method also manages tool numbers and coordinates based on
|
||||
the parsed command.
|
||||
"""
|
||||
|
||||
# strip comments:
|
||||
bits = self.line.split(';', 1)
|
||||
if (len(bits) > 1):
|
||||
|
@ -172,6 +261,22 @@ class GcodeParser:
|
|||
print("Unsupported gcode " + str(code))
|
||||
|
||||
def parseArgs(self, args):
|
||||
"""Parse command-line arguments into a dictionary.
|
||||
|
||||
This function takes a string of arguments, splits it into individual
|
||||
components, and maps each component's first character to its
|
||||
corresponding numeric value. If a numeric value cannot be converted from
|
||||
the string, it defaults to 1. The resulting dictionary contains the
|
||||
first characters as keys and their associated numeric values as values.
|
||||
|
||||
Args:
|
||||
args (str): A string of space-separated arguments, where each argument
|
||||
consists of a letter followed by a numeric value.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary mapping each letter to its corresponding numeric value.
|
||||
"""
|
||||
|
||||
dic = {}
|
||||
if args:
|
||||
bits = args.split()
|
||||
|
@ -208,6 +313,20 @@ class GcodeParser:
|
|||
print("[WARN] Line %d: %s (Text:'%s')" % (self.lineNb, msg, self.line))
|
||||
|
||||
def error(self, msg):
|
||||
"""Log an error message and raise an exception.
|
||||
|
||||
This method prints an error message to the console, including the line
|
||||
number, the provided message, and the text associated with the error.
|
||||
After logging the error, it raises a generic Exception with the same
|
||||
message format.
|
||||
|
||||
Args:
|
||||
msg (str): The error message to be logged.
|
||||
|
||||
Raises:
|
||||
Exception: Always raises an Exception with the formatted error message.
|
||||
"""
|
||||
|
||||
print("[ERROR] Line %d: %s (Text:'%s')" % (self.lineNb, msg, self.line))
|
||||
raise Exception("[ERROR] Line %d: %s (Text:'%s')" % (self.lineNb, msg, self.line))
|
||||
|
||||
|
@ -240,6 +359,21 @@ class GcodeModel:
|
|||
self.layers = []
|
||||
|
||||
def do_G1(self, args, type):
|
||||
"""Perform a rapid or controlled movement based on the provided arguments.
|
||||
|
||||
This method updates the current coordinates based on the input
|
||||
arguments, either in relative or absolute terms. It constructs a segment
|
||||
representing the movement and adds it to the model if there are changes
|
||||
in the XYZ coordinates. The function handles unknown axes by issuing a
|
||||
warning and ensures that the segment is only added if there are actual
|
||||
changes in position.
|
||||
|
||||
Args:
|
||||
args (dict): A dictionary containing movement parameters for each axis.
|
||||
type (str): The type of movement (e.g., 'G0' for rapid move, 'G1' for controlled
|
||||
move).
|
||||
"""
|
||||
|
||||
# G0/G1: Rapid/Controlled move
|
||||
# clone previous coords
|
||||
coords = dict(self.relative)
|
||||
|
@ -289,6 +423,20 @@ class GcodeModel:
|
|||
self.relative = coords
|
||||
|
||||
def do_G92(self, args):
|
||||
"""Set the current position of the axes without moving.
|
||||
|
||||
This method updates the current coordinates for the specified axes based
|
||||
on the provided arguments. If no axes are mentioned, it sets all axes
|
||||
(X, Y, Z) to zero. The method adjusts the offset values by transferring
|
||||
the difference between the relative and specified values for each axis.
|
||||
If an unknown axis is provided, a warning is issued.
|
||||
|
||||
Args:
|
||||
args (dict): A dictionary containing axis names as keys
|
||||
(e.g., 'X', 'Y', 'Z') and their corresponding
|
||||
position values as float.
|
||||
"""
|
||||
|
||||
# G92: Set Position
|
||||
# this changes the current coords, without moving, so do not generate a segment
|
||||
|
||||
|
@ -305,6 +453,25 @@ class GcodeModel:
|
|||
self.warn("Unknown axis '%s'" % axis)
|
||||
|
||||
def do_M163(self, args):
|
||||
"""Update the color settings for a specific segment based on given
|
||||
parameters.
|
||||
|
||||
This method modifies the color attributes of an object by updating the
|
||||
CMYKW values for a specified segment. It first creates a new list from
|
||||
the existing color attribute to avoid reference issues. The method then
|
||||
extracts the index and weight from the provided arguments and updates
|
||||
the color list accordingly. Additionally, it retrieves RGB values from
|
||||
the last comment and applies them to the color list.
|
||||
|
||||
Args:
|
||||
args (dict): A dictionary containing the parameters for the operation.
|
||||
- 'S' (int): The index of the segment to update.
|
||||
- 'P' (float): The weight to set for the CMYKW color component.
|
||||
|
||||
Returns:
|
||||
None: This method does not return a value; it modifies the object's state.
|
||||
"""
|
||||
|
||||
col = list(
|
||||
self.color) # list() creates new list, otherwise you just change reference and all segs have same color
|
||||
extr_idx = int(args['S']) # e.g. M163 S0 P1
|
||||
|
@ -332,6 +499,21 @@ class GcodeModel:
|
|||
self.parser.error(msg)
|
||||
|
||||
def classifySegments(self):
|
||||
"""Classify segments into layers based on their coordinates and extrusion
|
||||
style.
|
||||
|
||||
This method processes a list of segments, determining their extrusion
|
||||
style (travel, retract, restore, or extrude) based on the movement of
|
||||
the coordinates and the state of the extruder. It organizes the segments
|
||||
into layers, which are used for later rendering. The classification is
|
||||
based on changes in the Z-coordinate and the extruder's position. The
|
||||
function initializes the coordinates and iterates through each segment,
|
||||
checking for movements in the X, Y, and Z directions. It identifies when
|
||||
a new layer begins based on changes in the Z-coordinate and the
|
||||
extruder's state. Segments are then grouped into layers for further
|
||||
processing. Raises: None
|
||||
"""
|
||||
|
||||
|
||||
# start model at 0, act as prev_coords
|
||||
coords = {
|
||||
|
@ -393,6 +575,25 @@ class GcodeModel:
|
|||
coords = seg.coords
|
||||
|
||||
def subdivide(self, subd_threshold):
|
||||
"""Subdivide segments based on a specified threshold.
|
||||
|
||||
This method processes a list of segments and subdivides them into
|
||||
smaller segments if the distance between consecutive segments exceeds
|
||||
the given threshold. The subdivision is performed by interpolating
|
||||
points between the original segment's coordinates, ensuring that the
|
||||
resulting segments maintain the original order and properties. This is
|
||||
particularly useful for manipulating attributes such as color and
|
||||
continuous deformation in graphical representations.
|
||||
|
||||
Args:
|
||||
subd_threshold (float): The distance threshold for subdividing segments.
|
||||
Segments with a distance greater than this value
|
||||
will be subdivided.
|
||||
|
||||
Returns:
|
||||
None: The method modifies the instance's segments attribute in place.
|
||||
"""
|
||||
|
||||
# smart subdivide
|
||||
# divide edge if > subd_threshold
|
||||
# do it in parser to keep index order of vertex and travel/extrude info
|
||||
|
@ -457,6 +658,19 @@ class GcodeModel:
|
|||
|
||||
# create blender curve and vertex_info in text file(coords, style, color...)
|
||||
def draw(self, split_layers=False):
|
||||
"""Draws a mesh from segments and layers.
|
||||
|
||||
This function creates a Blender curve and vertex information in a text
|
||||
file, which includes coordinates, style, and color. If the
|
||||
`split_layers` parameter is set to True, it processes each layer
|
||||
individually, generating vertices and edges for each layer. If False, it
|
||||
processes the segments as a whole.
|
||||
|
||||
Args:
|
||||
split_layers (bool): A flag indicating whether to split the drawing into
|
||||
separate layers or not.
|
||||
"""
|
||||
|
||||
if split_layers:
|
||||
i = 0
|
||||
for layer in self.layers:
|
||||
|
@ -482,6 +696,17 @@ class Segment:
|
|||
self.layerIdx = None
|
||||
|
||||
def __str__(self):
|
||||
"""Return a string representation of the object.
|
||||
|
||||
This method constructs a string that includes the coordinates, line
|
||||
number, style, layer index, and color of the object. It formats these
|
||||
attributes into a readable string format for easier debugging and
|
||||
logging.
|
||||
|
||||
Returns:
|
||||
str: A formatted string representing the object's attributes.
|
||||
"""
|
||||
|
||||
return " <coords=%s, lineNb=%d, style=%s, layerIdx=%d, color=%s" % \
|
||||
(str(self.coords), self.lineNb, self.style, self.layerIdx, str(self.color))
|
||||
|
||||
|
|
|
@ -62,6 +62,28 @@ from .utils import (
|
|||
|
||||
|
||||
def pointonline(a, b, c, tolerence):
|
||||
"""Determine if the angle between two vectors is within a specified
|
||||
tolerance.
|
||||
|
||||
This function checks if the angle formed by two vectors, defined by
|
||||
points `b` and `c` relative to point `a`, is less than or equal to a
|
||||
given tolerance. It converts the points into vectors, calculates the dot
|
||||
product, and then computes the angle between them using the arccosine
|
||||
function. If the angle exceeds the specified tolerance, the function
|
||||
returns False; otherwise, it returns True.
|
||||
|
||||
Args:
|
||||
a (numpy.ndarray): The origin point as a vector.
|
||||
b (numpy.ndarray): The first point as a vector.
|
||||
c (numpy.ndarray): The second point as a vector.
|
||||
tolerence (float): The maximum allowable angle (in degrees) between the vectors.
|
||||
|
||||
Returns:
|
||||
bool: True if the angle between vectors b and c is within the specified
|
||||
tolerance,
|
||||
False otherwise.
|
||||
"""
|
||||
|
||||
b = b - a # convert to vector by subtracting origin
|
||||
c = c - a
|
||||
dot_pr = b.dot(c) # b dot c
|
||||
|
@ -75,7 +97,24 @@ def pointonline(a, b, c, tolerence):
|
|||
|
||||
|
||||
def exportGcodePath(filename, vertslist, operations):
|
||||
"""Exports G-code with the Heeks NC Adopted Library."""
|
||||
"""Exports G-code using the Heeks NC Adopted Library.
|
||||
|
||||
This function generates G-code from a list of vertices and operations
|
||||
specified by the user. It handles various post-processor settings based
|
||||
on the machine configuration and can split the output into multiple
|
||||
files if the total number of operations exceeds a specified limit. The
|
||||
G-code is tailored for different machine types and includes options for
|
||||
tool changes, spindle control, and various movement commands.
|
||||
|
||||
Args:
|
||||
filename (str): The name of the file to which the G-code will be exported.
|
||||
vertslist (list): A list of mesh objects containing vertex data.
|
||||
operations (list): A list of operations to be performed, each containing
|
||||
specific parameters for G-code generation.
|
||||
|
||||
Returns:
|
||||
None: This function does not return a value; it writes the G-code to a file.
|
||||
"""
|
||||
print("EXPORT")
|
||||
progress('Exporting G-code File')
|
||||
t = time.time()
|
||||
|
@ -157,6 +196,20 @@ def exportGcodePath(filename, vertslist, operations):
|
|||
use_experimental = bpy.context.preferences.addons[__package__].preferences.experimental
|
||||
|
||||
def startNewFile():
|
||||
"""Start a new file for G-code generation.
|
||||
|
||||
This function initializes a new file for G-code output based on the
|
||||
specified parameters. It constructs the filename using a base name, an
|
||||
optional index, and a file extension. The function also configures the
|
||||
post-processor settings based on user overrides and the selected unit
|
||||
system (metric or imperial). Finally, it begins the G-code program and
|
||||
sets the necessary configurations for the output.
|
||||
|
||||
Returns:
|
||||
Creator: An instance of the post-processor Creator class configured for
|
||||
G-code generation.
|
||||
"""
|
||||
|
||||
fileindex = ''
|
||||
if split:
|
||||
fileindex = '_' + str(findex)
|
||||
|
@ -520,7 +573,23 @@ def exportGcodePath(filename, vertslist, operations):
|
|||
print(time.time() - t)
|
||||
|
||||
|
||||
async def getPath(context, operation): # should do all path calculations.
|
||||
async def getPath(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.
|
||||
"""
|
||||
# should do all path calculations.
|
||||
t = time.process_time()
|
||||
# print('ahoj0')
|
||||
|
||||
|
@ -584,8 +653,23 @@ async def getPath(context, operation): # should do all path calculations.
|
|||
|
||||
|
||||
def getChangeData(o):
|
||||
"""This Is a Function to Check if Object Props Have Changed,
|
||||
to See if Image Updates Are Needed in the Image Based Method"""
|
||||
"""Check if object properties have changed to determine if image updates
|
||||
are needed.
|
||||
|
||||
This function inspects the properties of objects specified by the input
|
||||
parameter to see if any changes have occurred. It concatenates the
|
||||
location, rotation, and dimensions of the relevant objects into a single
|
||||
string, which can be used to determine if an image update is necessary
|
||||
based on changes in the object's state.
|
||||
|
||||
Args:
|
||||
o (object): An object containing properties that specify the geometry source
|
||||
and relevant object or collection names.
|
||||
|
||||
Returns:
|
||||
str: A string representation of the location, rotation, and dimensions of
|
||||
the specified objects.
|
||||
"""
|
||||
changedata = ''
|
||||
obs = []
|
||||
if o.geometry_source == 'OBJECT':
|
||||
|
@ -601,6 +685,23 @@ def getChangeData(o):
|
|||
|
||||
|
||||
def checkMemoryLimit(o):
|
||||
"""Check and adjust the memory limit for an object.
|
||||
|
||||
This function calculates the resolution of an object based on its
|
||||
dimensions and the specified pixel size. If the calculated resolution
|
||||
exceeds the defined memory limit, it adjusts the pixel size accordingly
|
||||
to reduce the resolution. A warning message is appended to the object's
|
||||
info if the pixel size is modified.
|
||||
|
||||
Args:
|
||||
o (object): An object containing properties such as max, min, optimisation, and
|
||||
info.
|
||||
|
||||
Returns:
|
||||
None: This function modifies the object's properties in place and does not
|
||||
return a value.
|
||||
"""
|
||||
|
||||
# getBounds(o)
|
||||
sx = o.max.x - o.min.x
|
||||
sy = o.max.y - o.min.y
|
||||
|
@ -619,6 +720,29 @@ def checkMemoryLimit(o):
|
|||
# this is the main function.
|
||||
# FIXME: split strategies into separate file!
|
||||
async def getPath3axis(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
|
||||
getBounds(o)
|
||||
|
@ -870,6 +994,25 @@ async def getPath3axis(context, operation):
|
|||
|
||||
|
||||
async def getPath4axis(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.
|
||||
"""
|
||||
|
||||
o = operation
|
||||
getBounds(o)
|
||||
if o.strategy4axis in ['PARALLELR', 'PARALLEL', 'HELIX', 'CROSS']:
|
||||
|
|
|
@ -48,6 +48,18 @@ from .numba_wrapper import (
|
|||
|
||||
|
||||
def numpysave(a, iname):
|
||||
"""Save a NumPy array as an image file in OpenEXR format.
|
||||
|
||||
This function converts a NumPy array into an image and saves it using
|
||||
Blender's rendering capabilities. It sets the image format to OpenEXR
|
||||
with black and white color mode and a color depth of 32 bits. The image
|
||||
is saved to the specified filename.
|
||||
|
||||
Args:
|
||||
a (numpy.ndarray): The NumPy array to be converted and saved as an image.
|
||||
iname (str): The file path where the image will be saved.
|
||||
"""
|
||||
|
||||
inamebase = bpy.path.basename(iname)
|
||||
|
||||
i = numpytoimage(a, inamebase)
|
||||
|
@ -62,6 +74,24 @@ def numpysave(a, iname):
|
|||
|
||||
|
||||
def getCircle(r, z):
|
||||
"""Generate a 2D array representing a circle.
|
||||
|
||||
This function creates a 2D NumPy array filled with a specified value for
|
||||
points that fall within a circle of a given radius. The circle is
|
||||
centered in the array, and the function uses the Euclidean distance to
|
||||
determine which points are inside the circle. The resulting array has
|
||||
dimensions that are twice the radius, ensuring that the entire circle
|
||||
fits within the array.
|
||||
|
||||
Args:
|
||||
r (int): The radius of the circle.
|
||||
z (float): The value to fill the points inside the circle.
|
||||
|
||||
Returns:
|
||||
numpy.ndarray: A 2D array where points inside the circle are filled
|
||||
with the value `z`, and points outside are filled with -10.
|
||||
"""
|
||||
|
||||
car = numpy.full(shape=(r*2, r*2), fill_value=-10, dtype=numpy.double)
|
||||
res = 2 * r
|
||||
m = r
|
||||
|
@ -76,6 +106,22 @@ def getCircle(r, z):
|
|||
|
||||
|
||||
def getCircleBinary(r):
|
||||
"""Generate a binary representation of a circle in a 2D grid.
|
||||
|
||||
This function creates a 2D boolean array where the elements inside a
|
||||
circle of radius `r` are set to `True`, and the elements outside the
|
||||
circle are set to `False`. The circle is centered in the middle of the
|
||||
array, which has dimensions of (2*r, 2*r). The function iterates over
|
||||
each point in the grid and checks if it lies within the specified
|
||||
radius.
|
||||
|
||||
Args:
|
||||
r (int): The radius of the circle.
|
||||
|
||||
Returns:
|
||||
numpy.ndarray: A 2D boolean array representing the circle.
|
||||
"""
|
||||
|
||||
car = numpy.full(shape=(r*2, r*2), fill_value=False, dtype=bool)
|
||||
res = 2 * r
|
||||
m = r
|
||||
|
@ -93,6 +139,22 @@ def getCircleBinary(r):
|
|||
|
||||
|
||||
def numpytoimage(a, iname):
|
||||
"""Convert a NumPy array to a Blender image.
|
||||
|
||||
This function takes a NumPy array and converts it into a Blender image.
|
||||
It first checks if an image with the specified name and dimensions
|
||||
already exists in Blender. If it does not exist, a new image is created
|
||||
with the specified name and dimensions. The pixel data from the NumPy
|
||||
array is then reshaped and assigned to the image's pixel buffer.
|
||||
|
||||
Args:
|
||||
a (numpy.ndarray): A 2D NumPy array representing the image data.
|
||||
iname (str): The name to assign to the created or found image.
|
||||
|
||||
Returns:
|
||||
bpy.types.Image: The Blender image object that was created or found.
|
||||
"""
|
||||
|
||||
print('numpy to image', iname)
|
||||
t = time.time()
|
||||
print(a.shape[0], a.shape[1])
|
||||
|
@ -122,6 +184,21 @@ def numpytoimage(a, iname):
|
|||
|
||||
|
||||
def imagetonumpy(i):
|
||||
"""Convert a Blender image to a NumPy array.
|
||||
|
||||
This function takes a Blender image object and converts its pixel data
|
||||
into a NumPy array. It retrieves the pixel data, reshapes it, and swaps
|
||||
the axes to match the expected format for further processing. The
|
||||
function also measures the time taken for the conversion and prints it
|
||||
to the console.
|
||||
|
||||
Args:
|
||||
i (Image): A Blender image object containing pixel data.
|
||||
|
||||
Returns:
|
||||
numpy.ndarray: A 2D NumPy array representing the image pixels.
|
||||
"""
|
||||
|
||||
t = time.time()
|
||||
|
||||
width = i.size[0]
|
||||
|
@ -142,13 +219,52 @@ def imagetonumpy(i):
|
|||
|
||||
@jit(nopython=True, parallel=True, fastmath=False, cache=True)
|
||||
def _offset_inner_loop(y1, y2, cutterArrayNan, cwidth, sourceArray, width, height, comparearea):
|
||||
"""Offset the inner loop for processing a specified area in a 2D array.
|
||||
|
||||
This function iterates over a specified range of rows and columns in a
|
||||
2D array, calculating the maximum value from a source array combined
|
||||
with a cutter array for each position in the defined area. The results
|
||||
are stored in the comparearea array, which is updated with the maximum
|
||||
values found.
|
||||
|
||||
Args:
|
||||
y1 (int): The starting index for the row iteration.
|
||||
y2 (int): The ending index for the row iteration.
|
||||
cutterArrayNan (numpy.ndarray): A 2D array used for modifying the source array.
|
||||
cwidth (int): The width of the area to consider for the maximum calculation.
|
||||
sourceArray (numpy.ndarray): The source 2D array from which maximum values are derived.
|
||||
width (int): The width of the source array.
|
||||
height (int): The height of the source array.
|
||||
comparearea (numpy.ndarray): A 2D array where the calculated maximum values are stored.
|
||||
|
||||
Returns:
|
||||
None: This function modifies the comparearea in place and does not return a
|
||||
value.
|
||||
"""
|
||||
|
||||
for y in prange(y1, y2):
|
||||
for x in range(0, width-cwidth):
|
||||
comparearea[x, y] = numpy.nanmax(sourceArray[x:x+cwidth, y:y+cwidth] + cutterArrayNan)
|
||||
|
||||
|
||||
async def offsetArea(o, samples):
|
||||
""" Offsets the Whole Image with the Cutter + Skin Offsets """
|
||||
"""Offsets the whole image with the cutter and skin offsets.
|
||||
|
||||
This function modifies the offset image based on the provided cutter and
|
||||
skin offsets. It calculates the dimensions of the source and cutter
|
||||
arrays, initializes an offset image, and processes the image in
|
||||
segments. The function handles the inversion of the source array if
|
||||
specified and updates the offset image accordingly. Progress is reported
|
||||
asynchronously during processing.
|
||||
|
||||
Args:
|
||||
o: An object containing properties such as `update_offsetimage_tag`,
|
||||
`min`, `max`, `inverse`, and `offset_image`.
|
||||
samples (numpy.ndarray): A 2D array representing the source image data.
|
||||
|
||||
Returns:
|
||||
numpy.ndarray: The updated offset image after applying the cutter and skin offsets.
|
||||
"""
|
||||
if o.update_offsetimage_tag:
|
||||
minx, miny, minz, maxx, maxy, maxz = o.min.x, o.min.y, o.min.z, o.max.x, o.max.y, o.max.z
|
||||
|
||||
|
@ -187,13 +303,49 @@ async def offsetArea(o, samples):
|
|||
|
||||
|
||||
def dilateAr(ar, cycles):
|
||||
"""Dilate a binary array using a specified number of cycles.
|
||||
|
||||
This function performs a dilation operation on a 2D binary array. For
|
||||
each cycle, it updates the array by applying a logical OR operation
|
||||
between the current array and its neighboring elements. The dilation
|
||||
effect expands the boundaries of the foreground (True) pixels in the
|
||||
binary array.
|
||||
|
||||
Args:
|
||||
ar (numpy.ndarray): A 2D binary array (numpy array) where
|
||||
dilation will be applied.
|
||||
cycles (int): The number of dilation cycles to perform.
|
||||
|
||||
Returns:
|
||||
None: The function modifies the input array in place and does not
|
||||
return a value.
|
||||
"""
|
||||
|
||||
for c in range(cycles):
|
||||
ar[1:-1, :] = numpy.logical_or(ar[1:-1, :], ar[:-2, :])
|
||||
ar[:, 1:-1] = numpy.logical_or(ar[:, 1:-1], ar[:, :-2])
|
||||
|
||||
|
||||
def getOffsetImageCavities(o, i): # for pencil operation mainly
|
||||
"""Detects Areas in the Offset Image Which Are 'cavities' - the Curvature Changes."""
|
||||
"""Detects areas in the offset image which are 'cavities' due to curvature
|
||||
changes.
|
||||
|
||||
This function analyzes the input image to identify regions where the
|
||||
curvature changes, indicating the presence of cavities. It computes
|
||||
vertical and horizontal differences in pixel values to detect edges and
|
||||
applies a threshold to filter out insignificant changes. The resulting
|
||||
areas are then processed to remove any chunks that do not meet the
|
||||
minimum criteria for cavity detection. The function returns a list of
|
||||
valid chunks that represent the detected cavities.
|
||||
|
||||
Args:
|
||||
o: An object containing parameters and thresholds for the detection
|
||||
process.
|
||||
i (numpy.ndarray): A 2D array representing the image data to be analyzed.
|
||||
|
||||
Returns:
|
||||
list: A list of detected chunks representing the cavities in the image.
|
||||
"""
|
||||
# i=numpy.logical_xor(lastislice , islice)
|
||||
progress('Detect Corners in the Offset Image')
|
||||
vertical = i[:-2, 1:-1] - i[1:-1, 1:-1] - o.pencil_threshold > i[1:-1, 1:-1] - i[2:, 1:-1]
|
||||
|
@ -225,6 +377,28 @@ def getOffsetImageCavities(o, i): # for pencil operation mainly
|
|||
|
||||
# search edges for pencil strategy, another try.
|
||||
def imageEdgeSearch_online(o, ar, zimage):
|
||||
"""Search for edges in an image using a pencil strategy.
|
||||
|
||||
This function implements an edge detection algorithm that simulates a
|
||||
pencil-like movement across the image represented by a 2D array. It
|
||||
identifies white pixels and builds chunks of points based on the
|
||||
detected edges. The algorithm iteratively explores possible directions
|
||||
to find and track the edges until a specified condition is met, such as
|
||||
exhausting the available white pixels or reaching a maximum number of
|
||||
tests.
|
||||
|
||||
Args:
|
||||
o (object): An object containing parameters such as min, max coordinates, cutter
|
||||
diameter,
|
||||
border width, and optimisation settings.
|
||||
ar (numpy.ndarray): A 2D array representing the image where edge detection is to be
|
||||
performed.
|
||||
zimage (numpy.ndarray): A 2D array representing the z-coordinates corresponding to the image.
|
||||
|
||||
Returns:
|
||||
list: A list of chunks representing the detected edges in the image.
|
||||
"""
|
||||
|
||||
minx, miny, minz, maxx, maxy, maxz = o.min.x, o.min.y, o.min.z, o.max.x, o.max.y, o.max.z
|
||||
r = ceil((o.cutter_diameter/12)/o.optimisation.pixsize) # was commented
|
||||
coef = 0.75
|
||||
|
@ -350,6 +524,23 @@ def imageEdgeSearch_online(o, ar, zimage):
|
|||
|
||||
|
||||
async def crazyPath(o):
|
||||
"""Execute a greedy adaptive algorithm for path planning.
|
||||
|
||||
This function prepares an area based on the provided object `o`,
|
||||
calculates the dimensions of the area, and initializes a mill image and
|
||||
cutter array. The dimensions are determined by the maximum and minimum
|
||||
coordinates of the object, adjusted by the simulation detail and border
|
||||
width. The function is currently a stub and requires further
|
||||
implementation.
|
||||
|
||||
Args:
|
||||
o (object): An object containing properties such as max, min, optimisation, and
|
||||
borderwidth.
|
||||
|
||||
Returns:
|
||||
None: This function does not return a value.
|
||||
"""
|
||||
|
||||
# TODO: try to do something with this stuff, it's just a stub. It should be a greedy adaptive algorithm.
|
||||
# started another thing below.
|
||||
await prepareArea(o)
|
||||
|
@ -365,6 +556,25 @@ async def crazyPath(o):
|
|||
|
||||
|
||||
def buildStroke(start, end, cutterArray):
|
||||
"""Build a stroke array based on start and end points.
|
||||
|
||||
This function generates a 2D stroke array that represents a stroke from
|
||||
a starting point to an ending point. It calculates the length of the
|
||||
stroke and creates a grid that is filled based on the positions defined
|
||||
by the start and end coordinates. The function uses a cutter array to
|
||||
determine how the stroke interacts with the grid.
|
||||
|
||||
Args:
|
||||
start (tuple): A tuple representing the starting coordinates (x, y, z).
|
||||
end (tuple): A tuple representing the ending coordinates (x, y, z).
|
||||
cutterArray: An object that contains size information used to modify
|
||||
the stroke array.
|
||||
|
||||
Returns:
|
||||
numpy.ndarray: A 2D array representing the stroke, filled with
|
||||
calculated values based on the input parameters.
|
||||
"""
|
||||
|
||||
strokelength = max(abs(end[0] - start[0]), abs(end[1] - start[1]))
|
||||
size_x = abs(end[0] - start[0]) + cutterArray.size[0]
|
||||
size_y = abs(end[1] - start[1]) + cutterArray.size[0]
|
||||
|
@ -394,6 +604,26 @@ def testStrokeBinary(img, stroke):
|
|||
|
||||
|
||||
def crazyStrokeImage(o):
|
||||
"""Generate a toolpath for a milling operation using a crazy stroke
|
||||
strategy.
|
||||
|
||||
This function computes a path for a milling cutter based on the provided
|
||||
parameters and the offset image. It utilizes a circular cutter
|
||||
representation and evaluates potential cutting positions based on
|
||||
various thresholds. The algorithm iteratively tests different angles and
|
||||
lengths for the cutter's movement until the desired cutting area is
|
||||
achieved or the maximum number of tests is reached.
|
||||
|
||||
Args:
|
||||
o (object): An object containing parameters such as cutter diameter,
|
||||
optimization settings, movement type, and thresholds for
|
||||
determining cutting effectiveness.
|
||||
|
||||
Returns:
|
||||
list: A list of chunks representing the computed toolpath for the milling
|
||||
operation.
|
||||
"""
|
||||
|
||||
# this surprisingly works, and can be used as a basis for something similar to adaptive milling strategy.
|
||||
minx, miny, minz, maxx, maxy, maxz = o.min.x, o.min.y, o.min.z, o.max.x, o.max.y, o.max.z
|
||||
|
||||
|
@ -577,6 +807,27 @@ def crazyStrokeImage(o):
|
|||
|
||||
|
||||
def crazyStrokeImageBinary(o, ar, avoidar):
|
||||
"""Perform a milling operation using a binary image representation.
|
||||
|
||||
This function implements a strategy for milling by navigating through a
|
||||
binary image. It starts from a defined point and attempts to move in
|
||||
various directions, evaluating the cutter load to determine the
|
||||
appropriate path. The algorithm continues until it either exhausts the
|
||||
available pixels to cut or reaches a predefined limit on the number of
|
||||
tests. The function modifies the input array to represent the areas that
|
||||
have been milled and returns the generated path as a list of chunks.
|
||||
|
||||
Args:
|
||||
o (object): An object containing parameters for the milling operation, including
|
||||
cutter diameter, thresholds, and movement type.
|
||||
ar (numpy.ndarray): A 2D binary array representing the image to be milled.
|
||||
avoidar (numpy.ndarray): A 2D binary array indicating areas to avoid during milling.
|
||||
|
||||
Returns:
|
||||
list: A list of chunks representing the path taken during the milling
|
||||
operation.
|
||||
"""
|
||||
|
||||
# this surprisingly works, and can be used as a basis for something similar to adaptive milling strategy.
|
||||
# works like this:
|
||||
# start 'somewhere'
|
||||
|
@ -845,6 +1096,28 @@ def crazyStrokeImageBinary(o, ar, avoidar):
|
|||
|
||||
|
||||
def imageToChunks(o, image, with_border=False):
|
||||
"""Convert an image into chunks based on detected edges.
|
||||
|
||||
This function processes a given image to identify edges and convert them
|
||||
into polychunks, which are essentially collections of connected edge
|
||||
segments. It utilizes the properties of the input object `o` to
|
||||
determine the boundaries and size of the chunks. The function can
|
||||
optionally include borders in the edge detection process. The output is
|
||||
a list of chunks that represent the detected polygons in the image.
|
||||
|
||||
Args:
|
||||
o (object): An object containing properties such as min, max, borderwidth,
|
||||
and optimisation settings.
|
||||
image (numpy.ndarray): A 2D array representing the image to be processed,
|
||||
expected to be in a format compatible with uint8.
|
||||
with_border (bool?): A flag indicating whether to include borders
|
||||
in the edge detection. Defaults to False.
|
||||
|
||||
Returns:
|
||||
list: A list of chunks, where each chunk is represented as a collection of
|
||||
points that outline the detected edges in the image.
|
||||
"""
|
||||
|
||||
t = time.time()
|
||||
minx, miny, minz, maxx, maxy, maxz = o.min.x, o.min.y, o.min.z, o.max.x, o.max.y, o.max.z
|
||||
pixsize = o.optimisation.pixsize
|
||||
|
@ -1018,6 +1291,24 @@ def imageToChunks(o, image, with_border=False):
|
|||
|
||||
|
||||
def imageToShapely(o, i, with_border=False):
|
||||
"""Convert an image to Shapely polygons.
|
||||
|
||||
This function takes an image and converts it into a series of Shapely
|
||||
polygon objects. It first processes the image into chunks and then
|
||||
transforms those chunks into polygon geometries. The `with_border`
|
||||
parameter allows for the inclusion of borders in the resulting polygons.
|
||||
|
||||
Args:
|
||||
o: The input image to be processed.
|
||||
i: Additional input parameters for processing the image.
|
||||
with_border (bool): A flag indicating whether to include
|
||||
borders in the resulting polygons. Defaults to False.
|
||||
|
||||
Returns:
|
||||
list: A list of Shapely polygon objects created from the
|
||||
image chunks.
|
||||
"""
|
||||
|
||||
polychunks = imageToChunks(o, i, with_border)
|
||||
polys = chunksToShapely(polychunks)
|
||||
|
||||
|
@ -1025,6 +1316,24 @@ def imageToShapely(o, i, with_border=False):
|
|||
|
||||
|
||||
def getSampleImage(s, sarray, minz):
|
||||
"""Get a sample image value from a 2D array based on given coordinates.
|
||||
|
||||
This function retrieves a value from a 2D array by performing bilinear
|
||||
interpolation based on the provided coordinates. It checks if the
|
||||
coordinates are within the bounds of the array and calculates the
|
||||
interpolated value accordingly. If the coordinates are out of bounds, it
|
||||
returns -10.
|
||||
|
||||
Args:
|
||||
s (tuple): A tuple containing the x and y coordinates (float).
|
||||
sarray (numpy.ndarray): A 2D array from which to sample the image values.
|
||||
minz (float): A minimum threshold value (not used in the current implementation).
|
||||
|
||||
Returns:
|
||||
float: The interpolated value from the 2D array, or -10 if the coordinates are
|
||||
out of bounds.
|
||||
"""
|
||||
|
||||
x = s[0]
|
||||
y = s[1]
|
||||
if (x < 0 or x > len(sarray) - 1) or (y < 0 or y > len(sarray[0]) - 1):
|
||||
|
@ -1050,6 +1359,25 @@ def getSampleImage(s, sarray, minz):
|
|||
|
||||
|
||||
def getResolution(o):
|
||||
"""Calculate the resolution based on the dimensions of an object.
|
||||
|
||||
This function computes the resolution in both x and y directions by
|
||||
determining the width and height of the object, adjusting for pixel size
|
||||
and border width. The resolution is calculated by dividing the
|
||||
dimensions by the pixel size and adding twice the border width to each
|
||||
dimension.
|
||||
|
||||
Args:
|
||||
o (object): An object with attributes `max`, `min`, `optimisation`,
|
||||
and `borderwidth`. The `max` and `min` attributes should
|
||||
have `x` and `y` properties representing the coordinates,
|
||||
while `optimisation` should have a `pixsize` attribute.
|
||||
|
||||
Returns:
|
||||
None: This function does not return a value; it performs calculations
|
||||
to determine resolution.
|
||||
"""
|
||||
|
||||
sx = o.max.x - o.min.x
|
||||
sy = o.max.y - o.min.y
|
||||
|
||||
|
@ -1061,6 +1389,25 @@ def getResolution(o):
|
|||
|
||||
|
||||
def _backup_render_settings(pairs):
|
||||
"""Backup the render settings of Blender objects.
|
||||
|
||||
This function iterates over a list of pairs consisting of owners and
|
||||
their corresponding structure names. It retrieves the properties of each
|
||||
structure and stores them in a backup list. If the structure is a
|
||||
Blender object, it saves all its properties that do not start with an
|
||||
underscore. For simple values, it directly appends them to the
|
||||
properties list. This is useful for preserving render settings that
|
||||
Blender does not allow direct access to during rendering.
|
||||
|
||||
Args:
|
||||
pairs (list): A list of tuples where each tuple contains an owner and a structure
|
||||
name.
|
||||
|
||||
Returns:
|
||||
list: A list containing the backed-up properties of the specified Blender
|
||||
objects.
|
||||
"""
|
||||
|
||||
properties = []
|
||||
for owner, struct_name in pairs:
|
||||
obj = getattr(owner, struct_name)
|
||||
|
@ -1077,6 +1424,22 @@ def _backup_render_settings(pairs):
|
|||
|
||||
|
||||
def _restore_render_settings(pairs, properties):
|
||||
"""Restore render settings for a given owner and structure.
|
||||
|
||||
This function takes pairs of owners and structure names along with their
|
||||
corresponding properties. It iterates through these pairs, retrieves the
|
||||
appropriate object from the owner using the structure name, and sets the
|
||||
properties on the object. If the object is an instance of
|
||||
`bpy.types.bpy_struct`, it updates its attributes; otherwise, it
|
||||
directly sets the value on the owner.
|
||||
|
||||
Args:
|
||||
pairs (list): A list of tuples where each tuple contains an owner and a structure
|
||||
name.
|
||||
properties (list): A list of dictionaries containing property names and their corresponding
|
||||
values.
|
||||
"""
|
||||
|
||||
for (owner, struct_name), obj_value in zip(pairs, properties):
|
||||
obj = getattr(owner, struct_name)
|
||||
if isinstance(obj, bpy.types.bpy_struct):
|
||||
|
@ -1087,6 +1450,22 @@ def _restore_render_settings(pairs, properties):
|
|||
|
||||
|
||||
def renderSampleImage(o):
|
||||
"""Render a sample image based on the provided object settings.
|
||||
|
||||
This function generates a Z-buffer image for a given object by either
|
||||
rendering it from scratch or loading an existing image from the cache.
|
||||
It handles different geometry sources and applies various settings to
|
||||
ensure the image is rendered correctly. The function also manages backup
|
||||
and restoration of render settings to maintain the scene's integrity
|
||||
during the rendering process.
|
||||
|
||||
Args:
|
||||
o (object): An object containing various properties and settings
|
||||
|
||||
Returns:
|
||||
numpy.ndarray: The generated or loaded Z-buffer image as a NumPy array.
|
||||
"""
|
||||
|
||||
t = time.time()
|
||||
progress('Getting Z-Buffer')
|
||||
# print(o.zbuffer_image)
|
||||
|
@ -1285,6 +1664,21 @@ def renderSampleImage(o):
|
|||
# return numpy.array([])
|
||||
|
||||
async def prepareArea(o):
|
||||
"""Prepare the area for rendering by processing the offset image.
|
||||
|
||||
This function handles the preparation of the area by rendering a sample
|
||||
image and managing the offset image based on the provided options. It
|
||||
checks if the offset image needs to be updated and loads it if
|
||||
necessary. If the inverse option is set, it adjusts the samples
|
||||
accordingly before calling the offsetArea function. Finally, it saves
|
||||
the processed offset image.
|
||||
|
||||
Args:
|
||||
o (object): An object containing various properties and methods
|
||||
required for preparing the area, including flags for
|
||||
updating the offset image and rendering options.
|
||||
"""
|
||||
|
||||
# if not o.use_exact:
|
||||
renderSampleImage(o)
|
||||
samples = o.zbuffer_image
|
||||
|
@ -1307,6 +1701,23 @@ async def prepareArea(o):
|
|||
|
||||
|
||||
def getCutterArray(operation, pixsize):
|
||||
"""Generate a cutter array based on the specified operation and pixel size.
|
||||
|
||||
This function calculates a 2D array representing the cutter shape based
|
||||
on the cutter type defined in the operation object. The cutter can be of
|
||||
various types such as 'END', 'BALL', 'VCARVE', 'CYLCONE', 'BALLCONE', or
|
||||
'CUSTOM'. The function uses geometric calculations to fill the array
|
||||
with appropriate values based on the cutter's dimensions and properties.
|
||||
|
||||
Args:
|
||||
operation (object): An object containing properties of the cutter, including
|
||||
cutter type, diameter, tip angle, and other relevant parameters.
|
||||
pixsize (float): The size of each pixel in the generated cutter array.
|
||||
|
||||
Returns:
|
||||
numpy.ndarray: A 2D array filled with values representing the cutter shape.
|
||||
"""
|
||||
|
||||
type = operation.cutter_type
|
||||
# print('generating cutter')
|
||||
r = operation.cutter_diameter / 2 + operation.skin # /operation.pixsize
|
||||
|
|
|
@ -110,6 +110,35 @@ def gear_q6(b, s, t, d):
|
|||
|
||||
def gear(mm_per_tooth=0.003, number_of_teeth=5, hole_diameter=0.003175,
|
||||
pressure_angle=0.3488, clearance=0.0, backlash=0.0, rim_size=0.0005, hub_diameter=0.006, spokes=4):
|
||||
"""Generate a 3D gear model based on specified parameters.
|
||||
|
||||
This function creates a 3D representation of a gear using the provided
|
||||
parameters such as the circular pitch, number of teeth, hole diameter,
|
||||
pressure angle, clearance, backlash, rim size, hub diameter, and the
|
||||
number of spokes. The gear is constructed by calculating various radii
|
||||
and angles based on the input parameters and then using geometric
|
||||
operations to form the final shape. The resulting gear is named
|
||||
according to its specifications.
|
||||
|
||||
Args:
|
||||
mm_per_tooth (float): The circular pitch of the gear in millimeters (default is 0.003).
|
||||
number_of_teeth (int): The total number of teeth on the gear (default is 5).
|
||||
hole_diameter (float): The diameter of the central hole in millimeters (default is 0.003175).
|
||||
pressure_angle (float): The angle that controls the shape of the tooth sides in radians (default
|
||||
is 0.3488).
|
||||
clearance (float): The gap between the top of a tooth and the bottom of a valley on a
|
||||
meshing gear in millimeters (default is 0.0).
|
||||
backlash (float): The gap between two meshing teeth along the circumference of the pitch
|
||||
circle in millimeters (default is 0.0).
|
||||
rim_size (float): The size of the rim around the gear in millimeters (default is 0.0005).
|
||||
hub_diameter (float): The diameter of the hub in millimeters (default is 0.006).
|
||||
spokes (int): The number of spokes on the gear (default is 4).
|
||||
|
||||
Returns:
|
||||
None: This function does not return a value but modifies the Blender scene to
|
||||
include the generated gear model.
|
||||
"""
|
||||
|
||||
simple.deselect()
|
||||
p = mm_per_tooth * number_of_teeth / pi / 2 # radius of pitch circle
|
||||
c = p + mm_per_tooth / pi - clearance # radius of outer circle
|
||||
|
@ -203,6 +232,26 @@ def gear(mm_per_tooth=0.003, number_of_teeth=5, hole_diameter=0.003175,
|
|||
|
||||
def rack(mm_per_tooth=0.01, number_of_teeth=11, height=0.012, pressure_angle=0.3488, backlash=0.0,
|
||||
hole_diameter=0.003175, tooth_per_hole=4):
|
||||
"""Generate a rack gear profile based on specified parameters.
|
||||
|
||||
This function creates a rack gear by calculating the geometry based on
|
||||
the provided parameters such as millimeters per tooth, number of teeth,
|
||||
height, pressure angle, backlash, hole diameter, and teeth per hole. It
|
||||
constructs the gear shape using the Shapely library and duplicates the
|
||||
tooth to create the full rack. If a hole diameter is specified, it also
|
||||
creates holes along the rack. The resulting gear is named based on the
|
||||
input parameters.
|
||||
|
||||
Args:
|
||||
mm_per_tooth (float): The distance in millimeters for each tooth. Default is 0.01.
|
||||
number_of_teeth (int): The total number of teeth on the rack. Default is 11.
|
||||
height (float): The height of the rack. Default is 0.012.
|
||||
pressure_angle (float): The pressure angle in radians. Default is 0.3488.
|
||||
backlash (float): The backlash distance in millimeters. Default is 0.0.
|
||||
hole_diameter (float): The diameter of the holes in millimeters. Default is 0.003175.
|
||||
tooth_per_hole (int): The number of teeth per hole. Default is 4.
|
||||
"""
|
||||
|
||||
simple.deselect()
|
||||
mm_per_tooth *= 1000
|
||||
a = mm_per_tooth / pi # addendum
|
||||
|
|
|
@ -1,110 +1,149 @@
|
|||
"""BlenderCAM 'oclSample.py'
|
||||
|
||||
Functions to sample mesh or curve data for OpenCAMLib processing.
|
||||
"""
|
||||
|
||||
from math import (
|
||||
radians,
|
||||
tan
|
||||
)
|
||||
|
||||
try:
|
||||
import ocl
|
||||
except ImportError:
|
||||
try:
|
||||
import opencamlib as ocl
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from bl_ext.blender_org.stl_format_legacy import blender_utils
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
import mathutils
|
||||
|
||||
from ..simple import activate
|
||||
from ..exception import CamException
|
||||
from ..async_op import progress_async
|
||||
|
||||
OCL_SCALE = 1000.0
|
||||
|
||||
_PREVIOUS_OCL_MESH = None
|
||||
|
||||
|
||||
def get_oclSTL(operation):
|
||||
me = None
|
||||
oclSTL = ocl.STLSurf()
|
||||
found_mesh = False
|
||||
for collision_object in operation.objects:
|
||||
activate(collision_object)
|
||||
if collision_object.type == "MESH" or collision_object.type == "CURVE" or collision_object.type == "FONT" or collision_object.type == "SURFACE":
|
||||
found_mesh = True
|
||||
global_matrix = mathutils.Matrix.Identity(4)
|
||||
faces = blender_utils.faces_from_mesh(
|
||||
collision_object, global_matrix, operation.use_modifiers)
|
||||
for face in faces:
|
||||
t = ocl.Triangle(ocl.Point(face[0][0]*OCL_SCALE, face[0][1]*OCL_SCALE, (face[0][2]+operation.skin)*OCL_SCALE),
|
||||
ocl.Point(face[1][0]*OCL_SCALE, face[1][1]*OCL_SCALE,
|
||||
(face[1][2]+operation.skin)*OCL_SCALE),
|
||||
ocl.Point(face[2][0]*OCL_SCALE, face[2][1]*OCL_SCALE, (face[2][2]+operation.skin)*OCL_SCALE))
|
||||
oclSTL.addTriangle(t)
|
||||
# FIXME needs to work with collections
|
||||
if not found_mesh:
|
||||
raise CamException(
|
||||
"This Operation Requires a Mesh or Curve Object or Equivalent (e.g. Text, Volume).")
|
||||
return oclSTL
|
||||
|
||||
|
||||
async def ocl_sample(operation, chunks, use_cached_mesh=False):
|
||||
global _PREVIOUS_OCL_MESH
|
||||
|
||||
op_cutter_type = operation.cutter_type
|
||||
op_cutter_diameter = operation.cutter_diameter
|
||||
op_minz = operation.minz
|
||||
op_cutter_tip_angle = radians(operation.cutter_tip_angle)/2
|
||||
if op_cutter_type == "VCARVE":
|
||||
cutter_length = (op_cutter_diameter/tan(op_cutter_tip_angle))/2
|
||||
else:
|
||||
cutter_length = 10
|
||||
|
||||
cutter = None
|
||||
|
||||
if op_cutter_type == 'END':
|
||||
cutter = ocl.CylCutter((op_cutter_diameter + operation.skin * 2) * 1000, cutter_length)
|
||||
elif op_cutter_type == 'BALLNOSE':
|
||||
cutter = ocl.BallCutter((op_cutter_diameter + operation.skin * 2) * 1000, cutter_length)
|
||||
elif op_cutter_type == 'VCARVE':
|
||||
cutter = ocl.ConeCutter((op_cutter_diameter + operation.skin * 2)
|
||||
* 1000, op_cutter_tip_angle, cutter_length)
|
||||
elif op_cutter_type == 'CYLCONE':
|
||||
cutter = ocl.CylConeCutter((operation.cylcone_diameter/2+operation.skin)*2000,
|
||||
(op_cutter_diameter + operation.skin * 2) * 1000, op_cutter_tip_angle)
|
||||
elif op_cutter_type == 'BALLCONE':
|
||||
cutter = ocl.BallConeCutter((operation.ball_radius + operation.skin) * 2000,
|
||||
(op_cutter_diameter + operation.skin * 2) * 1000, op_cutter_tip_angle)
|
||||
elif op_cutter_type == 'BULLNOSE':
|
||||
cutter = ocl.BullCutter((op_cutter_diameter + operation.skin * 2) *
|
||||
1000, operation.bull_corner_radius*1000, cutter_length)
|
||||
else:
|
||||
print("Cutter Unsupported: {0}\n".format(op_cutter_type))
|
||||
quit()
|
||||
|
||||
bdc = ocl.BatchDropCutter()
|
||||
if use_cached_mesh and _PREVIOUS_OCL_MESH is not None:
|
||||
oclSTL = _PREVIOUS_OCL_MESH
|
||||
else:
|
||||
oclSTL = get_oclSTL(operation)
|
||||
_PREVIOUS_OCL_MESH = oclSTL
|
||||
bdc.setSTL(oclSTL)
|
||||
bdc.setCutter(cutter)
|
||||
|
||||
for chunk in chunks:
|
||||
for coord in chunk.get_points_np():
|
||||
bdc.appendPoint(ocl.CLPoint(coord[0] * 1000, coord[1] * 1000, op_minz * 1000))
|
||||
await progress_async("OpenCAMLib Sampling")
|
||||
bdc.run()
|
||||
|
||||
cl_points = bdc.getCLPoints()
|
||||
|
||||
return cl_points
|
||||
"""BlenderCAM 'oclSample.py'
|
||||
|
||||
Functions to sample mesh or curve data for OpenCAMLib processing.
|
||||
"""
|
||||
|
||||
from math import (
|
||||
radians,
|
||||
tan
|
||||
)
|
||||
|
||||
try:
|
||||
import ocl
|
||||
except ImportError:
|
||||
try:
|
||||
import opencamlib as ocl
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from bl_ext.blender_org.stl_format_legacy import blender_utils
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
import mathutils
|
||||
|
||||
from ..simple import activate
|
||||
from ..exception import CamException
|
||||
from ..async_op import progress_async
|
||||
|
||||
OCL_SCALE = 1000.0
|
||||
|
||||
_PREVIOUS_OCL_MESH = None
|
||||
|
||||
|
||||
def get_oclSTL(operation):
|
||||
"""Get the oclSTL representation from the provided operation.
|
||||
|
||||
This function iterates through the objects in the given operation and
|
||||
constructs an oclSTL object by extracting triangle data from mesh,
|
||||
curve, font, or surface objects. It activates each object and checks its
|
||||
type to determine if it can be processed. If no valid objects are found,
|
||||
it raises an exception.
|
||||
|
||||
Args:
|
||||
operation (Operation): An object containing a collection of objects
|
||||
|
||||
Returns:
|
||||
ocl.STLSurf: An oclSTL object containing the triangles derived from
|
||||
the valid objects.
|
||||
|
||||
Raises:
|
||||
CamException: If no mesh, curve, or equivalent object is found in
|
||||
"""
|
||||
me = None
|
||||
oclSTL = ocl.STLSurf()
|
||||
found_mesh = False
|
||||
for collision_object in operation.objects:
|
||||
activate(collision_object)
|
||||
if collision_object.type == "MESH" or collision_object.type == "CURVE" or collision_object.type == "FONT" or collision_object.type == "SURFACE":
|
||||
found_mesh = True
|
||||
global_matrix = mathutils.Matrix.Identity(4)
|
||||
faces = blender_utils.faces_from_mesh(
|
||||
collision_object, global_matrix, operation.use_modifiers)
|
||||
for face in faces:
|
||||
t = ocl.Triangle(ocl.Point(face[0][0]*OCL_SCALE, face[0][1]*OCL_SCALE, (face[0][2]+operation.skin)*OCL_SCALE),
|
||||
ocl.Point(face[1][0]*OCL_SCALE, face[1][1]*OCL_SCALE,
|
||||
(face[1][2]+operation.skin)*OCL_SCALE),
|
||||
ocl.Point(face[2][0]*OCL_SCALE, face[2][1]*OCL_SCALE, (face[2][2]+operation.skin)*OCL_SCALE))
|
||||
oclSTL.addTriangle(t)
|
||||
# FIXME needs to work with collections
|
||||
if not found_mesh:
|
||||
raise CamException(
|
||||
"This Operation Requires a Mesh or Curve Object or Equivalent (e.g. Text, Volume).")
|
||||
return oclSTL
|
||||
|
||||
|
||||
async def ocl_sample(operation, chunks, use_cached_mesh=False):
|
||||
"""Sample points using a specified cutter and operation.
|
||||
|
||||
This function takes an operation and a list of chunks, and samples
|
||||
points based on the specified cutter type and its parameters. It
|
||||
supports various cutter types such as 'END', 'BALLNOSE', 'VCARVE',
|
||||
'CYLCONE', 'BALLCONE', and 'BULLNOSE'. The function can also utilize a
|
||||
cached mesh for efficiency. The sampled points are returned after
|
||||
processing all chunks.
|
||||
|
||||
Args:
|
||||
operation (Operation): An object containing the cutter type, diameter,
|
||||
minimum Z value, tip angle, and other relevant parameters.
|
||||
chunks (list): A list of chunk objects that contain point data to be
|
||||
processed.
|
||||
use_cached_mesh (bool): A flag indicating whether to use a cached mesh
|
||||
if available. Defaults to False.
|
||||
|
||||
Returns:
|
||||
list: A list of sampled CL points generated by the cutter.
|
||||
"""
|
||||
|
||||
global _PREVIOUS_OCL_MESH
|
||||
|
||||
op_cutter_type = operation.cutter_type
|
||||
op_cutter_diameter = operation.cutter_diameter
|
||||
op_minz = operation.minz
|
||||
op_cutter_tip_angle = radians(operation.cutter_tip_angle)/2
|
||||
if op_cutter_type == "VCARVE":
|
||||
cutter_length = (op_cutter_diameter/tan(op_cutter_tip_angle))/2
|
||||
else:
|
||||
cutter_length = 10
|
||||
|
||||
cutter = None
|
||||
|
||||
if op_cutter_type == 'END':
|
||||
cutter = ocl.CylCutter((op_cutter_diameter + operation.skin * 2) * 1000, cutter_length)
|
||||
elif op_cutter_type == 'BALLNOSE':
|
||||
cutter = ocl.BallCutter((op_cutter_diameter + operation.skin * 2) * 1000, cutter_length)
|
||||
elif op_cutter_type == 'VCARVE':
|
||||
cutter = ocl.ConeCutter((op_cutter_diameter + operation.skin * 2)
|
||||
* 1000, op_cutter_tip_angle, cutter_length)
|
||||
elif op_cutter_type == 'CYLCONE':
|
||||
cutter = ocl.CylConeCutter((operation.cylcone_diameter/2+operation.skin)*2000,
|
||||
(op_cutter_diameter + operation.skin * 2) * 1000, op_cutter_tip_angle)
|
||||
elif op_cutter_type == 'BALLCONE':
|
||||
cutter = ocl.BallConeCutter((operation.ball_radius + operation.skin) * 2000,
|
||||
(op_cutter_diameter + operation.skin * 2) * 1000, op_cutter_tip_angle)
|
||||
elif op_cutter_type == 'BULLNOSE':
|
||||
cutter = ocl.BullCutter((op_cutter_diameter + operation.skin * 2) *
|
||||
1000, operation.bull_corner_radius*1000, cutter_length)
|
||||
else:
|
||||
print("Cutter Unsupported: {0}\n".format(op_cutter_type))
|
||||
quit()
|
||||
|
||||
bdc = ocl.BatchDropCutter()
|
||||
if use_cached_mesh and _PREVIOUS_OCL_MESH is not None:
|
||||
oclSTL = _PREVIOUS_OCL_MESH
|
||||
else:
|
||||
oclSTL = get_oclSTL(operation)
|
||||
_PREVIOUS_OCL_MESH = oclSTL
|
||||
bdc.setSTL(oclSTL)
|
||||
bdc.setCutter(cutter)
|
||||
|
||||
for chunk in chunks:
|
||||
for coord in chunk.get_points_np():
|
||||
bdc.appendPoint(ocl.CLPoint(coord[0] * 1000, coord[1] * 1000, op_minz * 1000))
|
||||
await progress_async("OpenCAMLib Sampling")
|
||||
bdc.run()
|
||||
|
||||
cl_points = bdc.getCLPoints()
|
||||
|
||||
return cl_points
|
||||
|
|
|
@ -1,210 +1,349 @@
|
|||
"""BlenderCAM 'oclSample.py'
|
||||
|
||||
Functions used by OpenCAMLib sampling.
|
||||
"""
|
||||
|
||||
import os
|
||||
from subprocess import call
|
||||
import tempfile
|
||||
|
||||
import numpy as np
|
||||
try:
|
||||
import ocl
|
||||
except ImportError:
|
||||
try:
|
||||
import opencamlib as ocl
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
import bpy
|
||||
|
||||
from ..constants import BULLET_SCALE
|
||||
from ..simple import activate
|
||||
from .. import utils
|
||||
from ..cam_chunk import camPathChunk
|
||||
from ..async_op import progress_async
|
||||
from .oclSample import (
|
||||
get_oclSTL,
|
||||
ocl_sample
|
||||
)
|
||||
|
||||
OCL_SCALE = 1000.0
|
||||
|
||||
PYTHON_BIN = None
|
||||
|
||||
|
||||
def pointSamplesFromOCL(points, samples):
|
||||
for index, point in enumerate(points):
|
||||
point[2] = samples[index].z / OCL_SCALE
|
||||
|
||||
|
||||
def chunkPointSamplesFromOCL(chunks, samples):
|
||||
s_index = 0
|
||||
for ch in chunks:
|
||||
ch_points = ch.count()
|
||||
z_vals = np.array([p.z for p in samples[s_index:s_index+ch_points]])
|
||||
z_vals /= OCL_SCALE
|
||||
ch.setZ(z_vals)
|
||||
s_index += ch_points
|
||||
# p_index = 0
|
||||
# for point in ch.points:
|
||||
# if len(point) == 2 or point[2] != 2:
|
||||
# z_sample = samples[s_index].z / OCL_SCALE
|
||||
# ch.points[p_index] = (point[0], point[1], z_sample)
|
||||
# # print(str(point[2]))
|
||||
# else:
|
||||
# ch.points[p_index] = (point[0], point[1], 1)
|
||||
# p_index += 1
|
||||
# s_index += 1
|
||||
|
||||
|
||||
def chunkPointsResampleFromOCL(chunks, samples):
|
||||
s_index = 0
|
||||
for ch in chunks:
|
||||
ch_points = ch.count()
|
||||
z_vals = np.array([p.z for p in samples[s_index:s_index+ch_points]])
|
||||
z_vals /= OCL_SCALE
|
||||
ch.setZ(z_vals)
|
||||
s_index += ch_points
|
||||
|
||||
# s_index = 0
|
||||
# for ch in chunks:
|
||||
# p_index = 0
|
||||
# for point in ch.points:
|
||||
# if len(point) == 2 or point[2] != 2:
|
||||
# z_sample = samples[s_index].z / OCL_SCALE
|
||||
# ch.points[p_index] = (point[0], point[1], z_sample)
|
||||
# # print(str(point[2]))
|
||||
# else:
|
||||
# ch.points[p_index] = (point[0], point[1], 1)
|
||||
# p_index += 1
|
||||
# s_index += 1
|
||||
|
||||
|
||||
def exportModelsToSTL(operation):
|
||||
file_number = 0
|
||||
for collision_object in operation.objects:
|
||||
activate(collision_object)
|
||||
bpy.ops.object.duplicate(linked=False)
|
||||
# collision_object = bpy.context.scene.objects.active
|
||||
# bpy.context.scene.objects.selected = collision_object
|
||||
file_name = os.path.join(tempfile.gettempdir(), "model{0}.stl".format(str(file_number)))
|
||||
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
|
||||
bpy.ops.transform.resize(value=(OCL_SCALE, OCL_SCALE, OCL_SCALE), constraint_axis=(False, False, False),
|
||||
orient_type='GLOBAL', mirror=False, use_proportional_edit=False,
|
||||
proportional_edit_falloff='SMOOTH', proportional_size=1, snap=False,
|
||||
snap_target='CLOSEST', snap_point=(0, 0, 0), snap_align=False, snap_normal=(0, 0, 0),
|
||||
texture_space=False, release_confirm=False)
|
||||
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
|
||||
bpy.ops.export_mesh.stl(check_existing=True, filepath=file_name, filter_glob="*.stl", use_selection=True,
|
||||
ascii=False, use_mesh_modifiers=True, axis_forward='Y', axis_up='Z', global_scale=1.0)
|
||||
bpy.ops.object.delete()
|
||||
file_number += 1
|
||||
|
||||
|
||||
async def oclSamplePoints(operation, points):
|
||||
samples = await ocl_sample(operation, points)
|
||||
pointSamplesFromOCL(points, samples)
|
||||
|
||||
|
||||
async def oclSample(operation, chunks):
|
||||
samples = await ocl_sample(operation, chunks)
|
||||
chunkPointSamplesFromOCL(chunks, samples)
|
||||
|
||||
|
||||
async def oclResampleChunks(operation, chunks_to_resample, use_cached_mesh):
|
||||
tmp_chunks = list()
|
||||
tmp_chunks.append(camPathChunk(inpoints=[]))
|
||||
for chunk, i_start, i_length in chunks_to_resample:
|
||||
tmp_chunks[0].extend(chunk.get_points_np()[i_start:i_start+i_length])
|
||||
print(i_start, i_length, len(tmp_chunks[0].points))
|
||||
|
||||
samples = await ocl_sample(operation, tmp_chunks, use_cached_mesh=use_cached_mesh)
|
||||
|
||||
sample_index = 0
|
||||
for chunk, i_start, i_length in chunks_to_resample:
|
||||
z = np.array([p.z for p in samples[sample_index:sample_index+i_length]]) / OCL_SCALE
|
||||
pts = chunk.get_points_np()
|
||||
pt_z = pts[i_start:i_start+i_length, 2]
|
||||
pt_z = np.where(z > pt_z, z, pt_z)
|
||||
|
||||
sample_index += i_length
|
||||
# for p_index in range(i_start, i_start + i_length):
|
||||
# z = samples[sample_index].z / OCL_SCALE
|
||||
# sample_index += 1
|
||||
# if z > chunk.points[p_index][2]:
|
||||
# chunk.points[p_index][2] = z
|
||||
|
||||
|
||||
def oclWaterlineLayerHeights(operation):
|
||||
layers = []
|
||||
l_last = operation.minz
|
||||
l_step = operation.stepdown
|
||||
l_first = operation.maxz - l_step
|
||||
l_depth = l_first
|
||||
while l_depth > (l_last + 0.0000001):
|
||||
layers.append(l_depth)
|
||||
l_depth -= l_step
|
||||
layers.append(l_last)
|
||||
return layers
|
||||
|
||||
|
||||
# def oclGetMedialAxis(operation, chunks):
|
||||
# oclWaterlineHeightsToOCL(operation)
|
||||
# operationSettingsToOCL(operation)
|
||||
# curvesToOCL(operation)
|
||||
# call([PYTHON_BIN, os.path.join(bpy.utils.script_path_pref(), "addons", "cam", "opencamlib", "ocl.py")])
|
||||
# waterlineChunksFromOCL(operation, chunks)
|
||||
|
||||
|
||||
async def oclGetWaterline(operation, chunks):
|
||||
layers = oclWaterlineLayerHeights(operation)
|
||||
oclSTL = get_oclSTL(operation)
|
||||
|
||||
op_cutter_type = operation.cutter_type
|
||||
op_cutter_diameter = operation.cutter_diameter
|
||||
op_minz = operation.minz
|
||||
if op_cutter_type == "VCARVE":
|
||||
op_cutter_tip_angle = operation['cutter_tip_angle']
|
||||
|
||||
cutter = None
|
||||
# TODO: automatically determine necessary cutter length depending on object size
|
||||
cutter_length = 150
|
||||
|
||||
if op_cutter_type == 'END':
|
||||
cutter = ocl.CylCutter((op_cutter_diameter + operation.skin * 2) * 1000, cutter_length)
|
||||
elif op_cutter_type == 'BALLNOSE':
|
||||
cutter = ocl.BallCutter((op_cutter_diameter + operation.skin * 2) * 1000, cutter_length)
|
||||
elif op_cutter_type == 'VCARVE':
|
||||
cutter = ocl.ConeCutter((op_cutter_diameter + operation.skin * 2)
|
||||
* 1000, op_cutter_tip_angle, cutter_length)
|
||||
else:
|
||||
print("Cutter unsupported: {0}\n".format(op_cutter_type))
|
||||
quit()
|
||||
|
||||
waterline = ocl.Waterline()
|
||||
waterline.setSTL(oclSTL)
|
||||
waterline.setCutter(cutter)
|
||||
waterline.setSampling(0.1) # TODO: add sampling setting to UI
|
||||
last_pos = [0, 0, 0]
|
||||
for count, height in enumerate(layers):
|
||||
layer_chunks = []
|
||||
await progress_async("Waterline", int((100*count)/len(layers)))
|
||||
waterline.reset()
|
||||
waterline.setZ(height * OCL_SCALE)
|
||||
waterline.run2()
|
||||
wl_loops = waterline.getLoops()
|
||||
for l in wl_loops:
|
||||
inpoints = []
|
||||
for p in l:
|
||||
inpoints.append((p.x / OCL_SCALE, p.y / OCL_SCALE, p.z / OCL_SCALE))
|
||||
inpoints.append(inpoints[0])
|
||||
chunk = camPathChunk(inpoints=inpoints)
|
||||
chunk.closed = True
|
||||
layer_chunks.append(chunk)
|
||||
# sort chunks so that ordering is stable
|
||||
chunks.extend(await utils.sortChunks(layer_chunks, operation, last_pos=last_pos))
|
||||
if len(chunks) > 0:
|
||||
last_pos = chunks[-1].get_point(-1)
|
||||
|
||||
# def oclFillMedialAxis(operation):
|
||||
"""BlenderCAM 'oclSample.py'
|
||||
|
||||
Functions used by OpenCAMLib sampling.
|
||||
"""
|
||||
|
||||
import os
|
||||
from subprocess import call
|
||||
import tempfile
|
||||
|
||||
import numpy as np
|
||||
try:
|
||||
import ocl
|
||||
except ImportError:
|
||||
try:
|
||||
import opencamlib as ocl
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
import bpy
|
||||
|
||||
from ..constants import BULLET_SCALE
|
||||
from ..simple import activate
|
||||
from .. import utils
|
||||
from ..cam_chunk import camPathChunk
|
||||
from ..async_op import progress_async
|
||||
from .oclSample import (
|
||||
get_oclSTL,
|
||||
ocl_sample
|
||||
)
|
||||
|
||||
OCL_SCALE = 1000.0
|
||||
|
||||
PYTHON_BIN = None
|
||||
|
||||
|
||||
def pointSamplesFromOCL(points, samples):
|
||||
"""Update the z-coordinate of points based on corresponding sample values.
|
||||
|
||||
This function iterates over a list of points and updates the
|
||||
z-coordinate of each point using the z value from the corresponding
|
||||
sample. The z value is scaled by a predefined constant, OCL_SCALE. It is
|
||||
assumed that the length of the points list matches the length of the
|
||||
samples list.
|
||||
|
||||
Args:
|
||||
points (list): A list of points, where each point is expected to be
|
||||
a list or array with at least three elements.
|
||||
samples (list): A list of sample objects, where each sample is
|
||||
expected to have a z attribute.
|
||||
"""
|
||||
for index, point in enumerate(points):
|
||||
point[2] = samples[index].z / OCL_SCALE
|
||||
|
||||
|
||||
def chunkPointSamplesFromOCL(chunks, samples):
|
||||
"""Chunk point samples from OCL.
|
||||
|
||||
This function processes a list of chunks and corresponding samples,
|
||||
extracting the z-values from the samples and scaling them according to a
|
||||
predefined constant (OCL_SCALE). It sets the scaled z-values for each
|
||||
chunk based on the number of points in that chunk.
|
||||
|
||||
Args:
|
||||
chunks (list): A list of chunk objects that have a method `count()`
|
||||
and a method `setZ()`.
|
||||
samples (list): A list of sample objects from which z-values are
|
||||
extracted.
|
||||
"""
|
||||
s_index = 0
|
||||
for ch in chunks:
|
||||
ch_points = ch.count()
|
||||
z_vals = np.array([p.z for p in samples[s_index:s_index+ch_points]])
|
||||
z_vals /= OCL_SCALE
|
||||
ch.setZ(z_vals)
|
||||
s_index += ch_points
|
||||
# p_index = 0
|
||||
# for point in ch.points:
|
||||
# if len(point) == 2 or point[2] != 2:
|
||||
# z_sample = samples[s_index].z / OCL_SCALE
|
||||
# ch.points[p_index] = (point[0], point[1], z_sample)
|
||||
# # print(str(point[2]))
|
||||
# else:
|
||||
# ch.points[p_index] = (point[0], point[1], 1)
|
||||
# p_index += 1
|
||||
# s_index += 1
|
||||
|
||||
|
||||
def chunkPointsResampleFromOCL(chunks, samples):
|
||||
"""Resample the Z values of points in chunks based on provided samples.
|
||||
|
||||
This function iterates through a list of chunks and resamples the Z
|
||||
values of the points in each chunk using the corresponding samples. It
|
||||
first counts the number of points in each chunk, then extracts the Z
|
||||
values from the samples, scales them by a predefined constant
|
||||
(OCL_SCALE), and sets the resampled Z values back to the chunk.
|
||||
|
||||
Args:
|
||||
chunks (list): A list of chunk objects, each containing points that need
|
||||
to be resampled.
|
||||
samples (list): A list of sample objects from which Z values are extracted.
|
||||
"""
|
||||
s_index = 0
|
||||
for ch in chunks:
|
||||
ch_points = ch.count()
|
||||
z_vals = np.array([p.z for p in samples[s_index:s_index+ch_points]])
|
||||
z_vals /= OCL_SCALE
|
||||
ch.setZ(z_vals)
|
||||
s_index += ch_points
|
||||
|
||||
# s_index = 0
|
||||
# for ch in chunks:
|
||||
# p_index = 0
|
||||
# for point in ch.points:
|
||||
# if len(point) == 2 or point[2] != 2:
|
||||
# z_sample = samples[s_index].z / OCL_SCALE
|
||||
# ch.points[p_index] = (point[0], point[1], z_sample)
|
||||
# # print(str(point[2]))
|
||||
# else:
|
||||
# ch.points[p_index] = (point[0], point[1], 1)
|
||||
# p_index += 1
|
||||
# s_index += 1
|
||||
|
||||
|
||||
def exportModelsToSTL(operation):
|
||||
"""Export models to STL format.
|
||||
|
||||
This function takes an operation containing a collection of collision
|
||||
objects and exports each object as an STL file. It duplicates each
|
||||
object, applies transformations, and resizes them according to a
|
||||
predefined scale before exporting them to the temporary directory. The
|
||||
exported files are named sequentially as "model0.stl", "model1.stl",
|
||||
etc. After exporting, the function deletes the duplicated objects to
|
||||
clean up the scene.
|
||||
|
||||
Args:
|
||||
operation: An object containing a collection of collision objects to be exported.
|
||||
"""
|
||||
file_number = 0
|
||||
for collision_object in operation.objects:
|
||||
activate(collision_object)
|
||||
bpy.ops.object.duplicate(linked=False)
|
||||
# collision_object = bpy.context.scene.objects.active
|
||||
# bpy.context.scene.objects.selected = collision_object
|
||||
file_name = os.path.join(tempfile.gettempdir(), "model{0}.stl".format(str(file_number)))
|
||||
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
|
||||
bpy.ops.transform.resize(value=(OCL_SCALE, OCL_SCALE, OCL_SCALE), constraint_axis=(False, False, False),
|
||||
orient_type='GLOBAL', mirror=False, use_proportional_edit=False,
|
||||
proportional_edit_falloff='SMOOTH', proportional_size=1, snap=False,
|
||||
snap_target='CLOSEST', snap_point=(0, 0, 0), snap_align=False, snap_normal=(0, 0, 0),
|
||||
texture_space=False, release_confirm=False)
|
||||
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
|
||||
bpy.ops.export_mesh.stl(check_existing=True, filepath=file_name, filter_glob="*.stl", use_selection=True,
|
||||
ascii=False, use_mesh_modifiers=True, axis_forward='Y', axis_up='Z', global_scale=1.0)
|
||||
bpy.ops.object.delete()
|
||||
file_number += 1
|
||||
|
||||
|
||||
async def oclSamplePoints(operation, points):
|
||||
"""Sample points using an operation and process the results.
|
||||
|
||||
This asynchronous function takes an operation and a set of points,
|
||||
samples the points using the specified operation, and then processes the
|
||||
sampled points. The function relies on an external sampling function and
|
||||
a processing function to handle the sampling and post-processing of the
|
||||
data.
|
||||
|
||||
Args:
|
||||
operation (str): The operation to be performed on the points.
|
||||
points (list): A list of points to be sampled.
|
||||
"""
|
||||
|
||||
samples = await ocl_sample(operation, points)
|
||||
pointSamplesFromOCL(points, samples)
|
||||
|
||||
|
||||
async def oclSample(operation, chunks):
|
||||
"""Perform an operation on a set of chunks and process the resulting
|
||||
samples.
|
||||
|
||||
This asynchronous function calls the `ocl_sample` function to obtain
|
||||
samples based on the provided operation and chunks. After retrieving the
|
||||
samples, it processes them using the `chunkPointSamplesFromOCL`
|
||||
function. This is useful for handling large datasets in a chunked
|
||||
manner, allowing for efficient sampling and processing.
|
||||
|
||||
Args:
|
||||
operation (str): The operation to be performed on the chunks.
|
||||
chunks (list): A list of data chunks to be processed.
|
||||
|
||||
Returns:
|
||||
None: This function does not return a value.
|
||||
"""
|
||||
|
||||
samples = await ocl_sample(operation, chunks)
|
||||
chunkPointSamplesFromOCL(chunks, samples)
|
||||
|
||||
|
||||
async def oclResampleChunks(operation, chunks_to_resample, use_cached_mesh):
|
||||
"""Resample chunks of data using OpenCL operations.
|
||||
|
||||
This function takes a list of chunks to resample and performs an OpenCL
|
||||
sampling operation on them. It first prepares a temporary chunk that
|
||||
collects points from the specified chunks. Then, it calls the
|
||||
`ocl_sample` function to perform the sampling operation. After obtaining
|
||||
the samples, it updates the z-coordinates of the points in each chunk
|
||||
based on the sampled values.
|
||||
|
||||
Args:
|
||||
operation (OperationType): The OpenCL operation to be performed.
|
||||
chunks_to_resample (list): A list of tuples, where each tuple contains
|
||||
a chunk object and its corresponding start index and length for
|
||||
resampling.
|
||||
use_cached_mesh (bool): A flag indicating whether to use cached mesh
|
||||
data during the sampling process.
|
||||
|
||||
Returns:
|
||||
None: This function does not return a value but modifies the input
|
||||
chunks in place.
|
||||
"""
|
||||
|
||||
tmp_chunks = list()
|
||||
tmp_chunks.append(camPathChunk(inpoints=[]))
|
||||
for chunk, i_start, i_length in chunks_to_resample:
|
||||
tmp_chunks[0].extend(chunk.get_points_np()[i_start:i_start+i_length])
|
||||
print(i_start, i_length, len(tmp_chunks[0].points))
|
||||
|
||||
samples = await ocl_sample(operation, tmp_chunks, use_cached_mesh=use_cached_mesh)
|
||||
|
||||
sample_index = 0
|
||||
for chunk, i_start, i_length in chunks_to_resample:
|
||||
z = np.array([p.z for p in samples[sample_index:sample_index+i_length]]) / OCL_SCALE
|
||||
pts = chunk.get_points_np()
|
||||
pt_z = pts[i_start:i_start+i_length, 2]
|
||||
pt_z = np.where(z > pt_z, z, pt_z)
|
||||
|
||||
sample_index += i_length
|
||||
# for p_index in range(i_start, i_start + i_length):
|
||||
# z = samples[sample_index].z / OCL_SCALE
|
||||
# sample_index += 1
|
||||
# if z > chunk.points[p_index][2]:
|
||||
# chunk.points[p_index][2] = z
|
||||
|
||||
|
||||
def oclWaterlineLayerHeights(operation):
|
||||
"""Generate a list of waterline layer heights for a given operation.
|
||||
|
||||
This function calculates the heights of waterline layers based on the
|
||||
specified parameters of the operation. It starts from the maximum height
|
||||
and decrements by a specified step until it reaches the minimum height.
|
||||
The resulting list of heights can be used for further processing in
|
||||
operations that require layered depth information.
|
||||
|
||||
Args:
|
||||
operation (object): An object containing the properties `minz`,
|
||||
`maxz`, and `stepdown` which define the
|
||||
minimum height, maximum height, and step size
|
||||
for layer generation, respectively.
|
||||
|
||||
Returns:
|
||||
list: A list of waterline layer heights from maximum to minimum.
|
||||
"""
|
||||
layers = []
|
||||
l_last = operation.minz
|
||||
l_step = operation.stepdown
|
||||
l_first = operation.maxz - l_step
|
||||
l_depth = l_first
|
||||
while l_depth > (l_last + 0.0000001):
|
||||
layers.append(l_depth)
|
||||
l_depth -= l_step
|
||||
layers.append(l_last)
|
||||
return layers
|
||||
|
||||
|
||||
# def oclGetMedialAxis(operation, chunks):
|
||||
# oclWaterlineHeightsToOCL(operation)
|
||||
# operationSettingsToOCL(operation)
|
||||
# curvesToOCL(operation)
|
||||
# call([PYTHON_BIN, os.path.join(bpy.utils.script_path_pref(), "addons", "cam", "opencamlib", "ocl.py")])
|
||||
# waterlineChunksFromOCL(operation, chunks)
|
||||
|
||||
|
||||
async def oclGetWaterline(operation, chunks):
|
||||
"""Generate waterline paths for a given machining operation.
|
||||
|
||||
This function calculates the waterline paths based on the provided
|
||||
machining operation and its parameters. It determines the appropriate
|
||||
cutter type and dimensions, sets up the waterline object with the
|
||||
corresponding STL file, and processes each layer to generate the
|
||||
machining paths. The resulting paths are stored in the provided chunks
|
||||
list. The function also handles different cutter types, including end
|
||||
mills, ball nose cutters, and V-carve cutters.
|
||||
|
||||
Args:
|
||||
operation (Operation): An object representing the machining operation,
|
||||
containing details such as cutter type, diameter, and minimum Z height.
|
||||
chunks (list): A list that will be populated with the generated
|
||||
machining path chunks.
|
||||
"""
|
||||
|
||||
layers = oclWaterlineLayerHeights(operation)
|
||||
oclSTL = get_oclSTL(operation)
|
||||
|
||||
op_cutter_type = operation.cutter_type
|
||||
op_cutter_diameter = operation.cutter_diameter
|
||||
op_minz = operation.minz
|
||||
if op_cutter_type == "VCARVE":
|
||||
op_cutter_tip_angle = operation['cutter_tip_angle']
|
||||
|
||||
cutter = None
|
||||
# TODO: automatically determine necessary cutter length depending on object size
|
||||
cutter_length = 150
|
||||
|
||||
if op_cutter_type == 'END':
|
||||
cutter = ocl.CylCutter((op_cutter_diameter + operation.skin * 2) * 1000, cutter_length)
|
||||
elif op_cutter_type == 'BALLNOSE':
|
||||
cutter = ocl.BallCutter((op_cutter_diameter + operation.skin * 2) * 1000, cutter_length)
|
||||
elif op_cutter_type == 'VCARVE':
|
||||
cutter = ocl.ConeCutter((op_cutter_diameter + operation.skin * 2)
|
||||
* 1000, op_cutter_tip_angle, cutter_length)
|
||||
else:
|
||||
print("Cutter unsupported: {0}\n".format(op_cutter_type))
|
||||
quit()
|
||||
|
||||
waterline = ocl.Waterline()
|
||||
waterline.setSTL(oclSTL)
|
||||
waterline.setCutter(cutter)
|
||||
waterline.setSampling(0.1) # TODO: add sampling setting to UI
|
||||
last_pos = [0, 0, 0]
|
||||
for count, height in enumerate(layers):
|
||||
layer_chunks = []
|
||||
await progress_async("Waterline", int((100*count)/len(layers)))
|
||||
waterline.reset()
|
||||
waterline.setZ(height * OCL_SCALE)
|
||||
waterline.run2()
|
||||
wl_loops = waterline.getLoops()
|
||||
for l in wl_loops:
|
||||
inpoints = []
|
||||
for p in l:
|
||||
inpoints.append((p.x / OCL_SCALE, p.y / OCL_SCALE, p.z / OCL_SCALE))
|
||||
inpoints.append(inpoints[0])
|
||||
chunk = camPathChunk(inpoints=inpoints)
|
||||
chunk.closed = True
|
||||
layer_chunks.append(chunk)
|
||||
# sort chunks so that ordering is stable
|
||||
chunks.extend(await utils.sortChunks(layer_chunks, operation, last_pos=last_pos))
|
||||
if len(chunks) > 0:
|
||||
last_pos = chunks[-1].get_point(-1)
|
||||
|
||||
# def oclFillMedialAxis(operation):
|
||||
|
|
|
@ -52,7 +52,24 @@ class threadCom: # object passed to threads to read background process stdout i
|
|||
|
||||
|
||||
def threadread(tcom):
|
||||
"""Reads Stdout of Background Process, Done This Way to Have It Non-blocking"""
|
||||
"""Reads the standard output of a background process in a non-blocking
|
||||
manner.
|
||||
|
||||
This function reads a line from the standard output of a background
|
||||
process associated with the provided `tcom` object. It searches for a
|
||||
specific substring that indicates progress information, and if found,
|
||||
extracts that information and assigns it to the `outtext` attribute of
|
||||
the `tcom` object. This allows for real-time monitoring of the
|
||||
background process's output without blocking the main thread.
|
||||
|
||||
Args:
|
||||
tcom (object): An object that has a `proc` attribute with a `stdout`
|
||||
stream from which to read the output.
|
||||
|
||||
Returns:
|
||||
None: This function does not return a value; it modifies the `tcom`
|
||||
object in place.
|
||||
"""
|
||||
inline = tcom.proc.stdout.readline()
|
||||
inline = str(inline)
|
||||
s = inline.find('progress{')
|
||||
|
@ -63,7 +80,19 @@ def threadread(tcom):
|
|||
|
||||
@bpy.app.handlers.persistent
|
||||
def timer_update(context):
|
||||
"""Monitoring of Background Processes"""
|
||||
"""Monitor background processes related to camera path calculations.
|
||||
|
||||
This function checks the status of background processes that are
|
||||
responsible for calculating camera paths. It retrieves the current
|
||||
processes and monitors their state. If a process has finished, it
|
||||
updates the corresponding camera operation and reloads the necessary
|
||||
paths. If the process is still running, it restarts the associated
|
||||
thread to continue monitoring.
|
||||
|
||||
Args:
|
||||
context: The context in which the function is called, typically
|
||||
containing information about the current scene and operations.
|
||||
"""
|
||||
text = ''
|
||||
s = bpy.context.scene
|
||||
if hasattr(bpy.ops.object.calculate_cam_paths_background.__class__, 'cam_processes'):
|
||||
|
@ -104,6 +133,22 @@ class PathsBackground(Operator):
|
|||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
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
|
||||
|
@ -139,6 +184,22 @@ class KillPathsBackground(Operator):
|
|||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
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
|
||||
|
@ -156,6 +217,28 @@ class KillPathsBackground(Operator):
|
|||
|
||||
|
||||
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':
|
||||
|
@ -226,6 +309,20 @@ class CalculatePath(Operator, AsyncOperatorMixin):
|
|||
|
||||
@classmethod
|
||||
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:
|
||||
|
@ -234,6 +331,20 @@ class CalculatePath(Operator, AsyncOperatorMixin):
|
|||
return False
|
||||
|
||||
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
|
||||
|
@ -246,6 +357,22 @@ class PathsAll(Operator):
|
|||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
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
|
||||
|
@ -257,6 +384,17 @@ class PathsAll(Operator):
|
|||
return {'FINISHED'}
|
||||
|
||||
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")
|
||||
|
@ -269,6 +407,20 @@ class CamPackObjects(Operator):
|
|||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
"""Execute the operation in the given context.
|
||||
|
||||
This function sets the Blender object mode to 'OBJECT', retrieves the
|
||||
currently selected objects, and calls the `packCurves` function from the
|
||||
`pack` module. It is typically used to finalize operations on selected
|
||||
objects in Blender.
|
||||
|
||||
Args:
|
||||
context: The context in which the operation is executed.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the completion status of the operation.
|
||||
"""
|
||||
|
||||
bpy.ops.object.mode_set(mode='OBJECT') # force object mode
|
||||
obs = bpy.context.selected_objects
|
||||
pack.packCurves()
|
||||
|
@ -287,6 +439,21 @@ class CamSliceObjects(Operator):
|
|||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
"""Execute the slicing operation on the active Blender object.
|
||||
|
||||
This function retrieves the currently active object in the Blender
|
||||
context and performs a slicing operation on it using the `sliceObject`
|
||||
function from the `cam` module. The operation is intended to modify the
|
||||
object based on the slicing logic defined in the external module.
|
||||
|
||||
Args:
|
||||
context: The context in which the operation is executed.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of the operation,
|
||||
typically containing the key 'FINISHED' upon successful execution.
|
||||
"""
|
||||
|
||||
from cam import slice
|
||||
ob = bpy.context.active_object
|
||||
slice.sliceObject(ob)
|
||||
|
@ -297,7 +464,20 @@ class CamSliceObjects(Operator):
|
|||
|
||||
|
||||
def getChainOperations(chain):
|
||||
"""Return Chain Operations, Currently Chain Object Can't Store Operations Directly Due to Blender Limitations"""
|
||||
"""Return chain operations associated with a given chain object.
|
||||
|
||||
This function iterates through the operations of the provided chain
|
||||
object and retrieves the corresponding operations from the current
|
||||
scene's camera operations in Blender. Due to limitations in Blender,
|
||||
chain objects cannot store operations directly, so this function serves
|
||||
to extract and return the relevant operations for further processing.
|
||||
|
||||
Args:
|
||||
chain (object): The chain object from which to retrieve operations.
|
||||
|
||||
Returns:
|
||||
list: A list of operations associated with the given chain object.
|
||||
"""
|
||||
chop = []
|
||||
for cho in chain.operations:
|
||||
for so in bpy.context.scene.cam_operations:
|
||||
|
@ -314,11 +494,40 @@ class PathsChain(Operator, AsyncOperatorMixin):
|
|||
|
||||
@classmethod
|
||||
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
|
||||
chain = s.cam_chains[s.cam_active_chain]
|
||||
return isChainValid(chain, context)[0]
|
||||
|
||||
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]
|
||||
|
@ -353,11 +562,41 @@ class PathExportChain(Operator):
|
|||
|
||||
@classmethod
|
||||
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 isChainValid(chain, context)[0]
|
||||
|
||||
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]
|
||||
|
@ -380,6 +619,22 @@ class PathExport(Operator):
|
|||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
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]
|
||||
|
@ -407,6 +662,24 @@ class CAMSimulate(Operator, AsyncOperatorMixin):
|
|||
)
|
||||
|
||||
async def execute_async(self, context):
|
||||
"""Execute an asynchronous simulation operation based on the active camera
|
||||
operation.
|
||||
|
||||
This method retrieves the current scene and the active camera operation.
|
||||
It constructs the operation name and checks if the corresponding object
|
||||
exists in the Blender data. If it does, it attempts to run the
|
||||
simulation asynchronously. If the simulation is cancelled, it returns a
|
||||
cancellation status. If the object does not exist, it reports an error
|
||||
and returns a finished status.
|
||||
|
||||
Args:
|
||||
context: The context in which the operation is executed.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the status of the operation, either
|
||||
{'CANCELLED'} or {'FINISHED'}.
|
||||
"""
|
||||
|
||||
s = bpy.context.scene
|
||||
operation = s.cam_operations[s.cam_active_operation]
|
||||
|
||||
|
@ -423,6 +696,18 @@ class CAMSimulate(Operator, AsyncOperatorMixin):
|
|||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
"""Draws the user interface for selecting camera operations.
|
||||
|
||||
This method creates a layout element in the user interface that allows
|
||||
users to search and select a specific camera operation from a list of
|
||||
available operations defined in the current scene. It utilizes the
|
||||
Blender Python API to integrate with the UI.
|
||||
|
||||
Args:
|
||||
context: The context in which the drawing occurs, typically
|
||||
provided by Blender's UI system.
|
||||
"""
|
||||
|
||||
layout = self.layout
|
||||
layout.prop_search(self, "operation",
|
||||
bpy.context.scene, "cam_operations")
|
||||
|
@ -437,6 +722,20 @@ class CAMSimulateChain(Operator, AsyncOperatorMixin):
|
|||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
"""Check the validity of the active camera chain in the scene.
|
||||
|
||||
This method retrieves the currently active camera chain from the scene's
|
||||
camera chains and checks its validity using the `isChainValid` function.
|
||||
It returns a boolean indicating whether the active camera chain is
|
||||
valid.
|
||||
|
||||
Args:
|
||||
context (object): The context containing the scene and its properties.
|
||||
|
||||
Returns:
|
||||
bool: True if the active camera chain is valid, False otherwise.
|
||||
"""
|
||||
|
||||
s = context.scene
|
||||
chain = s.cam_chains[s.cam_active_chain]
|
||||
return isChainValid(chain, context)[0]
|
||||
|
@ -448,6 +747,23 @@ class CAMSimulateChain(Operator, AsyncOperatorMixin):
|
|||
)
|
||||
|
||||
async def execute_async(self, context):
|
||||
"""Execute an asynchronous simulation for a specified camera chain.
|
||||
|
||||
This method retrieves the active camera chain from the current Blender
|
||||
scene and determines the operations associated with that chain. It
|
||||
checks if all operations are valid and can be simulated. If valid, it
|
||||
proceeds to execute the simulation asynchronously. If any operation is
|
||||
invalid, it logs a message and returns a finished status without
|
||||
performing the simulation.
|
||||
|
||||
Args:
|
||||
context: The context in which the operation is executed.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the status of the operation, either
|
||||
operation completed successfully.
|
||||
"""
|
||||
|
||||
s = bpy.context.scene
|
||||
chain = s.cam_chains[s.cam_active_chain]
|
||||
chainops = getChainOperations(chain)
|
||||
|
@ -468,6 +784,18 @@ class CAMSimulateChain(Operator, AsyncOperatorMixin):
|
|||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
"""Draw the user interface for selecting camera operations.
|
||||
|
||||
This function creates a user interface element that allows the user to
|
||||
search and select a specific camera operation from a list of available
|
||||
operations in the current scene. It utilizes the Blender Python API to
|
||||
create a property search layout.
|
||||
|
||||
Args:
|
||||
context: The context in which the drawing occurs, typically containing
|
||||
information about the current scene and UI elements.
|
||||
"""
|
||||
|
||||
layout = self.layout
|
||||
layout.prop_search(self, "operation",
|
||||
bpy.context.scene, "cam_operations")
|
||||
|
@ -484,6 +812,21 @@ class CamChainAdd(Operator):
|
|||
return context.scene is not None
|
||||
|
||||
def execute(self, context):
|
||||
"""Execute the camera chain creation in the given context.
|
||||
|
||||
This function adds a new camera chain to the current scene in Blender.
|
||||
It updates the active camera chain index and assigns a name and filename
|
||||
to the newly created chain. The function is intended to be called within
|
||||
a Blender operator context.
|
||||
|
||||
Args:
|
||||
context: The context in which the operation is executed.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the operation's completion status,
|
||||
specifically returning {'FINISHED'} upon successful execution.
|
||||
"""
|
||||
|
||||
# main(context)
|
||||
s = bpy.context.scene
|
||||
s.cam_chains.add()
|
||||
|
@ -507,6 +850,20 @@ class CamChainRemove(Operator):
|
|||
return context.scene is not None
|
||||
|
||||
def execute(self, context):
|
||||
"""Execute the camera chain removal process.
|
||||
|
||||
This function removes the currently active camera chain from the scene
|
||||
and decrements the active camera chain index if it is greater than zero.
|
||||
It modifies the Blender context to reflect these changes.
|
||||
|
||||
Args:
|
||||
context: The context in which the function is executed.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the status of the operation,
|
||||
specifically {'FINISHED'} upon successful execution.
|
||||
"""
|
||||
|
||||
bpy.context.scene.cam_chains.remove(bpy.context.scene.cam_active_chain)
|
||||
if bpy.context.scene.cam_active_chain > 0:
|
||||
bpy.context.scene.cam_active_chain -= 1
|
||||
|
@ -525,6 +882,21 @@ class CamChainOperationAdd(Operator):
|
|||
return context.scene is not None
|
||||
|
||||
def execute(self, context):
|
||||
"""Execute an operation in the active camera chain.
|
||||
|
||||
This function retrieves the active camera chain from the current scene
|
||||
and adds a new operation to it. It increments the active operation index
|
||||
and assigns the name of the currently selected camera operation to the
|
||||
newly added operation. This is typically used in the context of managing
|
||||
camera operations in a 3D environment.
|
||||
|
||||
Args:
|
||||
context: The context in which the operation is executed.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the execution status, typically {'FINISHED'}.
|
||||
"""
|
||||
|
||||
s = bpy.context.scene
|
||||
chain = s.cam_chains[s.cam_active_chain]
|
||||
s = bpy.context.scene
|
||||
|
@ -545,6 +917,22 @@ class CamChainOperationUp(Operator):
|
|||
return context.scene is not None
|
||||
|
||||
def execute(self, context):
|
||||
"""Execute the operation to move the active camera operation in the chain.
|
||||
|
||||
This function retrieves the current scene and the active camera chain.
|
||||
If there is an active operation (i.e., its index is greater than 0), it
|
||||
moves the operation one step up in the chain by adjusting the indices
|
||||
accordingly. After moving the operation, it updates the active operation
|
||||
index to reflect the change.
|
||||
|
||||
Args:
|
||||
context: The context in which the operation is executed.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of the operation,
|
||||
specifically returning {'FINISHED'} upon successful execution.
|
||||
"""
|
||||
|
||||
s = bpy.context.scene
|
||||
chain = s.cam_chains[s.cam_active_chain]
|
||||
a = chain.active_operation
|
||||
|
@ -565,6 +953,21 @@ class CamChainOperationDown(Operator):
|
|||
return context.scene is not None
|
||||
|
||||
def execute(self, context):
|
||||
"""Execute the operation to move the active camera operation in the chain.
|
||||
|
||||
This function retrieves the current scene and the active camera chain.
|
||||
It checks if the active operation can be moved down in the list of
|
||||
operations. If so, it moves the active operation one position down and
|
||||
updates the active operation index accordingly.
|
||||
|
||||
Args:
|
||||
context: The context in which the operation is executed.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of the operation,
|
||||
specifically {'FINISHED'} when the operation completes successfully.
|
||||
"""
|
||||
|
||||
s = bpy.context.scene
|
||||
chain = s.cam_chains[s.cam_active_chain]
|
||||
a = chain.active_operation
|
||||
|
@ -585,6 +988,23 @@ class CamChainOperationRemove(Operator):
|
|||
return context.scene is not None
|
||||
|
||||
def execute(self, context):
|
||||
"""Execute the operation to remove the active operation from the camera
|
||||
chain.
|
||||
|
||||
This method accesses the current scene and retrieves the active camera
|
||||
chain. It then removes the currently active operation from that chain
|
||||
and adjusts the index of the active operation accordingly. If the active
|
||||
operation index becomes negative, it resets it to zero to ensure it
|
||||
remains within valid bounds.
|
||||
|
||||
Args:
|
||||
context: The context in which the operation is executed.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the execution status, typically
|
||||
containing {'FINISHED'} upon successful completion.
|
||||
"""
|
||||
|
||||
s = bpy.context.scene
|
||||
chain = s.cam_chains[s.cam_active_chain]
|
||||
chain.operations.remove(chain.active_operation)
|
||||
|
@ -595,7 +1015,13 @@ class CamChainOperationRemove(Operator):
|
|||
|
||||
|
||||
def fixUnits():
|
||||
"""Sets up Units for BlenderCAM"""
|
||||
"""Set up units for BlenderCAM.
|
||||
|
||||
This function configures the unit settings for the current Blender
|
||||
scene. It sets the rotation system to degrees and the scale length to
|
||||
1.0, ensuring that the units are appropriately configured for use within
|
||||
BlenderCAM.
|
||||
"""
|
||||
s = bpy.context.scene
|
||||
|
||||
s.unit_settings.system_rotation = 'DEGREES'
|
||||
|
@ -615,6 +1041,19 @@ class CamOperationAdd(Operator):
|
|||
return context.scene is not None
|
||||
|
||||
def execute(self, context):
|
||||
"""Execute the camera operation based on the active object in the scene.
|
||||
|
||||
This method retrieves the active object from the Blender context and
|
||||
performs operations related to camera settings. It checks if an object
|
||||
is selected and retrieves its bounding box dimensions. If no object is
|
||||
found, it reports an error and cancels the operation. If an object is
|
||||
present, it adds a new camera operation to the scene, sets its
|
||||
properties, and ensures that a machine area object is present.
|
||||
|
||||
Args:
|
||||
context: The context in which the operation is executed.
|
||||
"""
|
||||
|
||||
s = bpy.context.scene
|
||||
fixUnits()
|
||||
|
||||
|
@ -652,6 +1091,25 @@ class CamOperationCopy(Operator):
|
|||
return context.scene is not None
|
||||
|
||||
def execute(self, context):
|
||||
"""Execute the camera operation in the given context.
|
||||
|
||||
This method handles the execution of camera operations within the
|
||||
Blender scene. It first checks if there are any camera operations
|
||||
available. If not, it returns a cancellation status. If there are
|
||||
operations, it copies the active operation, increments the active
|
||||
operation index, and updates the name and filename of the new operation.
|
||||
The function also ensures that the new operation's name is unique by
|
||||
appending a copy suffix or incrementing a numeric suffix.
|
||||
|
||||
Args:
|
||||
context: The context in which the operation is executed.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the status of the operation,
|
||||
either {'CANCELLED'} if no operations are available or
|
||||
{'FINISHED'} if the operation was successfully executed.
|
||||
"""
|
||||
|
||||
# main(context)
|
||||
scene = bpy.context.scene
|
||||
|
||||
|
@ -703,6 +1161,24 @@ class CamOperationRemove(Operator):
|
|||
return context.scene is not None
|
||||
|
||||
def execute(self, context):
|
||||
"""Execute the camera operation in the given context.
|
||||
|
||||
This function performs the active camera operation by deleting the
|
||||
associated object from the scene. It checks if there are any camera
|
||||
operations available and handles the deletion of the active operation's
|
||||
object. If the active operation is removed, it updates the active
|
||||
operation index accordingly. Additionally, it manages a dictionary that
|
||||
tracks hidden objects.
|
||||
|
||||
Args:
|
||||
context (bpy.context): The Blender context containing the scene and operations.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of the operation, either
|
||||
{'CANCELLED'} if no operations are available or {'FINISHED'} if the
|
||||
operation was successfully executed.
|
||||
"""
|
||||
|
||||
scene = context.scene
|
||||
try:
|
||||
if len(scene.cam_operations) == 0:
|
||||
|
@ -748,6 +1224,23 @@ class CamOperationMove(Operator):
|
|||
return context.scene is not None
|
||||
|
||||
def execute(self, context):
|
||||
"""Execute a camera operation based on the specified direction.
|
||||
|
||||
This method modifies the active camera operation in the Blender context
|
||||
based on the direction specified. If the direction is 'UP', it moves the
|
||||
active operation up in the list, provided it is not already at the top.
|
||||
Conversely, if the direction is not 'UP', it moves the active operation
|
||||
down in the list, as long as it is not at the bottom. The method updates
|
||||
the active operation index accordingly.
|
||||
|
||||
Args:
|
||||
context: The context in which the operation is executed.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the operation has finished, with
|
||||
the key 'FINISHED'.
|
||||
"""
|
||||
|
||||
# main(context)
|
||||
a = bpy.context.scene.cam_active_operation
|
||||
cops = bpy.context.scene.cam_operations
|
||||
|
@ -775,6 +1268,22 @@ class CamOrientationAdd(Operator):
|
|||
return context.scene is not None
|
||||
|
||||
def execute(self, context):
|
||||
"""Execute the camera orientation operation in Blender.
|
||||
|
||||
This function retrieves the active camera operation from the current
|
||||
scene, creates an empty object to represent the camera orientation, and
|
||||
adds it to a specified group. The empty object is named based on the
|
||||
operation's name and the current count of objects in the group. The size
|
||||
of the empty object is set to a predefined value for visibility.
|
||||
|
||||
Args:
|
||||
context: The context in which the operation is executed.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the operation's completion status,
|
||||
typically {'FINISHED'}.
|
||||
"""
|
||||
|
||||
s = bpy.context.scene
|
||||
a = s.cam_active_operation
|
||||
o = s.cam_operations[a]
|
||||
|
@ -802,6 +1311,21 @@ class CamBridgesAdd(Operator):
|
|||
return context.scene is not None
|
||||
|
||||
def execute(self, context):
|
||||
"""Execute the camera operation in the given context.
|
||||
|
||||
This function retrieves the active camera operation from the current
|
||||
scene and adds automatic bridges to it. It is typically called within
|
||||
the context of a Blender operator to perform specific actions related to
|
||||
camera operations.
|
||||
|
||||
Args:
|
||||
context: The context in which the operation is executed.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of the operation, typically
|
||||
containing the key 'FINISHED' to signify successful completion.
|
||||
"""
|
||||
|
||||
s = bpy.context.scene
|
||||
a = s.cam_active_operation
|
||||
o = s.cam_operations[a]
|
||||
|
|
|
@ -40,6 +40,24 @@ from . import (
|
|||
|
||||
|
||||
def srotate(s, r, x, y):
|
||||
"""Rotate a polygon's coordinates around a specified point.
|
||||
|
||||
This function takes a polygon and rotates its exterior coordinates
|
||||
around a given point (x, y) by a specified angle (r) in radians. It uses
|
||||
the Euler rotation to compute the new coordinates for each point in the
|
||||
polygon's exterior. The resulting coordinates are then used to create a
|
||||
new polygon.
|
||||
|
||||
Args:
|
||||
s (shapely.geometry.Polygon): The polygon to be rotated.
|
||||
r (float): The angle of rotation in radians.
|
||||
x (float): The x-coordinate of the point around which to rotate.
|
||||
y (float): The y-coordinate of the point around which to rotate.
|
||||
|
||||
Returns:
|
||||
shapely.geometry.Polygon: A new polygon with the rotated coordinates.
|
||||
"""
|
||||
|
||||
ncoords = []
|
||||
e = Euler((0, 0, r))
|
||||
for p in s.exterior.coords:
|
||||
|
@ -53,6 +71,23 @@ def srotate(s, r, x, y):
|
|||
|
||||
|
||||
def packCurves():
|
||||
"""Pack selected curves into a defined area based on specified settings.
|
||||
|
||||
This function organizes selected curve objects in Blender by packing
|
||||
them into a specified area defined by the camera pack settings. It
|
||||
calculates the optimal positions for each curve while considering
|
||||
parameters such as sheet size, fill direction, distance, tolerance, and
|
||||
rotation. The function utilizes geometric operations to ensure that the
|
||||
curves do not overlap and fit within the defined boundaries. The packed
|
||||
curves are then transformed and their properties are updated
|
||||
accordingly. The function performs the following steps: 1. Activates
|
||||
speedup features if available. 2. Retrieves packing settings from the
|
||||
current scene. 3. Processes each selected object to create polygons from
|
||||
curves. 4. Attempts to place each polygon within the defined area while
|
||||
avoiding overlaps and respecting the specified fill direction. 5.
|
||||
Outputs the final arrangement of polygons.
|
||||
"""
|
||||
|
||||
if speedups.available:
|
||||
speedups.enable()
|
||||
t = time.time()
|
||||
|
|
|
@ -30,6 +30,26 @@ from .simple import progress
|
|||
|
||||
|
||||
def getPathPatternParallel(o, angle):
|
||||
"""Generate path chunks for parallel movement based on object dimensions
|
||||
and angle.
|
||||
|
||||
This function calculates a series of path chunks for a given object,
|
||||
taking into account its dimensions and the specified angle. It utilizes
|
||||
both a traditional method and an alternative algorithm (currently
|
||||
disabled) to generate these paths. The paths are constructed by
|
||||
iterating over calculated vectors and applying transformations based on
|
||||
the object's properties. The resulting path chunks can be used for
|
||||
various movement types, including conventional and climb movements.
|
||||
|
||||
Args:
|
||||
o (object): An object containing properties such as dimensions and movement type.
|
||||
angle (float): The angle to rotate the path generation.
|
||||
|
||||
Returns:
|
||||
list: A list of path chunks generated based on the object's dimensions and
|
||||
angle.
|
||||
"""
|
||||
|
||||
zlevel = 1
|
||||
pathd = o.dist_between_paths
|
||||
pathstep = o.dist_along_paths
|
||||
|
@ -133,6 +153,25 @@ def getPathPatternParallel(o, angle):
|
|||
|
||||
|
||||
def getPathPattern(operation):
|
||||
"""Generate a path pattern based on the specified operation strategy.
|
||||
|
||||
This function constructs a path pattern for a given operation by
|
||||
analyzing its parameters and applying different strategies such as
|
||||
'PARALLEL', 'CROSS', 'BLOCK', 'SPIRAL', 'CIRCLES', and 'OUTLINEFILL'.
|
||||
Each strategy dictates how the path is built, utilizing various
|
||||
geometric calculations and conditions to ensure the path adheres to the
|
||||
specified operational constraints. The function also handles the
|
||||
orientation and direction of the path based on the movement settings
|
||||
provided in the operation.
|
||||
|
||||
Args:
|
||||
operation (object): An object containing parameters for path generation,
|
||||
including strategy, movement type, and geometric bounds.
|
||||
|
||||
Returns:
|
||||
list: A list of path chunks representing the generated path pattern.
|
||||
"""
|
||||
|
||||
o = operation
|
||||
t = time.time()
|
||||
progress('Building Path Pattern')
|
||||
|
@ -391,6 +430,23 @@ def getPathPattern(operation):
|
|||
|
||||
|
||||
def getPathPattern4axis(operation):
|
||||
"""Generate path patterns for a specified operation along a rotary axis.
|
||||
|
||||
This function constructs a series of path chunks based on the provided
|
||||
operation's parameters, including the rotary axis, strategy, and
|
||||
dimensions. It calculates the necessary angles and positions for the
|
||||
cutter based on the specified strategy (PARALLELR, PARALLEL, or HELIX)
|
||||
and generates the corresponding path chunks for machining operations.
|
||||
|
||||
Args:
|
||||
operation (object): An object containing parameters for the machining operation,
|
||||
including min and max coordinates, rotary axis configuration,
|
||||
distance settings, and movement strategy.
|
||||
|
||||
Returns:
|
||||
list: A list of path chunks generated for the specified operation.
|
||||
"""
|
||||
|
||||
o = operation
|
||||
t = time.time()
|
||||
progress('Building Path Pattern')
|
||||
|
|
|
@ -20,6 +20,22 @@ SHAPELY = True
|
|||
|
||||
|
||||
def Circle(r, np):
|
||||
"""Generate a circle defined by a given radius and number of points.
|
||||
|
||||
This function creates a polygon representing a circle by generating a
|
||||
list of points based on the specified radius and the number of points
|
||||
(np). It uses vector rotation to calculate the coordinates of each point
|
||||
around the circle. The resulting points are then used to create a
|
||||
polygon object.
|
||||
|
||||
Args:
|
||||
r (float): The radius of the circle.
|
||||
np (int): The number of points to generate around the circle.
|
||||
|
||||
Returns:
|
||||
spolygon.Polygon: A polygon object representing the circle.
|
||||
"""
|
||||
|
||||
c = []
|
||||
v = Vector((r, 0, 0))
|
||||
e = Euler((0, 0, 2.0 * pi / np))
|
||||
|
@ -32,6 +48,23 @@ def Circle(r, np):
|
|||
|
||||
|
||||
def shapelyRemoveDoubles(p, optimize_threshold):
|
||||
"""Remove duplicate points from the boundary of a shape.
|
||||
|
||||
This function simplifies the boundary of a given shape by removing
|
||||
duplicate points using the Ramer-Douglas-Peucker algorithm. It iterates
|
||||
through each contour of the shape, applies the simplification, and adds
|
||||
the resulting contours to a new shape. The optimization threshold can be
|
||||
adjusted to control the level of simplification.
|
||||
|
||||
Args:
|
||||
p (Shape): The shape object containing boundaries to be simplified.
|
||||
optimize_threshold (float): A threshold value that influences the
|
||||
simplification process.
|
||||
|
||||
Returns:
|
||||
Shape: A new shape object with simplified boundaries.
|
||||
"""
|
||||
|
||||
optimize_threshold *= 0.000001
|
||||
|
||||
soptions = ['distance', 'distance', 0.0, 5, optimize_threshold, 5, optimize_threshold]
|
||||
|
@ -53,6 +86,24 @@ def shapelyRemoveDoubles(p, optimize_threshold):
|
|||
|
||||
|
||||
def shapelyToMultipolygon(anydata):
|
||||
"""Convert a Shapely geometry to a MultiPolygon.
|
||||
|
||||
This function takes a Shapely geometry object and converts it to a
|
||||
MultiPolygon. If the input geometry is already a MultiPolygon, it
|
||||
returns it as is. If the input is a Polygon and not empty, it wraps the
|
||||
Polygon in a MultiPolygon. If the input is an empty Polygon, it returns
|
||||
an empty MultiPolygon. For any other geometry type, it prints a message
|
||||
indicating that the conversion was aborted and returns an empty
|
||||
MultiPolygon.
|
||||
|
||||
Args:
|
||||
anydata (shapely.geometry.base.BaseGeometry): A Shapely geometry object
|
||||
|
||||
Returns:
|
||||
shapely.geometry.MultiPolygon: A MultiPolygon representation of the input
|
||||
geometry.
|
||||
"""
|
||||
|
||||
if anydata.geom_type == 'MultiPolygon':
|
||||
return anydata
|
||||
elif anydata.geom_type == 'Polygon':
|
||||
|
@ -66,6 +117,24 @@ def shapelyToMultipolygon(anydata):
|
|||
|
||||
|
||||
def shapelyToCoords(anydata):
|
||||
"""Convert a Shapely geometry object to a list of coordinates.
|
||||
|
||||
This function takes a Shapely geometry object and extracts its
|
||||
coordinates based on the geometry type. It handles various types of
|
||||
geometries including Polygon, MultiPolygon, LineString, MultiLineString,
|
||||
and GeometryCollection. If the geometry is empty or of type MultiPoint,
|
||||
it returns an empty list. The coordinates are returned in a nested list
|
||||
format, where each sublist corresponds to the exterior or interior
|
||||
coordinates of the geometries.
|
||||
|
||||
Args:
|
||||
anydata (shapely.geometry.base.BaseGeometry): A Shapely geometry object
|
||||
|
||||
Returns:
|
||||
list: A list of coordinates extracted from the input geometry.
|
||||
The structure of the list depends on the geometry type.
|
||||
"""
|
||||
|
||||
p = anydata
|
||||
seq = []
|
||||
# print(p.type)
|
||||
|
@ -116,6 +185,24 @@ def shapelyToCoords(anydata):
|
|||
|
||||
|
||||
def shapelyToCurve(name, p, z):
|
||||
"""Create a 3D curve object in Blender from a Shapely geometry.
|
||||
|
||||
This function takes a Shapely geometry and converts it into a 3D curve
|
||||
object in Blender. It extracts the coordinates from the Shapely geometry
|
||||
and creates a new curve object with the specified name. The curve is
|
||||
created in the 3D space at the given z-coordinate, with a default weight
|
||||
for the points.
|
||||
|
||||
Args:
|
||||
name (str): The name of the curve object to be created.
|
||||
p (shapely.geometry): A Shapely geometry object from which to extract
|
||||
coordinates.
|
||||
z (float): The z-coordinate for all points of the curve.
|
||||
|
||||
Returns:
|
||||
bpy.types.Object: The newly created curve object in Blender.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
import bmesh
|
||||
from bpy_extras import object_utils
|
||||
|
|
|
@ -24,6 +24,24 @@ DT = 1.025
|
|||
|
||||
|
||||
def finger(diameter, stem=2):
|
||||
"""Create a joint shape based on the specified diameter and stem.
|
||||
|
||||
This function generates a 3D joint shape using Blender's curve
|
||||
operations. It calculates the dimensions of a rectangle and an ellipse
|
||||
based on the provided diameter and stem parameters. The function then
|
||||
creates these shapes, duplicates and mirrors them, and performs boolean
|
||||
operations to form the final joint shape. The resulting object is named
|
||||
and cleaned up to ensure no overlapping vertices remain.
|
||||
|
||||
Args:
|
||||
diameter (float): The diameter of the tool for joint creation.
|
||||
stem (float?): The amount of radius the stem or neck of the joint will have. Defaults
|
||||
to 2.
|
||||
|
||||
Returns:
|
||||
None: This function does not return any value.
|
||||
"""
|
||||
|
||||
# diameter = diameter of the tool for joint creation
|
||||
# DT = Bit diameter tolerance
|
||||
# stem = amount of radius the stem or neck of the joint will have
|
||||
|
@ -77,6 +95,22 @@ def finger(diameter, stem=2):
|
|||
|
||||
|
||||
def fingers(diameter, inside, amount=1, stem=1):
|
||||
"""Create a specified number of fingers for a joint tool.
|
||||
|
||||
This function generates a set of fingers based on the provided diameter
|
||||
and tolerance values. It calculates the necessary translations for
|
||||
positioning the fingers and duplicates them if more than one is
|
||||
required. Additionally, it creates a receptacle using a silhouette
|
||||
offset from the fingers, allowing for precise joint creation.
|
||||
|
||||
Args:
|
||||
diameter (float): The diameter of the tool used for joint creation.
|
||||
inside (float): The tolerance in the joint receptacle.
|
||||
amount (int?): The number of fingers to create. Defaults to 1.
|
||||
stem (float?): The amount of radius the stem or neck of the joint will have. Defaults
|
||||
to 1.
|
||||
"""
|
||||
|
||||
# diameter = diameter of the tool for joint creation
|
||||
# inside = Tolerance in the joint receptacle
|
||||
global DT # Bit diameter tolerance
|
||||
|
@ -107,6 +141,28 @@ def fingers(diameter, inside, amount=1, stem=1):
|
|||
|
||||
|
||||
def twistf(name, length, diameter, tolerance, twist, tneck, tthick, twist_keep=False):
|
||||
"""Add a twist lock to a receptacle.
|
||||
|
||||
This function modifies the receptacle by adding a twist lock feature if
|
||||
the `twist` parameter is set to True. It performs several operations
|
||||
including interlocking the twist, rotating the object, and moving it to
|
||||
the correct position. If `twist_keep` is True, it duplicates the twist
|
||||
lock for further modifications. The function utilizes parameters such as
|
||||
length, diameter, tolerance, and thickness to accurately create the
|
||||
twist lock.
|
||||
|
||||
Args:
|
||||
name (str): The name of the receptacle to be modified.
|
||||
length (float): The length of the receptacle.
|
||||
diameter (float): The diameter of the receptacle.
|
||||
tolerance (float): The tolerance value for the twist lock.
|
||||
twist (bool): A flag indicating whether to add a twist lock.
|
||||
tneck (float): The neck thickness for the twist lock.
|
||||
tthick (float): The thickness of the twist lock.
|
||||
twist_keep (bool?): A flag indicating whether to keep the twist
|
||||
lock after duplication. Defaults to False.
|
||||
"""
|
||||
|
||||
# add twist lock to receptacle
|
||||
if twist:
|
||||
joinery.interlock_twist(length, tthick, tolerance, cx=0, cy=0, rotation=0, percentage=tneck)
|
||||
|
@ -123,6 +179,34 @@ def twistf(name, length, diameter, tolerance, twist, tneck, tthick, twist_keep=F
|
|||
|
||||
|
||||
def twistm(name, length, diameter, tolerance, twist, tneck, tthick, angle, twist_keep=False, x=0, y=0):
|
||||
"""Add a twist lock to a male connector.
|
||||
|
||||
This function modifies the geometry of a male connector by adding a
|
||||
twist lock feature. It utilizes various parameters to determine the
|
||||
dimensions and positioning of the twist lock. If the `twist_keep`
|
||||
parameter is set to True, it duplicates the twist lock for further
|
||||
modifications. The function also allows for adjustments in position
|
||||
through the `x` and `y` parameters.
|
||||
|
||||
Args:
|
||||
name (str): The name of the connector to be modified.
|
||||
length (float): The length of the connector.
|
||||
diameter (float): The diameter of the connector.
|
||||
tolerance (float): The tolerance level for the twist lock.
|
||||
twist (bool): A flag indicating whether to add a twist lock.
|
||||
tneck (float): The neck thickness for the twist lock.
|
||||
tthick (float): The thickness of the twist lock.
|
||||
angle (float): The angle at which to rotate the twist lock.
|
||||
twist_keep (bool?): A flag indicating whether to keep the twist lock duplicate. Defaults to
|
||||
False.
|
||||
x (float?): The x-coordinate for positioning. Defaults to 0.
|
||||
y (float?): The y-coordinate for positioning. Defaults to 0.
|
||||
|
||||
Returns:
|
||||
None: This function modifies the state of the connector but does not return a
|
||||
value.
|
||||
"""
|
||||
|
||||
# add twist lock to male connector
|
||||
global DT
|
||||
if twist:
|
||||
|
@ -143,6 +227,39 @@ def twistm(name, length, diameter, tolerance, twist, tneck, tthick, angle, twist
|
|||
|
||||
def bar(width, thick, diameter, tolerance, amount=0, stem=1, twist=False, tneck=0.5, tthick=0.01, twist_keep=False,
|
||||
twist_line=False, twist_line_amount=2, which='MF'):
|
||||
"""Create a bar with specified dimensions and joint features.
|
||||
|
||||
This function generates a bar with customizable parameters such as
|
||||
width, thickness, and joint characteristics. It can automatically
|
||||
determine the number of fingers in the joint if the amount is set to
|
||||
zero. The function also supports various options for twisting and neck
|
||||
dimensions, allowing for flexible design of the bar according to the
|
||||
specified parameters. The resulting bar can be manipulated further based
|
||||
on the provided options.
|
||||
|
||||
Args:
|
||||
width (float): The length of the bar.
|
||||
thick (float): The thickness of the bar.
|
||||
diameter (float): The diameter of the tool used for joint creation.
|
||||
tolerance (float): The tolerance in the joint.
|
||||
amount (int?): The number of fingers in the joint; 0 means auto-generate. Defaults to
|
||||
0.
|
||||
stem (float?): The radius of the stem or neck of the joint. Defaults to 1.
|
||||
twist (bool?): Whether to add a twist lock. Defaults to False.
|
||||
tneck (float?): The percentage the twist neck will have compared to thickness. Defaults
|
||||
to 0.5.
|
||||
tthick (float?): The thickness of the twist material. Defaults to 0.01.
|
||||
twist_keep (bool?): Whether to keep the twist feature. Defaults to False.
|
||||
twist_line (bool?): Whether to add a twist line. Defaults to False.
|
||||
twist_line_amount (int?): The amount for the twist line. Defaults to 2.
|
||||
which (str?): Specifies the type of joint; options are 'M', 'F', 'MF', 'MM', 'FF'.
|
||||
Defaults to 'MF'.
|
||||
|
||||
Returns:
|
||||
None: This function does not return a value but modifies the state of the 3D
|
||||
model in Blender.
|
||||
"""
|
||||
|
||||
|
||||
# width = length of the bar
|
||||
# thick = thickness of the bar
|
||||
|
@ -203,6 +320,36 @@ def bar(width, thick, diameter, tolerance, amount=0, stem=1, twist=False, tneck=
|
|||
|
||||
def arc(radius, thick, angle, diameter, tolerance, amount=0, stem=1, twist=False, tneck=0.5, tthick=0.01,
|
||||
twist_keep=False, which='MF'):
|
||||
"""Generate an arc with specified parameters.
|
||||
|
||||
This function creates a 3D arc based on the provided radius, thickness,
|
||||
angle, and other parameters. It handles the generation of fingers for
|
||||
the joint and applies twisting features if specified. The function also
|
||||
manages the orientation and positioning of the generated arc in a 3D
|
||||
space.
|
||||
|
||||
Args:
|
||||
radius (float): The radius of the curve.
|
||||
thick (float): The thickness of the bar.
|
||||
angle (float): The angle of the arc (must not be zero).
|
||||
diameter (float): The diameter of the tool for joint creation.
|
||||
tolerance (float): Tolerance in the joint.
|
||||
amount (int?): The amount of fingers in the joint; 0 means auto-generate. Defaults to
|
||||
0.
|
||||
stem (float?): The amount of radius the stem or neck of the joint will have. Defaults
|
||||
to 1.
|
||||
twist (bool?): Whether to add a twist lock. Defaults to False.
|
||||
tneck (float?): Percentage the twist neck will have compared to thickness. Defaults to
|
||||
0.5.
|
||||
tthick (float?): Thickness of the twist material. Defaults to 0.01.
|
||||
twist_keep (bool?): Whether to keep the twist. Defaults to False.
|
||||
which (str?): Specifies which joint to generate ('M', 'F', 'MF'). Defaults to 'MF'.
|
||||
|
||||
Returns:
|
||||
None: This function does not return a value but modifies the 3D scene
|
||||
directly.
|
||||
"""
|
||||
|
||||
# radius = radius of the curve
|
||||
# thick = thickness of the bar
|
||||
# angle = angle of the arc
|
||||
|
@ -289,6 +436,41 @@ def arc(radius, thick, angle, diameter, tolerance, amount=0, stem=1, twist=False
|
|||
|
||||
def arcbararc(length, radius, thick, angle, angleb, diameter, tolerance, amount=0, stem=1, twist=False,
|
||||
tneck=0.5, tthick=0.01, which='MF', twist_keep=False, twist_line=False, twist_line_amount=2):
|
||||
"""Generate an arc bar joint with specified parameters.
|
||||
|
||||
This function creates a joint consisting of male and female sections
|
||||
based on the provided parameters. It adjusts the length to account for
|
||||
the radius and thickness, generates a base rectangle, and then
|
||||
constructs the male and/or female sections as specified. Additionally,
|
||||
it can create a twist lock feature if required. The function utilizes
|
||||
Blender's bpy operations to manipulate 3D objects.
|
||||
|
||||
Args:
|
||||
length (float): The total width of the segments including 2 * radius and thickness.
|
||||
radius (float): The radius of the curve.
|
||||
thick (float): The thickness of the bar.
|
||||
angle (float): The angle of the female part.
|
||||
angleb (float): The angle of the male part.
|
||||
diameter (float): The diameter of the tool for joint creation.
|
||||
tolerance (float): Tolerance in the joint.
|
||||
amount (int?): The number of fingers in the joint; 0 means auto-generate. Defaults to
|
||||
0.
|
||||
stem (float?): The amount of radius the stem or neck of the joint will have. Defaults
|
||||
to 1.
|
||||
twist (bool?): Whether to add a twist lock feature. Defaults to False.
|
||||
tneck (float?): Percentage the twist neck will have compared to thickness. Defaults to
|
||||
0.5.
|
||||
tthick (float?): Thickness of the twist material. Defaults to 0.01.
|
||||
which (str?): Specifies which joint to generate ('M', 'F', or 'MF'). Defaults to 'MF'.
|
||||
twist_keep (bool?): Whether to keep the twist after creation. Defaults to False.
|
||||
twist_line (bool?): Whether to create a twist line feature. Defaults to False.
|
||||
twist_line_amount (int?): Amount for the twist line feature. Defaults to 2.
|
||||
|
||||
Returns:
|
||||
None: This function does not return a value but modifies the Blender scene
|
||||
directly.
|
||||
"""
|
||||
|
||||
# length is the total width of the segments including 2 * radius and thick
|
||||
# radius = radius of the curve
|
||||
# thick = thickness of the bar
|
||||
|
@ -345,6 +527,36 @@ def arcbararc(length, radius, thick, angle, angleb, diameter, tolerance, amount=
|
|||
|
||||
def arcbar(length, radius, thick, angle, diameter, tolerance, amount=0, stem=1, twist=False,
|
||||
tneck=0.5, tthick=0.01, twist_keep=False, which='MF', twist_line=False, twist_line_amount=2):
|
||||
"""Generate an arc bar joint based on specified parameters.
|
||||
|
||||
This function constructs an arc bar joint by generating male and female
|
||||
sections according to the specified parameters such as length, radius,
|
||||
thickness, and joint type. The function adjusts the length to account
|
||||
for the radius and thickness of the bar and creates the appropriate
|
||||
geometric shapes for the joint. It also includes options for twisting
|
||||
and adjusting the neck thickness of the joint.
|
||||
|
||||
Args:
|
||||
length (float): The total width of the segments including 2 * radius and thickness.
|
||||
radius (float): The radius of the curve.
|
||||
thick (float): The thickness of the bar.
|
||||
angle (float): The angle of the female part.
|
||||
diameter (float): The diameter of the tool for joint creation.
|
||||
tolerance (float): Tolerance in the joint.
|
||||
amount (int?): The number of fingers in the joint; 0 means auto-generate. Defaults to
|
||||
0.
|
||||
stem (float?): The amount of radius the stem or neck of the joint will have. Defaults
|
||||
to 1.
|
||||
twist (bool?): Whether to add a twist lock. Defaults to False.
|
||||
tneck (float?): Percentage the twist neck will have compared to thickness. Defaults to
|
||||
0.5.
|
||||
tthick (float?): Thickness of the twist material. Defaults to 0.01.
|
||||
twist_keep (bool?): Whether to keep the twist. Defaults to False.
|
||||
which (str?): Specifies which joint to generate ('M', 'F', 'MF'). Defaults to 'MF'.
|
||||
twist_line (bool?): Whether to include a twist line. Defaults to False.
|
||||
twist_line_amount (int?): Amount of twist line. Defaults to 2.
|
||||
"""
|
||||
|
||||
# length is the total width of the segments including 2 * radius and thick
|
||||
# radius = radius of the curve
|
||||
# thick = thickness of the bar
|
||||
|
@ -403,6 +615,39 @@ def arcbar(length, radius, thick, angle, diameter, tolerance, amount=0, stem=1,
|
|||
|
||||
def multiangle(radius, thick, angle, diameter, tolerance, amount=0, stem=1, twist=False,
|
||||
tneck=0.5, tthick=0.01, combination='MFF'):
|
||||
"""Generate a multi-angle joint based on specified parameters.
|
||||
|
||||
This function creates a multi-angle joint by generating various
|
||||
geometric shapes using the provided parameters such as radius,
|
||||
thickness, angle, diameter, and tolerance. It utilizes Blender's
|
||||
operations to create and manipulate curves, resulting in a joint that
|
||||
can be customized with different combinations of male and female parts.
|
||||
The function also allows for automatic generation of the number of
|
||||
fingers in the joint and includes options for twisting and neck
|
||||
dimensions.
|
||||
|
||||
Args:
|
||||
radius (float): The radius of the curve.
|
||||
thick (float): The thickness of the bar.
|
||||
angle (float): The angle of the female part.
|
||||
diameter (float): The diameter of the tool for joint creation.
|
||||
tolerance (float): Tolerance in the joint.
|
||||
amount (int?): The amount of fingers in the joint; 0 means auto-generate. Defaults to
|
||||
0.
|
||||
stem (float?): The amount of radius the stem or neck of the joint will have. Defaults
|
||||
to 1.
|
||||
twist (bool?): Indicates if a twist lock addition is required. Defaults to False.
|
||||
tneck (float?): Percentage the twist neck will have compared to thickness. Defaults to
|
||||
0.5.
|
||||
tthick (float?): Thickness of the twist material. Defaults to 0.01.
|
||||
combination (str?): Specifies which joint to generate ('M', 'F', 'MF', 'MFF', 'MMF').
|
||||
Defaults to 'MFF'.
|
||||
|
||||
Returns:
|
||||
None: This function does not return a value but performs operations in
|
||||
Blender.
|
||||
"""
|
||||
|
||||
# length is the total width of the segments including 2 * radius and thick
|
||||
# radius = radius of the curve
|
||||
# thick = thickness of the bar
|
||||
|
@ -452,6 +697,33 @@ def multiangle(radius, thick, angle, diameter, tolerance, amount=0, stem=1, twis
|
|||
|
||||
def t(length, thick, diameter, tolerance, amount=0, stem=1, twist=False, tneck=0.5, tthick=0.01, combination='MF',
|
||||
base_gender='M', corner=False):
|
||||
"""Generate a 3D model based on specified parameters.
|
||||
|
||||
This function creates a 3D model by manipulating geometric shapes based
|
||||
on the provided parameters. It handles different combinations of shapes
|
||||
and orientations based on the specified gender and corner options. The
|
||||
function utilizes several helper functions to perform operations such as
|
||||
moving, duplicating, and uniting shapes to form the final model.
|
||||
|
||||
Args:
|
||||
length (float): The length of the model.
|
||||
thick (float): The thickness of the model.
|
||||
diameter (float): The diameter of the model.
|
||||
tolerance (float): The tolerance level for the model dimensions.
|
||||
amount (int?): The amount of material to use. Defaults to 0.
|
||||
stem (int?): The stem value for the model. Defaults to 1.
|
||||
twist (bool?): Whether to apply a twist to the model. Defaults to False.
|
||||
tneck (float?): The neck thickness. Defaults to 0.5.
|
||||
tthick (float?): The thickness for the neck. Defaults to 0.01.
|
||||
combination (str?): The combination type ('MF', 'F', 'M'). Defaults to 'MF'.
|
||||
base_gender (str?): The base gender for the model ('M' or 'F'). Defaults to 'M'.
|
||||
corner (bool?): Whether to apply corner adjustments. Defaults to False.
|
||||
|
||||
Returns:
|
||||
None: This function does not return a value but modifies the 3D model
|
||||
directly.
|
||||
"""
|
||||
|
||||
if corner:
|
||||
if combination == 'MF':
|
||||
base_gender = 'M'
|
||||
|
@ -502,6 +774,35 @@ def t(length, thick, diameter, tolerance, amount=0, stem=1, twist=False, tneck=0
|
|||
|
||||
def curved_t(length, thick, radius, diameter, tolerance, amount=0, stem=1, twist=False, tneck=0.5, tthick=0.01,
|
||||
combination='MF', base_gender='M'):
|
||||
"""Create a curved shape based on specified parameters.
|
||||
|
||||
This function generates a 3D curved shape using the provided dimensions
|
||||
and characteristics. It utilizes the `bar` and `arc` functions to create
|
||||
the desired geometry and applies transformations such as mirroring and
|
||||
union operations to achieve the final shape. The function also allows
|
||||
for customization based on the gender specification, which influences
|
||||
the shape's design.
|
||||
|
||||
Args:
|
||||
length (float): The length of the bar.
|
||||
thick (float): The thickness of the bar.
|
||||
radius (float): The radius of the arc.
|
||||
diameter (float): The diameter used in arc creation.
|
||||
tolerance (float): The tolerance level for the shape.
|
||||
amount (int?): The amount parameter for the shape generation. Defaults to 0.
|
||||
stem (int?): The stem parameter for the shape generation. Defaults to 1.
|
||||
twist (bool?): A flag indicating whether to apply a twist to the shape. Defaults to
|
||||
False.
|
||||
tneck (float?): The neck thickness parameter. Defaults to 0.5.
|
||||
tthick (float?): The thickness parameter for the neck. Defaults to 0.01.
|
||||
combination (str?): The combination type for the shape. Defaults to 'MF'.
|
||||
base_gender (str?): The base gender for the shape design. Defaults to 'M'.
|
||||
|
||||
Returns:
|
||||
None: This function does not return a value but modifies the 3D model in the
|
||||
environment.
|
||||
"""
|
||||
|
||||
bar(length, thick, diameter, tolerance, amount=amount, stem=stem, twist=twist, tneck=tneck,
|
||||
tthick=tthick, which=combination)
|
||||
simple.active_name('tmpbar')
|
||||
|
@ -543,6 +844,32 @@ def curved_t(length, thick, radius, diameter, tolerance, amount=0, stem=1, twist
|
|||
|
||||
def mitre(length, thick, angle, angleb, diameter, tolerance, amount=0, stem=1, twist=False,
|
||||
tneck=0.5, tthick=0.01, which='MF'):
|
||||
"""Generate a mitre joint based on specified parameters.
|
||||
|
||||
This function creates a 3D representation of a mitre joint using
|
||||
Blender's bpy.ops.curve.simple operations. It generates a base rectangle
|
||||
and cutout shapes, then constructs male and female sections of the joint
|
||||
based on the provided angles and dimensions. The function allows for
|
||||
customization of various parameters such as thickness, diameter,
|
||||
tolerance, and the number of fingers in the joint. The resulting joint
|
||||
can be either male, female, or a combination of both.
|
||||
|
||||
Args:
|
||||
length (float): The total width of the segments including 2 * radius and thickness.
|
||||
thick (float): The thickness of the bar.
|
||||
angle (float): The angle of the female part.
|
||||
angleb (float): The angle of the male part.
|
||||
diameter (float): The diameter of the tool for joint creation.
|
||||
tolerance (float): Tolerance in the joint.
|
||||
amount (int?): Amount of fingers in the joint; 0 means auto-generate. Defaults to 0.
|
||||
stem (float?): Amount of radius the stem or neck of the joint will have. Defaults to 1.
|
||||
twist (bool?): Indicates if a twist lock addition is required. Defaults to False.
|
||||
tneck (float?): Percentage the twist neck will have compared to thickness. Defaults to
|
||||
0.5.
|
||||
tthick (float?): Thickness of the twist material. Defaults to 0.01.
|
||||
which (str?): Specifies which joint to generate ('M', 'F', 'MF'). Defaults to 'MF'.
|
||||
"""
|
||||
|
||||
# length is the total width of the segments including 2 * radius and thick
|
||||
# radius = radius of the curve
|
||||
# thick = thickness of the bar
|
||||
|
@ -632,6 +959,41 @@ def mitre(length, thick, angle, angleb, diameter, tolerance, amount=0, stem=1, t
|
|||
|
||||
def open_curve(line, thick, diameter, tolerance, amount=0, stem=1, twist=False, t_neck=0.5, t_thick=0.01,
|
||||
twist_amount=1, which='MF', twist_keep=False):
|
||||
"""Open a curve and add puzzle connectors with optional twist lock
|
||||
connectors.
|
||||
|
||||
This function takes a shapely LineString and creates an open curve with
|
||||
specified parameters such as thickness, diameter, tolerance, and twist
|
||||
options. It generates puzzle connectors at the ends of the curve and can
|
||||
optionally add twist lock connectors along the curve. The function also
|
||||
handles the creation of the joint based on the provided parameters,
|
||||
ensuring that the resulting geometry meets the specified design
|
||||
requirements.
|
||||
|
||||
Args:
|
||||
line (LineString): A shapely LineString representing the path of the curve.
|
||||
thick (float): The thickness of the bar used in the joint.
|
||||
diameter (float): The diameter of the tool for joint creation.
|
||||
tolerance (float): The tolerance in the joint.
|
||||
amount (int?): The number of fingers in the joint; 0 means auto-generate. Defaults to
|
||||
0.
|
||||
stem (float?): The amount of radius the stem or neck of the joint will have. Defaults
|
||||
to 1.
|
||||
twist (bool?): Whether to add twist lock connectors. Defaults to False.
|
||||
t_neck (float?): The percentage the twist neck will have compared to thickness. Defaults
|
||||
to 0.5.
|
||||
t_thick (float?): The thickness of the twist material. Defaults to 0.01.
|
||||
twist_amount (int?): The amount of twist distributed on the curve, not counting joint twists.
|
||||
Defaults to 1.
|
||||
which (str?): Specifies the type of joint; options include 'M', 'F', 'MF', 'MM', 'FF'.
|
||||
Defaults to 'MF'.
|
||||
twist_keep (bool?): Whether to keep the twist lock connectors. Defaults to False.
|
||||
|
||||
Returns:
|
||||
None: This function does not return a value but modifies the geometry in the
|
||||
Blender context.
|
||||
"""
|
||||
|
||||
# puts puzzle connectors at the end of an open curve
|
||||
# optionally puts twist lock connectors at the puzzle connection
|
||||
# optionally puts twist lock connectors along the open curve
|
||||
|
@ -710,6 +1072,26 @@ def open_curve(line, thick, diameter, tolerance, amount=0, stem=1, twist=False,
|
|||
|
||||
|
||||
def tile(diameter, tolerance, tile_x_amount, tile_y_amount, stem=1):
|
||||
"""Create a tile shape based on specified dimensions and parameters.
|
||||
|
||||
This function calculates the dimensions of a tile based on the provided
|
||||
diameter and tolerance, as well as the number of tiles in the x and y
|
||||
directions. It constructs the tile shape by creating a base and adding
|
||||
features such as fingers for interlocking. The function also handles
|
||||
transformations such as moving, rotating, and performing boolean
|
||||
operations to achieve the desired tile geometry.
|
||||
|
||||
Args:
|
||||
diameter (float): The diameter of the tile.
|
||||
tolerance (float): The tolerance to be applied to the tile dimensions.
|
||||
tile_x_amount (int): The number of tiles along the x-axis.
|
||||
tile_y_amount (int): The number of tiles along the y-axis.
|
||||
stem (int?): A parameter affecting the tile's features. Defaults to 1.
|
||||
|
||||
Returns:
|
||||
None: This function does not return a value but modifies global state.
|
||||
"""
|
||||
|
||||
global DT
|
||||
diameter = diameter * DT
|
||||
width = ((tile_x_amount) * (4 + 2 * (stem-1)) + 1) * diameter
|
||||
|
|
Plik diff jest za duży
Load Diff
|
@ -1,319 +1,387 @@
|
|||
"""BlenderCAM 'simulation.py' © 2012 Vilem Novak
|
||||
|
||||
Functions to generate a mesh simulation from CAM Chain / Operation data.
|
||||
"""
|
||||
|
||||
import math
|
||||
import time
|
||||
|
||||
import numpy as np
|
||||
|
||||
import bpy
|
||||
from mathutils import Vector
|
||||
|
||||
from .async_op import progress_async
|
||||
from .image_utils import (
|
||||
getCutterArray,
|
||||
numpysave,
|
||||
)
|
||||
from .simple import getSimulationPath
|
||||
from .utils import (
|
||||
getBoundsMultiple,
|
||||
getOperationSources,
|
||||
)
|
||||
|
||||
|
||||
def createSimulationObject(name, operations, i):
|
||||
oname = 'csim_' + name
|
||||
|
||||
o = operations[0]
|
||||
|
||||
if oname in bpy.data.objects:
|
||||
ob = bpy.data.objects[oname]
|
||||
else:
|
||||
bpy.ops.mesh.primitive_plane_add(
|
||||
align='WORLD', enter_editmode=False, location=(0, 0, 0), rotation=(0, 0, 0))
|
||||
ob = bpy.context.active_object
|
||||
ob.name = oname
|
||||
|
||||
bpy.ops.object.modifier_add(type='SUBSURF')
|
||||
ss = ob.modifiers[-1]
|
||||
ss.subdivision_type = 'SIMPLE'
|
||||
ss.levels = 6
|
||||
ss.render_levels = 6
|
||||
bpy.ops.object.modifier_add(type='SUBSURF')
|
||||
ss = ob.modifiers[-1]
|
||||
ss.subdivision_type = 'SIMPLE'
|
||||
ss.levels = 4
|
||||
ss.render_levels = 3
|
||||
bpy.ops.object.modifier_add(type='DISPLACE')
|
||||
|
||||
ob.location = ((o.max.x + o.min.x) / 2, (o.max.y + o.min.y) / 2, o.min.z)
|
||||
ob.scale.x = (o.max.x - o.min.x) / 2
|
||||
ob.scale.y = (o.max.y - o.min.y) / 2
|
||||
print(o.max.x, o.min.x)
|
||||
print(o.max.y, o.min.y)
|
||||
print('bounds')
|
||||
disp = ob.modifiers[-1]
|
||||
disp.direction = 'Z'
|
||||
disp.texture_coords = 'LOCAL'
|
||||
disp.mid_level = 0
|
||||
|
||||
if oname in bpy.data.textures:
|
||||
t = bpy.data.textures[oname]
|
||||
|
||||
t.type = 'IMAGE'
|
||||
disp.texture = t
|
||||
|
||||
t.image = i
|
||||
else:
|
||||
bpy.ops.texture.new()
|
||||
for t in bpy.data.textures:
|
||||
if t.name == 'Texture':
|
||||
t.type = 'IMAGE'
|
||||
t.name = oname
|
||||
t = t.type_recast()
|
||||
t.type = 'IMAGE'
|
||||
t.image = i
|
||||
disp.texture = t
|
||||
ob.hide_render = True
|
||||
bpy.ops.object.shade_smooth()
|
||||
|
||||
|
||||
async def doSimulation(name, operations):
|
||||
"""Perform Simulation of Operations. Currently only for 3 Axis"""
|
||||
for o in operations:
|
||||
getOperationSources(o)
|
||||
limits = getBoundsMultiple(
|
||||
operations) # this is here because some background computed operations still didn't have bounds data
|
||||
i = await generateSimulationImage(operations, limits)
|
||||
# cp = getCachePath(operations[0])[:-len(operations[0].name)] + name
|
||||
cp = getSimulationPath()+name
|
||||
print('cp=', cp)
|
||||
iname = cp + '_sim.exr'
|
||||
|
||||
numpysave(i, iname)
|
||||
i = bpy.data.images.load(iname)
|
||||
createSimulationObject(name, operations, i)
|
||||
|
||||
|
||||
async def generateSimulationImage(operations, limits):
|
||||
minx, miny, minz, maxx, maxy, maxz = limits
|
||||
# print(minx,miny,minz,maxx,maxy,maxz)
|
||||
sx = maxx - minx
|
||||
sy = maxy - miny
|
||||
|
||||
o = operations[0] # getting sim detail and others from first op.
|
||||
simulation_detail = o.optimisation.simulation_detail
|
||||
borderwidth = o.borderwidth
|
||||
resx = math.ceil(sx / simulation_detail) + 2 * borderwidth
|
||||
resy = math.ceil(sy / simulation_detail) + 2 * borderwidth
|
||||
|
||||
# create array in which simulation happens, similar to an image to be painted in.
|
||||
si = np.full(shape=(resx, resy), fill_value=maxz, dtype=float)
|
||||
|
||||
num_operations = len(operations)
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
for op_count, o in enumerate(operations):
|
||||
ob = bpy.data.objects["cam_path_{}".format(o.name)]
|
||||
m = ob.data
|
||||
verts = m.vertices
|
||||
|
||||
if o.do_simulation_feedrate:
|
||||
kname = 'feedrates'
|
||||
m.use_customdata_edge_crease = True
|
||||
|
||||
if m.shape_keys is None or m.shape_keys.key_blocks.find(kname) == -1:
|
||||
ob.shape_key_add()
|
||||
if len(m.shape_keys.key_blocks) == 1:
|
||||
ob.shape_key_add()
|
||||
shapek = m.shape_keys.key_blocks[-1]
|
||||
shapek.name = kname
|
||||
else:
|
||||
shapek = m.shape_keys.key_blocks[kname]
|
||||
shapek.data[0].co = (0.0, 0, 0)
|
||||
|
||||
totalvolume = 0.0
|
||||
|
||||
cutterArray = getCutterArray(o, simulation_detail)
|
||||
cutterArray = -cutterArray
|
||||
lasts = verts[1].co
|
||||
perc = -1
|
||||
vtotal = len(verts)
|
||||
dropped = 0
|
||||
|
||||
xs = 0
|
||||
ys = 0
|
||||
|
||||
for i, vert in enumerate(verts):
|
||||
if perc != int(100 * i / vtotal):
|
||||
perc = int(100 * i / vtotal)
|
||||
total_perc = (perc + op_count*100) / num_operations
|
||||
await progress_async(f'Simulation', int(total_perc))
|
||||
|
||||
if i > 0:
|
||||
volume = 0
|
||||
volume_partial = 0
|
||||
s = vert.co
|
||||
v = s - lasts
|
||||
|
||||
l = v.length
|
||||
if (lasts.z < maxz or s.z < maxz) and not (
|
||||
v.x == 0 and v.y == 0 and v.z > 0): # only simulate inside material, and exclude lift-ups
|
||||
if (
|
||||
v.x == 0 and v.y == 0 and v.z < 0):
|
||||
# if the cutter goes straight down, we don't have to interpolate.
|
||||
pass
|
||||
|
||||
elif v.length > simulation_detail: # and not :
|
||||
|
||||
v.length = simulation_detail
|
||||
lastxs = xs
|
||||
lastys = ys
|
||||
while v.length < l:
|
||||
xs = int((lasts.x + v.x - minx) / simulation_detail +
|
||||
borderwidth + simulation_detail / 2)
|
||||
# -middle
|
||||
ys = int((lasts.y + v.y - miny) / simulation_detail +
|
||||
borderwidth + simulation_detail / 2)
|
||||
# -middle
|
||||
z = lasts.z + v.z
|
||||
# print(z)
|
||||
if lastxs != xs or lastys != ys:
|
||||
volume_partial = simCutterSpot(
|
||||
xs, ys, z, cutterArray, si, o.do_simulation_feedrate)
|
||||
if o.do_simulation_feedrate:
|
||||
totalvolume += volume
|
||||
volume += volume_partial
|
||||
lastxs = xs
|
||||
lastys = ys
|
||||
else:
|
||||
dropped += 1
|
||||
v.length += simulation_detail
|
||||
|
||||
xs = int((s.x - minx) / simulation_detail +
|
||||
borderwidth + simulation_detail / 2) # -middle
|
||||
ys = int((s.y - miny) / simulation_detail +
|
||||
borderwidth + simulation_detail / 2) # -middle
|
||||
volume_partial = simCutterSpot(
|
||||
xs, ys, s.z, cutterArray, si, o.do_simulation_feedrate)
|
||||
if o.do_simulation_feedrate: # compute volumes and write data into shapekey.
|
||||
volume += volume_partial
|
||||
totalvolume += volume
|
||||
if l > 0:
|
||||
load = volume / l
|
||||
else:
|
||||
load = 0
|
||||
|
||||
# this will show the shapekey as debugging graph and will use same data to estimate parts
|
||||
# with heavy load
|
||||
if l != 0:
|
||||
shapek.data[i].co.y = (load) * 0.000002
|
||||
else:
|
||||
shapek.data[i].co.y = shapek.data[i - 1].co.y
|
||||
shapek.data[i].co.x = shapek.data[i - 1].co.x + l * 0.04
|
||||
shapek.data[i].co.z = 0
|
||||
lasts = s
|
||||
|
||||
# print('dropped '+str(dropped))
|
||||
if o.do_simulation_feedrate: # smoothing ,but only backward!
|
||||
xcoef = shapek.data[len(shapek.data) - 1].co.x / len(shapek.data)
|
||||
for a in range(0, 10):
|
||||
# print(shapek.data[-1].co)
|
||||
nvals = []
|
||||
val1 = 0 #
|
||||
val2 = 0
|
||||
w1 = 0 #
|
||||
w2 = 0
|
||||
|
||||
for i, d in enumerate(shapek.data):
|
||||
val = d.co.y
|
||||
|
||||
if i > 1:
|
||||
d1 = shapek.data[i - 1].co
|
||||
val1 = d1.y
|
||||
if d1.x - d.co.x != 0:
|
||||
w1 = 1 / (abs(d1.x - d.co.x) / xcoef)
|
||||
|
||||
if i < len(shapek.data) - 1:
|
||||
d2 = shapek.data[i + 1].co
|
||||
val2 = d2.y
|
||||
if d2.x - d.co.x != 0:
|
||||
w2 = 1 / (abs(d2.x - d.co.x) / xcoef)
|
||||
|
||||
# print(val,val1,val2,w1,w2)
|
||||
|
||||
val = (val + val1 * w1 + val2 * w2) / (1.0 + w1 + w2)
|
||||
nvals.append(val)
|
||||
for i, d in enumerate(shapek.data):
|
||||
d.co.y = nvals[i]
|
||||
|
||||
# apply mapping - convert the values to actual feedrates.
|
||||
total_load = 0
|
||||
max_load = 0
|
||||
for i, d in enumerate(shapek.data):
|
||||
total_load += d.co.y
|
||||
max_load = max(max_load, d.co.y)
|
||||
normal_load = total_load / len(shapek.data)
|
||||
|
||||
thres = 0.5
|
||||
|
||||
scale_graph = 0.05 # warning this has to be same as in export in utils!!!!
|
||||
|
||||
totverts = len(shapek.data)
|
||||
for i, d in enumerate(shapek.data):
|
||||
if d.co.y > normal_load:
|
||||
d.co.z = scale_graph * max(0.3, normal_load / d.co.y)
|
||||
else:
|
||||
d.co.z = scale_graph * 1
|
||||
if i < totverts - 1:
|
||||
m.edges[i].crease = d.co.y / (normal_load * 4)
|
||||
|
||||
si = si[borderwidth:-borderwidth, borderwidth:-borderwidth]
|
||||
si += -minz
|
||||
|
||||
await progress_async("Simulated:", time.time()-start_time, 's')
|
||||
return si
|
||||
|
||||
|
||||
def simCutterSpot(xs, ys, z, cutterArray, si, getvolume=False):
|
||||
"""Simulates a Cutter Cutting Into Stock, Taking Away the Volume,
|
||||
and Optionally Returning the Volume that Has Been Milled. This Is Now Used for Feedrate Tweaking."""
|
||||
m = int(cutterArray.shape[0] / 2)
|
||||
size = cutterArray.shape[0]
|
||||
# whole cutter in image there
|
||||
if xs > m and xs < si.shape[0] - m and ys > m and ys < si.shape[1] - m:
|
||||
if getvolume:
|
||||
volarray = si[xs - m:xs - m + size, ys - m:ys - m + size].copy()
|
||||
si[xs - m:xs - m + size, ys - m:ys - m + size] = np.minimum(si[xs - m:xs - m + size, ys - m:ys - m + size],
|
||||
cutterArray + z)
|
||||
if getvolume:
|
||||
volarray = si[xs - m:xs - m + size, ys - m:ys - m + size] - volarray
|
||||
vsum = abs(volarray.sum())
|
||||
# print(vsum)
|
||||
return vsum
|
||||
|
||||
elif xs > -m and xs < si.shape[0] + m and ys > -m and ys < si.shape[1] + m:
|
||||
# part of cutter in image, for extra large cutters
|
||||
|
||||
startx = max(0, xs - m)
|
||||
starty = max(0, ys - m)
|
||||
endx = min(si.shape[0], xs - m + size)
|
||||
endy = min(si.shape[0], ys - m + size)
|
||||
castartx = max(0, m - xs)
|
||||
castarty = max(0, m - ys)
|
||||
caendx = min(size, si.shape[0] - xs + m)
|
||||
caendy = min(size, si.shape[1] - ys + m)
|
||||
|
||||
if getvolume:
|
||||
volarray = si[startx:endx, starty:endy].copy()
|
||||
si[startx:endx, starty:endy] = np.minimum(si[startx:endx, starty:endy],
|
||||
cutterArray[castartx:caendx, castarty:caendy] + z)
|
||||
if getvolume:
|
||||
volarray = si[startx:endx, starty:endy] - volarray
|
||||
vsum = abs(volarray.sum())
|
||||
# print(vsum)
|
||||
return vsum
|
||||
return 0
|
||||
"""BlenderCAM 'simulation.py' © 2012 Vilem Novak
|
||||
|
||||
Functions to generate a mesh simulation from CAM Chain / Operation data.
|
||||
"""
|
||||
|
||||
import math
|
||||
import time
|
||||
|
||||
import numpy as np
|
||||
|
||||
import bpy
|
||||
from mathutils import Vector
|
||||
|
||||
from .async_op import progress_async
|
||||
from .image_utils import (
|
||||
getCutterArray,
|
||||
numpysave,
|
||||
)
|
||||
from .simple import getSimulationPath
|
||||
from .utils import (
|
||||
getBoundsMultiple,
|
||||
getOperationSources,
|
||||
)
|
||||
|
||||
|
||||
def createSimulationObject(name, operations, i):
|
||||
"""Create a simulation object in Blender.
|
||||
|
||||
This function creates a simulation object in Blender with the specified
|
||||
name and operations. If an object with the given name already exists, it
|
||||
retrieves that object; otherwise, it creates a new plane object and
|
||||
applies several modifiers to it. The function also sets the object's
|
||||
location and scale based on the provided operations and assigns a
|
||||
texture to the object.
|
||||
|
||||
Args:
|
||||
name (str): The name of the simulation object to be created.
|
||||
operations (list): A list of operation objects that contain bounding box information.
|
||||
i: The image to be used as a texture for the simulation object.
|
||||
"""
|
||||
oname = 'csim_' + name
|
||||
|
||||
o = operations[0]
|
||||
|
||||
if oname in bpy.data.objects:
|
||||
ob = bpy.data.objects[oname]
|
||||
else:
|
||||
bpy.ops.mesh.primitive_plane_add(
|
||||
align='WORLD', enter_editmode=False, location=(0, 0, 0), rotation=(0, 0, 0))
|
||||
ob = bpy.context.active_object
|
||||
ob.name = oname
|
||||
|
||||
bpy.ops.object.modifier_add(type='SUBSURF')
|
||||
ss = ob.modifiers[-1]
|
||||
ss.subdivision_type = 'SIMPLE'
|
||||
ss.levels = 6
|
||||
ss.render_levels = 6
|
||||
bpy.ops.object.modifier_add(type='SUBSURF')
|
||||
ss = ob.modifiers[-1]
|
||||
ss.subdivision_type = 'SIMPLE'
|
||||
ss.levels = 4
|
||||
ss.render_levels = 3
|
||||
bpy.ops.object.modifier_add(type='DISPLACE')
|
||||
|
||||
ob.location = ((o.max.x + o.min.x) / 2, (o.max.y + o.min.y) / 2, o.min.z)
|
||||
ob.scale.x = (o.max.x - o.min.x) / 2
|
||||
ob.scale.y = (o.max.y - o.min.y) / 2
|
||||
print(o.max.x, o.min.x)
|
||||
print(o.max.y, o.min.y)
|
||||
print('bounds')
|
||||
disp = ob.modifiers[-1]
|
||||
disp.direction = 'Z'
|
||||
disp.texture_coords = 'LOCAL'
|
||||
disp.mid_level = 0
|
||||
|
||||
if oname in bpy.data.textures:
|
||||
t = bpy.data.textures[oname]
|
||||
|
||||
t.type = 'IMAGE'
|
||||
disp.texture = t
|
||||
|
||||
t.image = i
|
||||
else:
|
||||
bpy.ops.texture.new()
|
||||
for t in bpy.data.textures:
|
||||
if t.name == 'Texture':
|
||||
t.type = 'IMAGE'
|
||||
t.name = oname
|
||||
t = t.type_recast()
|
||||
t.type = 'IMAGE'
|
||||
t.image = i
|
||||
disp.texture = t
|
||||
ob.hide_render = True
|
||||
bpy.ops.object.shade_smooth()
|
||||
|
||||
|
||||
async def doSimulation(name, operations):
|
||||
"""Perform simulation of operations for a 3-axis system.
|
||||
|
||||
This function iterates through a list of operations, retrieves the
|
||||
necessary sources for each operation, and computes the bounds for the
|
||||
operations. It then generates a simulation image based on the operations
|
||||
and their limits, saves the image to a specified path, and finally
|
||||
creates a simulation object in Blender using the generated image.
|
||||
|
||||
Args:
|
||||
name (str): The name to be used for the simulation object.
|
||||
operations (list): A list of operations to be simulated.
|
||||
"""
|
||||
for o in operations:
|
||||
getOperationSources(o)
|
||||
limits = getBoundsMultiple(
|
||||
operations) # this is here because some background computed operations still didn't have bounds data
|
||||
i = await generateSimulationImage(operations, limits)
|
||||
# cp = getCachePath(operations[0])[:-len(operations[0].name)] + name
|
||||
cp = getSimulationPath()+name
|
||||
print('cp=', cp)
|
||||
iname = cp + '_sim.exr'
|
||||
|
||||
numpysave(i, iname)
|
||||
i = bpy.data.images.load(iname)
|
||||
createSimulationObject(name, operations, i)
|
||||
|
||||
|
||||
async def generateSimulationImage(operations, limits):
|
||||
"""Generate a simulation image based on provided operations and limits.
|
||||
|
||||
This function creates a 2D simulation image by processing a series of
|
||||
operations that define how the simulation should be conducted. It uses
|
||||
the limits provided to determine the boundaries of the simulation area.
|
||||
The function calculates the necessary resolution for the simulation
|
||||
image based on the specified simulation detail and border width. It
|
||||
iterates through each operation, simulating the effect of each operation
|
||||
on the image, and updates the shape keys of the corresponding Blender
|
||||
object to reflect the simulation results. The final output is a 2D array
|
||||
representing the simulated image.
|
||||
|
||||
Args:
|
||||
operations (list): A list of operation objects that contain details
|
||||
about the simulation, including feed rates and other parameters.
|
||||
limits (tuple): A tuple containing the minimum and maximum coordinates
|
||||
(minx, miny, minz, maxx, maxy, maxz) that define the simulation
|
||||
boundaries.
|
||||
|
||||
Returns:
|
||||
np.ndarray: A 2D array representing the simulated image.
|
||||
"""
|
||||
|
||||
minx, miny, minz, maxx, maxy, maxz = limits
|
||||
# print(minx,miny,minz,maxx,maxy,maxz)
|
||||
sx = maxx - minx
|
||||
sy = maxy - miny
|
||||
|
||||
o = operations[0] # getting sim detail and others from first op.
|
||||
simulation_detail = o.optimisation.simulation_detail
|
||||
borderwidth = o.borderwidth
|
||||
resx = math.ceil(sx / simulation_detail) + 2 * borderwidth
|
||||
resy = math.ceil(sy / simulation_detail) + 2 * borderwidth
|
||||
|
||||
# create array in which simulation happens, similar to an image to be painted in.
|
||||
si = np.full(shape=(resx, resy), fill_value=maxz, dtype=float)
|
||||
|
||||
num_operations = len(operations)
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
for op_count, o in enumerate(operations):
|
||||
ob = bpy.data.objects["cam_path_{}".format(o.name)]
|
||||
m = ob.data
|
||||
verts = m.vertices
|
||||
|
||||
if o.do_simulation_feedrate:
|
||||
kname = 'feedrates'
|
||||
m.use_customdata_edge_crease = True
|
||||
|
||||
if m.shape_keys is None or m.shape_keys.key_blocks.find(kname) == -1:
|
||||
ob.shape_key_add()
|
||||
if len(m.shape_keys.key_blocks) == 1:
|
||||
ob.shape_key_add()
|
||||
shapek = m.shape_keys.key_blocks[-1]
|
||||
shapek.name = kname
|
||||
else:
|
||||
shapek = m.shape_keys.key_blocks[kname]
|
||||
shapek.data[0].co = (0.0, 0, 0)
|
||||
|
||||
totalvolume = 0.0
|
||||
|
||||
cutterArray = getCutterArray(o, simulation_detail)
|
||||
cutterArray = -cutterArray
|
||||
lasts = verts[1].co
|
||||
perc = -1
|
||||
vtotal = len(verts)
|
||||
dropped = 0
|
||||
|
||||
xs = 0
|
||||
ys = 0
|
||||
|
||||
for i, vert in enumerate(verts):
|
||||
if perc != int(100 * i / vtotal):
|
||||
perc = int(100 * i / vtotal)
|
||||
total_perc = (perc + op_count*100) / num_operations
|
||||
await progress_async(f'Simulation', int(total_perc))
|
||||
|
||||
if i > 0:
|
||||
volume = 0
|
||||
volume_partial = 0
|
||||
s = vert.co
|
||||
v = s - lasts
|
||||
|
||||
l = v.length
|
||||
if (lasts.z < maxz or s.z < maxz) and not (
|
||||
v.x == 0 and v.y == 0 and v.z > 0): # only simulate inside material, and exclude lift-ups
|
||||
if (
|
||||
v.x == 0 and v.y == 0 and v.z < 0):
|
||||
# if the cutter goes straight down, we don't have to interpolate.
|
||||
pass
|
||||
|
||||
elif v.length > simulation_detail: # and not :
|
||||
|
||||
v.length = simulation_detail
|
||||
lastxs = xs
|
||||
lastys = ys
|
||||
while v.length < l:
|
||||
xs = int((lasts.x + v.x - minx) / simulation_detail +
|
||||
borderwidth + simulation_detail / 2)
|
||||
# -middle
|
||||
ys = int((lasts.y + v.y - miny) / simulation_detail +
|
||||
borderwidth + simulation_detail / 2)
|
||||
# -middle
|
||||
z = lasts.z + v.z
|
||||
# print(z)
|
||||
if lastxs != xs or lastys != ys:
|
||||
volume_partial = simCutterSpot(
|
||||
xs, ys, z, cutterArray, si, o.do_simulation_feedrate)
|
||||
if o.do_simulation_feedrate:
|
||||
totalvolume += volume
|
||||
volume += volume_partial
|
||||
lastxs = xs
|
||||
lastys = ys
|
||||
else:
|
||||
dropped += 1
|
||||
v.length += simulation_detail
|
||||
|
||||
xs = int((s.x - minx) / simulation_detail +
|
||||
borderwidth + simulation_detail / 2) # -middle
|
||||
ys = int((s.y - miny) / simulation_detail +
|
||||
borderwidth + simulation_detail / 2) # -middle
|
||||
volume_partial = simCutterSpot(
|
||||
xs, ys, s.z, cutterArray, si, o.do_simulation_feedrate)
|
||||
if o.do_simulation_feedrate: # compute volumes and write data into shapekey.
|
||||
volume += volume_partial
|
||||
totalvolume += volume
|
||||
if l > 0:
|
||||
load = volume / l
|
||||
else:
|
||||
load = 0
|
||||
|
||||
# this will show the shapekey as debugging graph and will use same data to estimate parts
|
||||
# with heavy load
|
||||
if l != 0:
|
||||
shapek.data[i].co.y = (load) * 0.000002
|
||||
else:
|
||||
shapek.data[i].co.y = shapek.data[i - 1].co.y
|
||||
shapek.data[i].co.x = shapek.data[i - 1].co.x + l * 0.04
|
||||
shapek.data[i].co.z = 0
|
||||
lasts = s
|
||||
|
||||
# print('dropped '+str(dropped))
|
||||
if o.do_simulation_feedrate: # smoothing ,but only backward!
|
||||
xcoef = shapek.data[len(shapek.data) - 1].co.x / len(shapek.data)
|
||||
for a in range(0, 10):
|
||||
# print(shapek.data[-1].co)
|
||||
nvals = []
|
||||
val1 = 0 #
|
||||
val2 = 0
|
||||
w1 = 0 #
|
||||
w2 = 0
|
||||
|
||||
for i, d in enumerate(shapek.data):
|
||||
val = d.co.y
|
||||
|
||||
if i > 1:
|
||||
d1 = shapek.data[i - 1].co
|
||||
val1 = d1.y
|
||||
if d1.x - d.co.x != 0:
|
||||
w1 = 1 / (abs(d1.x - d.co.x) / xcoef)
|
||||
|
||||
if i < len(shapek.data) - 1:
|
||||
d2 = shapek.data[i + 1].co
|
||||
val2 = d2.y
|
||||
if d2.x - d.co.x != 0:
|
||||
w2 = 1 / (abs(d2.x - d.co.x) / xcoef)
|
||||
|
||||
# print(val,val1,val2,w1,w2)
|
||||
|
||||
val = (val + val1 * w1 + val2 * w2) / (1.0 + w1 + w2)
|
||||
nvals.append(val)
|
||||
for i, d in enumerate(shapek.data):
|
||||
d.co.y = nvals[i]
|
||||
|
||||
# apply mapping - convert the values to actual feedrates.
|
||||
total_load = 0
|
||||
max_load = 0
|
||||
for i, d in enumerate(shapek.data):
|
||||
total_load += d.co.y
|
||||
max_load = max(max_load, d.co.y)
|
||||
normal_load = total_load / len(shapek.data)
|
||||
|
||||
thres = 0.5
|
||||
|
||||
scale_graph = 0.05 # warning this has to be same as in export in utils!!!!
|
||||
|
||||
totverts = len(shapek.data)
|
||||
for i, d in enumerate(shapek.data):
|
||||
if d.co.y > normal_load:
|
||||
d.co.z = scale_graph * max(0.3, normal_load / d.co.y)
|
||||
else:
|
||||
d.co.z = scale_graph * 1
|
||||
if i < totverts - 1:
|
||||
m.edges[i].crease = d.co.y / (normal_load * 4)
|
||||
|
||||
si = si[borderwidth:-borderwidth, borderwidth:-borderwidth]
|
||||
si += -minz
|
||||
|
||||
await progress_async("Simulated:", time.time()-start_time, 's')
|
||||
return si
|
||||
|
||||
|
||||
def simCutterSpot(xs, ys, z, cutterArray, si, getvolume=False):
|
||||
"""Simulates a cutter cutting into stock and optionally returns the volume
|
||||
removed.
|
||||
|
||||
This function takes the position of a cutter and modifies a stock image
|
||||
by simulating the cutting process. It updates the stock image based on
|
||||
the cutter's dimensions and position, ensuring that the stock does not
|
||||
go below a certain level defined by the cutter's height. If requested,
|
||||
it also calculates and returns the volume of material that has been
|
||||
milled away.
|
||||
|
||||
Args:
|
||||
xs (int): The x-coordinate of the cutter's position.
|
||||
ys (int): The y-coordinate of the cutter's position.
|
||||
z (float): The height of the cutter.
|
||||
cutterArray (numpy.ndarray): A 2D array representing the cutter's shape.
|
||||
si (numpy.ndarray): A 2D array representing the stock image to be modified.
|
||||
getvolume (bool?): If True, the function returns the volume removed. Defaults to False.
|
||||
|
||||
Returns:
|
||||
float: The volume of material removed if `getvolume` is True; otherwise,
|
||||
returns 0.
|
||||
"""
|
||||
m = int(cutterArray.shape[0] / 2)
|
||||
size = cutterArray.shape[0]
|
||||
# whole cutter in image there
|
||||
if xs > m and xs < si.shape[0] - m and ys > m and ys < si.shape[1] - m:
|
||||
if getvolume:
|
||||
volarray = si[xs - m:xs - m + size, ys - m:ys - m + size].copy()
|
||||
si[xs - m:xs - m + size, ys - m:ys - m + size] = np.minimum(si[xs - m:xs - m + size, ys - m:ys - m + size],
|
||||
cutterArray + z)
|
||||
if getvolume:
|
||||
volarray = si[xs - m:xs - m + size, ys - m:ys - m + size] - volarray
|
||||
vsum = abs(volarray.sum())
|
||||
# print(vsum)
|
||||
return vsum
|
||||
|
||||
elif xs > -m and xs < si.shape[0] + m and ys > -m and ys < si.shape[1] + m:
|
||||
# part of cutter in image, for extra large cutters
|
||||
|
||||
startx = max(0, xs - m)
|
||||
starty = max(0, ys - m)
|
||||
endx = min(si.shape[0], xs - m + size)
|
||||
endy = min(si.shape[0], ys - m + size)
|
||||
castartx = max(0, m - xs)
|
||||
castarty = max(0, m - ys)
|
||||
caendx = min(size, si.shape[0] - xs + m)
|
||||
caendy = min(size, si.shape[1] - ys + m)
|
||||
|
||||
if getvolume:
|
||||
volarray = si[startx:endx, starty:endy].copy()
|
||||
si[startx:endx, starty:endy] = np.minimum(si[startx:endx, starty:endy],
|
||||
cutterArray[castartx:caendx, castarty:caendy] + z)
|
||||
if getvolume:
|
||||
volarray = si[startx:endx, starty:endy] - volarray
|
||||
vsum = abs(volarray.sum())
|
||||
# print(vsum)
|
||||
return vsum
|
||||
return 0
|
||||
|
|
|
@ -17,7 +17,25 @@ from . import (
|
|||
)
|
||||
|
||||
|
||||
def slicing2d(ob, height): # April 2020 Alain Pelletier
|
||||
def slicing2d(ob, height):
|
||||
"""Slice a 3D object at a specified height and convert it to a curve.
|
||||
|
||||
This function applies transformations to the given object, switches to
|
||||
edit mode, selects all vertices, and performs a bisect operation to
|
||||
slice the object at the specified height. After slicing, it resets the
|
||||
object's location and applies transformations again before converting
|
||||
the object to a curve. If the conversion fails (for instance, if the
|
||||
mesh was empty), the function deletes the mesh and returns False.
|
||||
Otherwise, it returns True.
|
||||
|
||||
Args:
|
||||
ob (bpy.types.Object): The Blender object to be sliced and converted.
|
||||
height (float): The height at which to slice the object.
|
||||
|
||||
Returns:
|
||||
bool: True if the conversion to curve was successful, False otherwise.
|
||||
"""
|
||||
# April 2020 Alain Pelletier
|
||||
# let's slice things
|
||||
bpy.ops.object.transform_apply(location=True, rotation=False, scale=False)
|
||||
bpy.ops.object.mode_set(mode='EDIT') # force edit mode
|
||||
|
@ -38,7 +56,25 @@ def slicing2d(ob, height): # April 2020 Alain Pelletier
|
|||
return True
|
||||
|
||||
|
||||
def slicing3d(ob, start, end): # April 2020 Alain Pelletier
|
||||
def slicing3d(ob, start, end):
|
||||
"""Slice a 3D object along specified planes.
|
||||
|
||||
This function applies transformations to a given object and slices it in
|
||||
the Z-axis between two specified values, `start` and `end`. It first
|
||||
ensures that the object is in edit mode and selects all vertices before
|
||||
performing the slicing operations using the `bisect` method. After
|
||||
slicing, it resets the object's location and applies the transformations
|
||||
to maintain the changes.
|
||||
|
||||
Args:
|
||||
ob (Object): The 3D object to be sliced.
|
||||
start (float): The starting Z-coordinate for the slice.
|
||||
end (float): The ending Z-coordinate for the slice.
|
||||
|
||||
Returns:
|
||||
bool: True if the slicing operation was successful.
|
||||
"""
|
||||
# April 2020 Alain Pelletier
|
||||
# let's slice things
|
||||
bpy.ops.object.transform_apply(location=True, rotation=False, scale=False)
|
||||
bpy.ops.object.mode_set(mode='EDIT') # force edit mode
|
||||
|
@ -59,7 +95,20 @@ def slicing3d(ob, start, end): # April 2020 Alain Pelletier
|
|||
return True
|
||||
|
||||
|
||||
def sliceObject(ob): # April 2020 Alain Pelletier
|
||||
def sliceObject(ob):
|
||||
"""Slice a 3D object into layers based on a specified thickness.
|
||||
|
||||
This function takes a 3D object and slices it into multiple layers
|
||||
according to the specified thickness. It creates a new collection for
|
||||
the slices and optionally creates text labels for each slice if the
|
||||
indexes parameter is set. The slicing can be done in either 2D or 3D
|
||||
based on the user's selection. The function also handles the positioning
|
||||
of the slices based on the object's bounding box.
|
||||
|
||||
Args:
|
||||
ob (bpy.types.Object): The 3D object to be sliced.
|
||||
"""
|
||||
# April 2020 Alain Pelletier
|
||||
# get variables from menu
|
||||
thickness = bpy.context.scene.cam_slice.slice_distance
|
||||
slice3d = bpy.context.scene.cam_slice.slice_3d
|
||||
|
|
Plik diff jest za duży
Load Diff
|
@ -1,182 +1,305 @@
|
|||
"""BlenderCAM 'testing.py' © 2012 Vilem Novak
|
||||
|
||||
Functions for automated testing.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
|
||||
from .gcodepath import getPath
|
||||
from .simple import activate
|
||||
|
||||
|
||||
def addTestCurve(loc):
|
||||
bpy.ops.curve.primitive_bezier_circle_add(
|
||||
radius=.05, align='WORLD', enter_editmode=False, location=loc)
|
||||
bpy.ops.object.editmode_toggle()
|
||||
bpy.ops.curve.duplicate()
|
||||
bpy.ops.transform.resize(value=(0.5, 0.5, 0.5), constraint_axis=(False, False, False),
|
||||
orient_type='GLOBAL', mirror=False, use_proportional_edit=False,
|
||||
proportional_edit_falloff='SMOOTH', proportional_size=1)
|
||||
bpy.ops.curve.duplicate()
|
||||
bpy.ops.transform.resize(value=(0.5, 0.5, 0.5), constraint_axis=(False, False, False),
|
||||
orient_type='GLOBAL', mirror=False, use_proportional_edit=False,
|
||||
proportional_edit_falloff='SMOOTH', proportional_size=1)
|
||||
bpy.ops.object.editmode_toggle()
|
||||
|
||||
|
||||
def addTestMesh(loc):
|
||||
bpy.ops.mesh.primitive_monkey_add(radius=.01, align='WORLD', enter_editmode=False, location=loc)
|
||||
bpy.ops.transform.rotate(value=-1.5708, axis=(1, 0, 0), constraint_axis=(True, False, False),
|
||||
orient_type='GLOBAL')
|
||||
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
|
||||
bpy.ops.object.editmode_toggle()
|
||||
bpy.ops.mesh.primitive_plane_add(radius=1, align='WORLD', enter_editmode=False, location=loc)
|
||||
bpy.ops.transform.resize(value=(0.01, 0.01, 0.01), constraint_axis=(False, False, False),
|
||||
orient_type='GLOBAL')
|
||||
bpy.ops.transform.translate(value=(-0.01, 0, 0), constraint_axis=(True, False, False),
|
||||
orient_type='GLOBAL')
|
||||
|
||||
bpy.ops.object.editmode_toggle()
|
||||
|
||||
|
||||
def deleteFirstVert(ob):
|
||||
activate(ob)
|
||||
bpy.ops.object.editmode_toggle()
|
||||
|
||||
bpy.ops.mesh.select_all(action='DESELECT')
|
||||
|
||||
bpy.ops.object.editmode_toggle()
|
||||
for i, v in enumerate(ob.data.vertices):
|
||||
v.select = False
|
||||
if i == 0:
|
||||
v.select = True
|
||||
ob.data.update()
|
||||
|
||||
bpy.ops.object.editmode_toggle()
|
||||
bpy.ops.mesh.delete(type='VERT')
|
||||
bpy.ops.object.editmode_toggle()
|
||||
|
||||
|
||||
def testCalc(o):
|
||||
bpy.ops.object.calculate_cam_path()
|
||||
deleteFirstVert(bpy.data.objects[o.name])
|
||||
|
||||
|
||||
def testCutout(pos):
|
||||
addTestCurve((pos[0], pos[1], -.05))
|
||||
bpy.ops.scene.cam_operation_add()
|
||||
o = bpy.context.scene.cam_operations[-1]
|
||||
o.strategy = 'CUTOUT'
|
||||
testCalc(o)
|
||||
|
||||
|
||||
def testPocket(pos):
|
||||
addTestCurve((pos[0], pos[1], -.01))
|
||||
bpy.ops.scene.cam_operation_add()
|
||||
o = bpy.context.scene.cam_operations[-1]
|
||||
o.strategy = 'POCKET'
|
||||
o.movement.helix_enter = True
|
||||
o.movement.retract_tangential = True
|
||||
testCalc(o)
|
||||
|
||||
|
||||
def testParallel(pos):
|
||||
addTestMesh((pos[0], pos[1], -.02))
|
||||
bpy.ops.scene.cam_operation_add()
|
||||
o = bpy.context.scene.cam_operations[-1]
|
||||
o.ambient_behaviour = 'AROUND'
|
||||
o.material.radius_around_model = 0.01
|
||||
bpy.ops.object.calculate_cam_path()
|
||||
|
||||
|
||||
def testWaterline(pos):
|
||||
addTestMesh((pos[0], pos[1], -.02))
|
||||
bpy.ops.scene.cam_operation_add()
|
||||
o = bpy.context.scene.cam_operations[-1]
|
||||
o.strategy = 'WATERLINE'
|
||||
o.optimisation.pixsize = .0002
|
||||
# o.ambient_behaviour='AROUND'
|
||||
# o.material_radius_around_model=0.01
|
||||
|
||||
testCalc(o)
|
||||
|
||||
|
||||
# bpy.ops.object.cam_simulate()
|
||||
|
||||
|
||||
def testSimulation():
|
||||
pass
|
||||
|
||||
|
||||
def cleanUp():
|
||||
bpy.ops.object.select_all(action='SELECT')
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
while len(bpy.context.scene.cam_operations):
|
||||
bpy.ops.scene.cam_operation_remove()
|
||||
|
||||
|
||||
def testOperation(i):
|
||||
s = bpy.context.scene
|
||||
o = s.cam_operations[i]
|
||||
report = ''
|
||||
report += 'testing operation ' + o.name + '\n'
|
||||
|
||||
getPath(bpy.context, o)
|
||||
|
||||
newresult = bpy.data.objects[o.path_object_name]
|
||||
origname = "test_cam_path_" + o.name
|
||||
if origname not in s.objects:
|
||||
report += 'Operation Test Has Nothing to Compare with, Making the New Result as Comparable Result.\n\n'
|
||||
newresult.name = origname
|
||||
else:
|
||||
testresult = bpy.data.objects[origname]
|
||||
m1 = testresult.data
|
||||
m2 = newresult.data
|
||||
test_ok = True
|
||||
if len(m1.vertices) != len(m2.vertices):
|
||||
report += "Vertex Counts Don't Match\n\n"
|
||||
test_ok = False
|
||||
else:
|
||||
different_co_count = 0
|
||||
for i in range(0, len(m1.vertices)):
|
||||
v1 = m1.vertices[i]
|
||||
v2 = m2.vertices[i]
|
||||
if v1.co != v2.co:
|
||||
different_co_count += 1
|
||||
if different_co_count > 0:
|
||||
report += 'Vertex Position Is Different on %i Vertices \n\n' % (different_co_count)
|
||||
test_ok = False
|
||||
if test_ok:
|
||||
report += 'Test Ok\n\n'
|
||||
else:
|
||||
report += 'Test Result Is Different\n \n '
|
||||
print(report)
|
||||
return report
|
||||
|
||||
|
||||
def testAll():
|
||||
s = bpy.context.scene
|
||||
report = ''
|
||||
for i in range(0, len(s.cam_operations)):
|
||||
report += testOperation(i)
|
||||
print(report)
|
||||
|
||||
|
||||
tests = [
|
||||
testCutout,
|
||||
testParallel,
|
||||
testWaterline,
|
||||
testPocket,
|
||||
|
||||
]
|
||||
|
||||
cleanUp()
|
||||
|
||||
# deleteFirstVert(bpy.context.active_object)
|
||||
for i, t in enumerate(tests):
|
||||
p = i * .2
|
||||
t((p, 0, 0))
|
||||
# cleanUp()
|
||||
|
||||
|
||||
# cleanUp()
|
||||
"""BlenderCAM 'testing.py' © 2012 Vilem Novak
|
||||
|
||||
Functions for automated testing.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
|
||||
from .gcodepath import getPath
|
||||
from .simple import activate
|
||||
|
||||
|
||||
def addTestCurve(loc):
|
||||
"""Add a test curve to the Blender scene.
|
||||
|
||||
This function creates a Bezier circle at the specified location in the
|
||||
Blender scene. It first adds a primitive Bezier circle, then enters edit
|
||||
mode to duplicate the circle twice, resizing each duplicate to half its
|
||||
original size. The function ensures that the transformations are applied
|
||||
in the global orientation and does not use proportional editing.
|
||||
|
||||
Args:
|
||||
loc (tuple): A tuple representing the (x, y, z) coordinates where
|
||||
the Bezier circle will be added in the 3D space.
|
||||
"""
|
||||
bpy.ops.curve.primitive_bezier_circle_add(
|
||||
radius=.05, align='WORLD', enter_editmode=False, location=loc)
|
||||
bpy.ops.object.editmode_toggle()
|
||||
bpy.ops.curve.duplicate()
|
||||
bpy.ops.transform.resize(value=(0.5, 0.5, 0.5), constraint_axis=(False, False, False),
|
||||
orient_type='GLOBAL', mirror=False, use_proportional_edit=False,
|
||||
proportional_edit_falloff='SMOOTH', proportional_size=1)
|
||||
bpy.ops.curve.duplicate()
|
||||
bpy.ops.transform.resize(value=(0.5, 0.5, 0.5), constraint_axis=(False, False, False),
|
||||
orient_type='GLOBAL', mirror=False, use_proportional_edit=False,
|
||||
proportional_edit_falloff='SMOOTH', proportional_size=1)
|
||||
bpy.ops.object.editmode_toggle()
|
||||
|
||||
|
||||
def addTestMesh(loc):
|
||||
"""Add a test mesh to the Blender scene.
|
||||
|
||||
This function creates a monkey mesh and a plane mesh at the specified
|
||||
location in the Blender scene. It first adds a monkey mesh with a small
|
||||
radius, rotates it, and applies the transformation. Then, it toggles
|
||||
into edit mode, adds a plane mesh, resizes it, and translates it
|
||||
slightly before toggling back out of edit mode.
|
||||
|
||||
Args:
|
||||
loc (tuple): A tuple representing the (x, y, z) coordinates where
|
||||
the meshes will be added in the Blender scene.
|
||||
"""
|
||||
bpy.ops.mesh.primitive_monkey_add(radius=.01, align='WORLD', enter_editmode=False, location=loc)
|
||||
bpy.ops.transform.rotate(value=-1.5708, axis=(1, 0, 0), constraint_axis=(True, False, False),
|
||||
orient_type='GLOBAL')
|
||||
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
|
||||
bpy.ops.object.editmode_toggle()
|
||||
bpy.ops.mesh.primitive_plane_add(radius=1, align='WORLD', enter_editmode=False, location=loc)
|
||||
bpy.ops.transform.resize(value=(0.01, 0.01, 0.01), constraint_axis=(False, False, False),
|
||||
orient_type='GLOBAL')
|
||||
bpy.ops.transform.translate(value=(-0.01, 0, 0), constraint_axis=(True, False, False),
|
||||
orient_type='GLOBAL')
|
||||
|
||||
bpy.ops.object.editmode_toggle()
|
||||
|
||||
|
||||
def deleteFirstVert(ob):
|
||||
"""Delete the first vertex of a given object.
|
||||
|
||||
This function activates the specified object, enters edit mode,
|
||||
deselects all vertices, selects the first vertex, and then deletes it.
|
||||
The function ensures that the object is properly updated after the
|
||||
deletion.
|
||||
|
||||
Args:
|
||||
ob (bpy.types.Object): The Blender object from which the first
|
||||
"""
|
||||
activate(ob)
|
||||
bpy.ops.object.editmode_toggle()
|
||||
|
||||
bpy.ops.mesh.select_all(action='DESELECT')
|
||||
|
||||
bpy.ops.object.editmode_toggle()
|
||||
for i, v in enumerate(ob.data.vertices):
|
||||
v.select = False
|
||||
if i == 0:
|
||||
v.select = True
|
||||
ob.data.update()
|
||||
|
||||
bpy.ops.object.editmode_toggle()
|
||||
bpy.ops.mesh.delete(type='VERT')
|
||||
bpy.ops.object.editmode_toggle()
|
||||
|
||||
|
||||
def testCalc(o):
|
||||
"""Test the calculation of the camera path for a given object.
|
||||
|
||||
This function invokes the Blender operator to calculate the camera path
|
||||
for the specified object and then deletes the first vertex of that
|
||||
object. It is intended to be used within a Blender environment where the
|
||||
bpy module is available.
|
||||
|
||||
Args:
|
||||
o (Object): The Blender object for which the camera path is to be calculated.
|
||||
"""
|
||||
bpy.ops.object.calculate_cam_path()
|
||||
deleteFirstVert(bpy.data.objects[o.name])
|
||||
|
||||
|
||||
def testCutout(pos):
|
||||
"""Test the cutout functionality in the scene.
|
||||
|
||||
This function adds a test curve based on the provided position, performs
|
||||
a camera operation, and sets the strategy to 'CUTOUT'. It then calls the
|
||||
`testCalc` function to perform further calculations on the camera
|
||||
operation.
|
||||
|
||||
Args:
|
||||
pos (tuple): A tuple containing the x and y coordinates for the
|
||||
position of the test curve.
|
||||
"""
|
||||
addTestCurve((pos[0], pos[1], -.05))
|
||||
bpy.ops.scene.cam_operation_add()
|
||||
o = bpy.context.scene.cam_operations[-1]
|
||||
o.strategy = 'CUTOUT'
|
||||
testCalc(o)
|
||||
|
||||
|
||||
def testPocket(pos):
|
||||
"""Test the pocket operation in a 3D scene.
|
||||
|
||||
This function sets up a pocket operation by adding a test curve based on
|
||||
the provided position. It configures the camera operation settings for
|
||||
the pocket strategy, enabling helix entry and tangential retraction.
|
||||
Finally, it performs a calculation based on the configured operation.
|
||||
|
||||
Args:
|
||||
pos (tuple): A tuple containing the x and y coordinates for
|
||||
the position of the test curve.
|
||||
"""
|
||||
addTestCurve((pos[0], pos[1], -.01))
|
||||
bpy.ops.scene.cam_operation_add()
|
||||
o = bpy.context.scene.cam_operations[-1]
|
||||
o.strategy = 'POCKET'
|
||||
o.movement.helix_enter = True
|
||||
o.movement.retract_tangential = True
|
||||
testCalc(o)
|
||||
|
||||
|
||||
def testParallel(pos):
|
||||
"""Test the parallel functionality of the camera operations.
|
||||
|
||||
This function adds a test mesh at a specified position and then performs
|
||||
camera operations in the Blender environment. It sets the ambient
|
||||
behavior of the camera operation to 'AROUND' and configures the material
|
||||
radius around the model. Finally, it calculates the camera path based on
|
||||
the current scene settings.
|
||||
|
||||
Args:
|
||||
pos (tuple): A tuple containing the x and y coordinates for
|
||||
positioning the test mesh.
|
||||
"""
|
||||
addTestMesh((pos[0], pos[1], -.02))
|
||||
bpy.ops.scene.cam_operation_add()
|
||||
o = bpy.context.scene.cam_operations[-1]
|
||||
o.ambient_behaviour = 'AROUND'
|
||||
o.material.radius_around_model = 0.01
|
||||
bpy.ops.object.calculate_cam_path()
|
||||
|
||||
|
||||
def testWaterline(pos):
|
||||
"""Test the waterline functionality in the scene.
|
||||
|
||||
This function adds a test mesh at a specified position and then performs
|
||||
a camera operation with the strategy set to 'WATERLINE'. It also
|
||||
configures the optimization pixel size for the operation. The function
|
||||
is intended for use in a 3D environment where waterline calculations are
|
||||
necessary for rendering or simulation.
|
||||
|
||||
Args:
|
||||
pos (tuple): A tuple containing the x and y coordinates for
|
||||
the position of the test mesh.
|
||||
"""
|
||||
addTestMesh((pos[0], pos[1], -.02))
|
||||
bpy.ops.scene.cam_operation_add()
|
||||
o = bpy.context.scene.cam_operations[-1]
|
||||
o.strategy = 'WATERLINE'
|
||||
o.optimisation.pixsize = .0002
|
||||
# o.ambient_behaviour='AROUND'
|
||||
# o.material_radius_around_model=0.01
|
||||
|
||||
testCalc(o)
|
||||
|
||||
|
||||
# bpy.ops.object.cam_simulate()
|
||||
|
||||
|
||||
def testSimulation():
|
||||
"""Testsimulation function."""
|
||||
pass
|
||||
|
||||
|
||||
def cleanUp():
|
||||
"""Clean up the Blender scene by removing all objects and camera
|
||||
operations.
|
||||
|
||||
This function selects all objects in the current Blender scene and
|
||||
deletes them. It also removes any camera operations that are present in
|
||||
the scene. This is useful for resetting the scene to a clean state
|
||||
before performing further operations.
|
||||
"""
|
||||
bpy.ops.object.select_all(action='SELECT')
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
while len(bpy.context.scene.cam_operations):
|
||||
bpy.ops.scene.cam_operation_remove()
|
||||
|
||||
|
||||
def testOperation(i):
|
||||
"""Test the operation of a camera path in Blender.
|
||||
|
||||
This function tests a specific camera operation by comparing the
|
||||
generated camera path with an existing reference path. It retrieves the
|
||||
camera operation from the scene and checks if the generated path matches
|
||||
the expected path in terms of vertex count and positions. If there is no
|
||||
existing reference path, it marks the new result as comparable. The
|
||||
function generates a report detailing the results of the comparison,
|
||||
including any discrepancies found.
|
||||
|
||||
Args:
|
||||
i (int): The index of the camera operation to test.
|
||||
|
||||
Returns:
|
||||
str: A report summarizing the results of the operation test.
|
||||
"""
|
||||
s = bpy.context.scene
|
||||
o = s.cam_operations[i]
|
||||
report = ''
|
||||
report += 'testing operation ' + o.name + '\n'
|
||||
|
||||
getPath(bpy.context, o)
|
||||
|
||||
newresult = bpy.data.objects[o.path_object_name]
|
||||
origname = "test_cam_path_" + o.name
|
||||
if origname not in s.objects:
|
||||
report += 'Operation Test Has Nothing to Compare with, Making the New Result as Comparable Result.\n\n'
|
||||
newresult.name = origname
|
||||
else:
|
||||
testresult = bpy.data.objects[origname]
|
||||
m1 = testresult.data
|
||||
m2 = newresult.data
|
||||
test_ok = True
|
||||
if len(m1.vertices) != len(m2.vertices):
|
||||
report += "Vertex Counts Don't Match\n\n"
|
||||
test_ok = False
|
||||
else:
|
||||
different_co_count = 0
|
||||
for i in range(0, len(m1.vertices)):
|
||||
v1 = m1.vertices[i]
|
||||
v2 = m2.vertices[i]
|
||||
if v1.co != v2.co:
|
||||
different_co_count += 1
|
||||
if different_co_count > 0:
|
||||
report += 'Vertex Position Is Different on %i Vertices \n\n' % (different_co_count)
|
||||
test_ok = False
|
||||
if test_ok:
|
||||
report += 'Test Ok\n\n'
|
||||
else:
|
||||
report += 'Test Result Is Different\n \n '
|
||||
print(report)
|
||||
return report
|
||||
|
||||
|
||||
def testAll():
|
||||
"""Run tests on all camera operations in the current scene.
|
||||
|
||||
This function iterates through all camera operations defined in the
|
||||
current Blender scene and executes a test for each operation. The
|
||||
results of these tests are collected into a report string, which is then
|
||||
printed to the console. This is useful for verifying the functionality
|
||||
of camera operations within the Blender environment.
|
||||
"""
|
||||
s = bpy.context.scene
|
||||
report = ''
|
||||
for i in range(0, len(s.cam_operations)):
|
||||
report += testOperation(i)
|
||||
print(report)
|
||||
|
||||
|
||||
tests = [
|
||||
testCutout,
|
||||
testParallel,
|
||||
testWaterline,
|
||||
testPocket,
|
||||
|
||||
]
|
||||
|
||||
cleanUp()
|
||||
|
||||
# deleteFirstVert(bpy.context.active_object)
|
||||
for i, t in enumerate(tests):
|
||||
p = i * .2
|
||||
t((p, 0, 0))
|
||||
# cleanUp()
|
||||
|
||||
|
||||
# cleanUp()
|
||||
|
|
Plik diff jest za duży
Load Diff
Plik diff jest za duży
Load Diff
|
@ -0,0 +1,20 @@
|
|||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line, and also
|
||||
# from the environment for the first two.
|
||||
SPHINXOPTS ?=
|
||||
SPHINXBUILD ?= sphinx-build
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = _build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
Plik binarny nie jest wyświetlany.
Po Szerokość: | Wysokość: | Rozmiar: 11 KiB |
|
@ -0,0 +1,29 @@
|
|||
cam.opencamlib package
|
||||
======================
|
||||
|
||||
Submodules
|
||||
----------
|
||||
|
||||
cam.opencamlib.oclSample module
|
||||
-------------------------------
|
||||
|
||||
.. automodule:: cam.opencamlib.oclSample
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
cam.opencamlib.opencamlib module
|
||||
--------------------------------
|
||||
|
||||
.. automodule:: cam.opencamlib.opencamlib
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Module contents
|
||||
---------------
|
||||
|
||||
.. automodule:: cam.opencamlib
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
|
@ -0,0 +1,327 @@
|
|||
cam package
|
||||
===========
|
||||
|
||||
Subpackages
|
||||
-----------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 4
|
||||
|
||||
cam.nc
|
||||
cam.opencamlib
|
||||
cam.ui_panels
|
||||
|
||||
Submodules
|
||||
----------
|
||||
|
||||
cam.async\_op module
|
||||
--------------------
|
||||
|
||||
.. automodule:: cam.async_op
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
cam.autoupdate module
|
||||
---------------------
|
||||
|
||||
.. automodule:: cam.autoupdate
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
cam.basrelief module
|
||||
--------------------
|
||||
|
||||
.. automodule:: cam.basrelief
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
cam.bridges module
|
||||
------------------
|
||||
|
||||
.. automodule:: cam.bridges
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
cam.cam\_chunk module
|
||||
---------------------
|
||||
|
||||
.. automodule:: cam.cam_chunk
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
cam.cam\_operation module
|
||||
-------------------------
|
||||
|
||||
.. automodule:: cam.cam_operation
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
cam.chain module
|
||||
----------------
|
||||
|
||||
.. automodule:: cam.chain
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
cam.collision module
|
||||
--------------------
|
||||
|
||||
.. automodule:: cam.collision
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
cam.constants module
|
||||
--------------------
|
||||
|
||||
.. automodule:: cam.constants
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
cam.curvecamcreate module
|
||||
-------------------------
|
||||
|
||||
.. automodule:: cam.curvecamcreate
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
cam.curvecamequation module
|
||||
---------------------------
|
||||
|
||||
.. automodule:: cam.curvecamequation
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
cam.curvecamtools module
|
||||
------------------------
|
||||
|
||||
.. automodule:: cam.curvecamtools
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
cam.engine module
|
||||
-----------------
|
||||
|
||||
.. automodule:: cam.engine
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
cam.exception module
|
||||
--------------------
|
||||
|
||||
.. automodule:: cam.exception
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
cam.gcodeimportparser module
|
||||
----------------------------
|
||||
|
||||
.. automodule:: cam.gcodeimportparser
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
cam.gcodepath module
|
||||
--------------------
|
||||
|
||||
.. automodule:: cam.gcodepath
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
cam.image\_utils module
|
||||
-----------------------
|
||||
|
||||
.. automodule:: cam.image_utils
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
cam.involute\_gear module
|
||||
-------------------------
|
||||
|
||||
.. automodule:: cam.involute_gear
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
cam.joinery module
|
||||
------------------
|
||||
|
||||
.. automodule:: cam.joinery
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
cam.machine\_settings module
|
||||
----------------------------
|
||||
|
||||
.. automodule:: cam.machine_settings
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
cam.numba\_wrapper module
|
||||
-------------------------
|
||||
|
||||
.. automodule:: cam.numba_wrapper
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
cam.ops module
|
||||
--------------
|
||||
|
||||
.. automodule:: cam.ops
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
cam.pack module
|
||||
---------------
|
||||
|
||||
.. automodule:: cam.pack
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
cam.parametric module
|
||||
---------------------
|
||||
|
||||
.. automodule:: cam.parametric
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
cam.pattern module
|
||||
------------------
|
||||
|
||||
.. automodule:: cam.pattern
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
cam.polygon\_utils\_cam module
|
||||
------------------------------
|
||||
|
||||
.. automodule:: cam.polygon_utils_cam
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
cam.preferences module
|
||||
----------------------
|
||||
|
||||
.. automodule:: cam.preferences
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
cam.preset\_managers module
|
||||
---------------------------
|
||||
|
||||
.. automodule:: cam.preset_managers
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
cam.puzzle\_joinery module
|
||||
--------------------------
|
||||
|
||||
.. automodule:: cam.puzzle_joinery
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
cam.simple module
|
||||
-----------------
|
||||
|
||||
.. automodule:: cam.simple
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
cam.simulation module
|
||||
---------------------
|
||||
|
||||
.. automodule:: cam.simulation
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
cam.slice module
|
||||
----------------
|
||||
|
||||
.. automodule:: cam.slice
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
cam.strategy module
|
||||
-------------------
|
||||
|
||||
.. automodule:: cam.strategy
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
cam.testing module
|
||||
------------------
|
||||
|
||||
.. automodule:: cam.testing
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
cam.ui module
|
||||
-------------
|
||||
|
||||
.. automodule:: cam.ui
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
cam.utils module
|
||||
----------------
|
||||
|
||||
.. automodule:: cam.utils
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
cam.version module
|
||||
------------------
|
||||
|
||||
.. automodule:: cam.version
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
cam.voronoi module
|
||||
------------------
|
||||
|
||||
.. automodule:: cam.voronoi
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Module contents
|
||||
---------------
|
||||
|
||||
.. automodule:: cam
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
|
@ -0,0 +1,58 @@
|
|||
# Configuration file for the Sphinx documentation builder.
|
||||
|
||||
import os
|
||||
import sys
|
||||
sys.path.insert(0, os.path.abspath('../cam/'))
|
||||
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
||||
|
||||
project = 'BlenderCAM'
|
||||
copyright = '2024'
|
||||
author = 'Vilem Novak, Alain Pelletier & Contributors'
|
||||
release = '1.0.38'
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinx.ext.viewcode',
|
||||
'autoapi.extension',
|
||||
'sphinx.ext.napoleon',
|
||||
'sphinx.ext.graphviz',
|
||||
'sphinx.ext.inheritance_diagram'
|
||||
]
|
||||
|
||||
autoapi_type = 'python'
|
||||
autoapi_dirs = ['../cam']
|
||||
autoapi_ignore = ['*nc*', '*presets*', '*ui_panels*', '*pie_menu*', '*tests*', '*wheels*']
|
||||
|
||||
templates_path = ['_templates']
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '*nc*', '*presets*', '*ui_panels*', '*pie_menu*', '*tests*', '*wheels*']
|
||||
|
||||
add_module_names = False
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
||||
|
||||
html_theme = 'sphinx_book_theme'
|
||||
html_static_path = ['_static']
|
||||
html_logo = "_static/logo_blendercam.png"
|
||||
html_theme_options = {
|
||||
"icon_links": [
|
||||
{
|
||||
"name": "GitHub",
|
||||
"url": "https://github.com/pppalain/blendercam",
|
||||
"icon": "fa-brands fa-square-github",
|
||||
"type": "fontawesome",
|
||||
},
|
||||
{
|
||||
"name": "Matrix",
|
||||
"url": "https://riot.im/app/#/room/#blendercam:matrix.org",
|
||||
"icon": "fa-solid fa-comments",
|
||||
"type": "fontawesome",
|
||||
},
|
||||
]
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
.. BlenderCAM documentation master file, created by
|
||||
sphinx-quickstart on Sun Sep 8 08:23:06 2024.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Welcome to BlenderCAM's API Documentation!
|
||||
==========================================
|
||||
|
||||
This site serves as an introduction to the code behind BlenderCAM.
|
||||
|
||||
If you just want to know how to use the addon to mill projects, check out the `wiki <https://blendercam.com/documentation/>`_
|
||||
|
||||
This resource is for people who want to contribute code to BlenderCAM, people who want to modify the addon for their needs, or anyone who simply want to better understand what is happening 'under the hood'.
|
||||
|
||||
:doc:`overview` offers a guide to the addon files and how they relate to one another.
|
||||
|
||||
:doc:`styleguide` gives tips on editors, linting, formatting etc.
|
||||
|
||||
:doc:`testing` has information on how to run and contribute to the Test Suite.
|
||||
|
||||
:doc:`workflows` contains an explanation of how the addon, testing and documentation are automated via Github Actions.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:caption: Contents:
|
||||
|
||||
overview
|
||||
styleguide
|
||||
testing
|
||||
workflows
|
|
@ -0,0 +1,35 @@
|
|||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=.
|
||||
set BUILDDIR=_build
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.https://www.sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
|
||||
:end
|
||||
popd
|
|
@ -0,0 +1,7 @@
|
|||
cam
|
||||
===
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 4
|
||||
|
||||
cam
|
|
@ -0,0 +1,4 @@
|
|||
Overview
|
||||
===========
|
||||
|
||||
The package cam
|
|
@ -0,0 +1,4 @@
|
|||
Style Guide
|
||||
===========
|
||||
|
||||
Use this style!
|
|
@ -0,0 +1,4 @@
|
|||
Test Suite
|
||||
===========
|
||||
|
||||
This is how tests work
|
|
@ -0,0 +1,4 @@
|
|||
Workflows & Actions
|
||||
===================
|
||||
|
||||
Github Actions automate part of the workflow
|
Ładowanie…
Reference in New Issue