Merge pull request #10 from SpectralVectors/docstrings

Docstrings
pull/273/head
SpectralVectors 2024-09-08 16:09:13 -04:00 zatwierdzone przez GitHub
commit a1ca039b96
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
36 zmienionych plików z 9451 dodań i 3259 usunięć

Wyświetl plik

@ -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)

Wyświetl plik

@ -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

Wyświetl plik

@ -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:

Wyświetl plik

@ -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))

Wyświetl plik

@ -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,

Wyświetl plik

@ -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',

Wyświetl plik

@ -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))

Wyświetl plik

@ -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']:

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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):

Wyświetl plik

@ -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]

Wyświetl plik

@ -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()

Wyświetl plik

@ -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')

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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()

Wyświetl plik

@ -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

Wyświetl plik

@ -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:

Wyświetl plik

@ -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:

Wyświetl plik

@ -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",
},
]
}

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -0,0 +1,7 @@
cam
===
.. toctree::
:maxdepth: 4
cam

Wyświetl plik

@ -0,0 +1,4 @@
Overview
===========
The package cam

Wyświetl plik

@ -0,0 +1,4 @@
Style Guide
===========
Use this style!

Wyświetl plik

@ -0,0 +1,4 @@
Test Suite
===========
This is how tests work

Wyświetl plik

@ -0,0 +1,4 @@
Workflows & Actions
===================
Github Actions automate part of the workflow