Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Swept Surface #438

Open
wants to merge 53 commits into
base: main
Choose a base branch
from
Open

Swept Surface #438

wants to merge 53 commits into from

Conversation

OberGue
Copy link
Member

@OberGue OberGue commented Jun 12, 2024

Overview

swept is a tool within the splinepy.helpme.create environment to provide sweeping a cross section along a trajectory.

Addressed issues

  • Referring to this discussion topic.

Showcase

In order to create a spline patch of a spaghetti pasta, we can pass a circular cross-section and an arbitrary trajectory. The result can be both a 3-dimensional volume spline or a 2-dimensional shell spline, depending on the dimension of your cross-section.

import splinepy

dict_trajectory = {
        "degrees": [3],
        "knot_vectors": [[0.0, 0.0, 0.0, 0.0, 0.2, 0.4, 0.6, 0.8, 0.9, 1.0, 1.0, 1.0, 1.0]],
        "control_points": np.array(
            [   [0.5, 0],
                [0.5, 2],
                [1.0, 3],
                [2.0, 4],
                [2.15, 5],
                [1.8, 5.9],
                [1.0, 6.2],
                [-0.25, 6],
                [-0.5, 5],]),}

trajectory = splinepy.BSpline(**dict_trajectory)
cross_section = splinepy.helpme.create.surface_circle(0.5).nurbs

spaghetti = splinepy.helpme.create.swept(cross_section, trajectory)

Checklists

  • Documentations are up-to-date.
  • Added example(s)
  • Added test(s)

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Introduced a demonstration script for creating and visualizing swept surfaces and solids using spline representations.
    • Enhanced the sweeping process with advanced mathematical techniques for better control over rotation and placement of splines.
  • Tests

    • Added multiple test functions to evaluate swept spline functionality, including basic operations, comparison with extruded surfaces, and validation of control point placement and cross-section alignment.

@jzwar
Copy link
Collaborator

jzwar commented Jun 12, 2024

Nice, can you post some screenshots?

@OberGue
Copy link
Member Author

OberGue commented Jun 12, 2024

Nice, can you post some screenshots?

Sure! Be aware that work is still in progress.

2D_S_topfront_view
3D_questionmark_topfrontview

splinepy/helpme/create.py Outdated Show resolved Hide resolved
splinepy/helpme/create.py Outdated Show resolved Hide resolved
splinepy/helpme/create.py Outdated Show resolved Hide resolved
splinepy/helpme/create.py Outdated Show resolved Hide resolved
splinepy/helpme/create.py Outdated Show resolved Hide resolved
Copy link

coderabbitai bot commented Jun 20, 2024

Walkthrough

The recent updates introduce a new example script for creating and visualizing swept surfaces and solids using spline representations in the splinepy library. Enhancements to the swept function allow for improved control over the sweeping process. The changes also involve the addition of various cross-section shapes and their visualization, along with new test cases to ensure functionality.

Changes

Files Change Summaries
examples/show_swept.py Added functionality to demonstrate the creation and visualization of swept surfaces and solids, including various cross-section shapes.
splinepy/helpme/create.py Expanded the swept function to enhance control over rotation and placement options for sweeping cross-sections along trajectories.
tests/helpme/test_create.py Introduced test functions to validate swept spline functionality and compare with extruded surfaces.
splinepy/bspline.py Minor code cleanup in the remove_knots method by removing unnecessary whitespace.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ExampleScript as show_swept.py
    participant SplinePy as splinepy.create.py
    participant Visualizer as gustaf

    User->>ExampleScript: Run script
    ExampleScript->>SplinePy: Call swept(cross_section, trajectory)
    SplinePy-->>ExampleScript: Return swept_surface
    ExampleScript->>Visualizer: Visualize trajectory, cross-section, and swept_surface
    ExampleScript->>User: Display result
Loading

Possibly related PRs

  • v0.1.3 #454: The changes in the main PR involve the swept function, which is related to the sweeping process of cross-sections along trajectories, while the retrieved PR enhances the __array__ method in KnotVector, potentially affecting how spline objects are handled in sweeping operations.

Poem

In the world of splines, a tale unfolds,
With trajectories swept, new wonders are told.
From cross-sections fine, surfaces arise,
Visualized with care, they dazzle the eyes.
Through math so advanced, they weave and entwine,
Splines of beauty, intricate by design.
🎨✨


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

Share
Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    -- I pushed a fix in commit <commit_id>, please review it.
    -- Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    -- @coderabbitai generate unit testing code for this file.
    -- @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    -- @coderabbitai generate interesting stats about this repository and render them as a table.
    -- @coderabbitai read src/utils.ts and generate unit testing code.
    -- @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    -- @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@j042 j042 marked this pull request as ready for review June 21, 2024 10:46
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 6d3e137 and 48b4bbb.

Files selected for processing (2)
  • examples/show_swept.py (1 hunks)
  • splinepy/helpme/create.py (2 hunks)
Files skipped from review due to trivial changes (1)
  • examples/show_swept.py

splinepy/helpme/create.py Outdated Show resolved Hide resolved
@tataratat tataratat deleted a comment from j042 Jun 27, 2024
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between 48b4bbb and 0c90044.

Files selected for processing (2)
  • examples/show_swept.py (1 hunks)
  • splinepy/helpme/create.py (1 hunks)
Additional comments not posted (1)
examples/show_swept.py (1)

1-4: Approve the import statements.

The imports are correctly placed and necessary for the functionality demonstrated in this example.

examples/show_swept.py Outdated Show resolved Hide resolved
splinepy/helpme/create.py Outdated Show resolved Hide resolved
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between 0c90044 and 4387404.

Files selected for processing (3)
  • examples/show_swept.py (1 hunks)
  • splinepy/bspline.py (2 hunks)
  • splinepy/helpme/create.py (1 hunks)
Files not summarized due to errors (3)
  • splinepy/helpme/create.py: Error: Server error. Please try again later.
  • examples/show_swept.py: Error: Server error. Please try again later.
  • splinepy/bspline.py: Error: Server error. Please try again later.
Additional comments not posted (5)
examples/show_swept.py (5)

1-5: Imports look good.

The import statements for gustaf, numpy, and splinepy are necessary and correctly placed.


6-28: Trajectory definition is clear and correct.

The trajectory is defined using a dictionary of parameters and is correctly initialized as a BSpline.


29-45: Cross-section definition is clear and correct.

The cross-section is defined using a dictionary of parameters and is correctly initialized as a BSpline.


54-59: Swept surface creation is clear and correct.

The swept surface is created using the swept function from splinepy.helpme.create with the specified trajectory, cross-section, and normal vector.


61-71: Visualization section is clear and correct.

The visualization section correctly uses gustaf to display the trajectory, cross-section, and swept surface with appropriate options.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between 0c90044 and 4387404.

Files selected for processing (3)
  • examples/show_swept.py (1 hunks)
  • splinepy/bspline.py (2 hunks)
  • splinepy/helpme/create.py (1 hunks)
Additional comments not posted (17)
examples/show_swept.py (5)

1-2: Organize and validate import statements.

Ensure that the imported modules are necessary and correctly used in the script. The import statements seem appropriate for the functionality in this script.

Also applies to: 4-4


8-27: Validate trajectory definition and initialization.

The trajectory is defined using a dictionary and initialized as a B-spline. The trajectory is then uniformly refined. Ensure that the trajectory definition and refinement are correct and logical.


29-52: Validate cross-section definition and initialization.

The cross-section is defined using a dictionary and initialized as a B-spline. The cross-section normal vector is also defined. Ensure that the cross-section definition and initialization are correct and logical.


54-59: Validate sweep operation.

The sweep operation is performed using the swept function, which takes the trajectory, cross-section, and cross-section normal vector as inputs. Ensure that the sweep operation is correctly implemented.


61-71: Validate visualization.

The resulting spline is visualized using the gustaf library. Ensure that the visualization section is correctly implemented and provides a clear display of the spline structures.

splinepy/bspline.py (5)

182-197: Method documentation is clear and detailed.

The documentation for the uniform_refine_fixed_knots method is clear and provides detailed information about the parameters and return type.


199-204: Parameter handling is correct.

The method correctly handles the para_dims parameter, ensuring it is a list of parametric dimensions to be refined.


206-228: Helper function determine_new_knots_fixed is well-implemented.

The helper function correctly calculates the new knots to be added between each pair of existing knots. The logic is sound and the implementation is efficient.


230-237: Knot insertion logic is correct.

The method correctly determines the new knots for each parametric dimension and inserts them into the knot vector.


206-228: Helper function determine_new_knots_fixed is well-implemented.

The helper function correctly calculates the new knots to be added between each pair of existing knots. The logic is sound and the implementation is efficient.

splinepy/helpme/create.py (7)

301-307: Function documentation is clear and detailed.

The documentation for the swept function is clear and provides detailed information about the parameters and return type.


332-340: Input validation is correct.

The function correctly validates the input types and sets a default value for the cross_section_normal parameter.


346-397: Transformation matrices calculation is correct.

The function correctly calculates the transformation matrices for each trajectory point. The logic is sound and the implementation is efficient.


402-441: Knot insertion logic is correct.

The function correctly calculates the curvature of the trajectory and inserts knots in areas with the highest curvature. The logic is sound and the implementation is efficient.


443-451: Cross-section translation is correct.

The function correctly evaluates the center of the cross-section and translates it to the origin. The logic is sound and the implementation is efficient.


453-469: Control points transformation is correct.

The function correctly transforms the cross-section control points along the trajectory. The logic is sound and the implementation is efficient.


475-504: Spline creation is correct.

The function correctly creates the spline dictionary and checks if the spline is rational. The logic is sound and the implementation is efficient.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between 0c90044 and 4387404.

Files selected for processing (3)
  • examples/show_swept.py (1 hunks)
  • splinepy/bspline.py (2 hunks)
  • splinepy/helpme/create.py (1 hunks)
Additional comments not posted (8)
examples/show_swept.py (5)

1-2: Organize imports for better readability.

Consider grouping the imports into standard library imports, third-party imports, and local imports.

import numpy as np
import gustaf as gus
import splinepy

25-25: Remove commented-out code.

The commented-out code on this line is unnecessary and can be removed to improve readability.

-    # trajectory = splinepy.helpme.create.circle(10)

8-19: Consider adding a docstring for the trajectory definition.

Adding a docstring will improve the readability and understanding of the code.

"""
Define the trajectory as a B-spline with specified degrees, knot vectors, and control points.
"""
dict_trajectory = {
    "degrees": [2],
    "knot_vectors": [[0.0, 0.0, 0.0, 0.333, 0.666, 1.0, 1.0, 1.0]],
    "control_points": np.array(
        [
            [0.0, 0.0, 0.0],
            [0.0, 0.0, 5.0],
            [10.0, 5.0, 0.0],
            [15.0, 0.0, -5.0],
            [20.0, 0.0, 0.0],
        ]
    ),
}

30-42: Consider adding a docstring for the cross-section definition.

Adding a docstring will improve the readability and understanding of the code.

"""
Define the cross-section as a B-spline with specified degrees, knot vectors, and control points.
"""
dict_cross_section = {
    "degrees": [3],
    "knot_vectors": [[0.0, 0.0, 0.0, 0.0, 0.5, 1.0, 1.0, 1.0, 1.0]],
    "control_points": np.array(
        [
            [0.0, 0.0, 0.0],
            [1.0, 2.0, 0.0],
            [2.0, 0.0, 0.0],
            [3.0, -2.0, 0.0],
            [4.0, 0.0, 0.0],
        ]
    ),
}

50-52: Clarify the purpose of the normal vector definition.

Adding a comment to explain the purpose of defining the normal vector will improve readability.

# Define the normal vector for the cross-section, used if the cross-section is not planar in the x-y plane.
cs_nv = np.array([0, 0, 1])
splinepy/helpme/create.py (3)

301-307: Add a docstring summary.

The docstring should start with a brief summary of the function's purpose.

"""
Sweeps a cross-section along a trajectory to create a swept surface.
"""

338-340: Inline comment for default value assignment.

Adding an inline comment will improve readability.

if cross_section_normal is None:
    cross_section_normal = _np.array([0, 0, 1])  # Default normal vector

486-502: Add a comment to explain the rational check.

Adding a comment to explain the rational check will improve readability.

# Check if the spline is rational and handle weights accordingly.
if cross_section.is_rational or trajectory.is_rational:
    def weights(spline):
        if spline.is_rational:
            return spline.weights
        return _np.ones(spline.control_points.shape[0])

    trajectory_weights = weights(trajectory)
    cross_section_weights = weights(cross_section)
    dict_swept_spline["weights"] = _np.outer(trajectory_weights, cross_section_weights).reshape(-1, 1)
    spline_type = _NURBS
else:
    spline_type = _BSpline

splinepy/bspline.py Outdated Show resolved Hide resolved
splinepy/helpme/create.py Outdated Show resolved Hide resolved
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between 4387404 and 6737fb8.

Files selected for processing (2)
  • examples/show_swept.py (1 hunks)
  • splinepy/helpme/create.py (1 hunks)
Additional comments not posted (7)
examples/show_swept.py (5)

1-7: Imports look good.

The necessary modules for creating, visualizing, and exporting swept surfaces are imported.


93-116: Cross-section initialization looks good.

The cross-section is correctly initialized as a B-spline.


139-182: Export functionality looks good.

The swept surface is correctly exported as a mesh.


118-137: Swept surface creation and visualization look good.

The swept surface is correctly created and visualized.

Ensure that the swept surface visualization options are correctly set.

Verification successful

Swept surface creation and visualization look good.

The swept surface is correctly created and visualized.

Scripts executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Description: Verify the swept surface visualization options.

# Test: Search for the swept surface visualization options. Expect: Correctly set options.
rg --type python 'swept_surface.show_options'

Length of output: 76



Script:

#!/bin/bash
# Description: Verify the swept surface visualization options.

# Test: Search for the swept surface visualization options. Expect: Correctly set options.
rg 'swept_surface.show_options' --glob '*.py'

Length of output: 123


8-91: Verify the trajectory variable usage.

Ensure that the trajectory variable is not shadowed by the commented lines to avoid confusion or errors in future modifications.

splinepy/helpme/create.py (2)

330-342: Input type checks and default value settings look good.

The input type checks and default value settings are correctly implemented.


306-328: Correct the typo in the documentation.

Ensure that the parameter cross_section in the documentation is corrected to match the function signature.

-    cross_section : Spline
+    cross_section: Spline

Likely invalid or redundant comment.

splinepy/helpme/create.py Outdated Show resolved Hide resolved
splinepy/helpme/create.py Outdated Show resolved Hide resolved
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between 6737fb8 and cc32216.

Files selected for processing (1)
  • splinepy/helpme/create.py (1 hunks)
Additional comments not posted (5)
splinepy/helpme/create.py (5)

301-305: Consider providing more detailed parameter descriptions.

The function parameters are briefly described. Consider adding more details about their expected types and shapes.


356-358: Avoid recomputing the tangent vector.

Store the tangent vector once to avoid recomputing it.
[PERFORMANCE]

- x = traj.derivative([par_value[0]], [1])
- x = (x / _np.linalg.norm(x)).ravel()
+ x = (traj.derivative([par_value[0]], [1]) / _np.linalg.norm(traj.derivative([par_value[0]], [1]))).ravel()

375-395: Optimize the loop for transformation matrices computation.

The loop for computing transformation matrices can be optimized by avoiding repeated calculations.
[PERFORMANCE]

- for i in range(len(par_value)):
-     x = traj.derivative([par_value[i]], [1])
-     x = (x / _np.linalg.norm(x)).ravel()
-     x_collection.append(x)
-     B.append(B[i] - _np.dot(B[i], x) * x)
-     B[i + 1] = B[i + 1] / _np.linalg.norm(B[i + 1])
-     z = B[i + 1]
-     y = _np.cross(z, x)
-     T.append(_np.vstack((x, y, z)))
-     A.append(_np.linalg.inv(T[i]))
+ for i, param in enumerate(par_value):
+     x = (traj.derivative([param], [1]) / _np.linalg.norm(traj.derivative([param], [1]))).ravel()
+     x_collection.append(x)
+     B.append(B[i] - _np.dot(B[i], x) * x)
+     B[i + 1] = B[i + 1] / _np.linalg.norm(B[i + 1])
+     T.append(_np.vstack((x, _np.cross(B[i + 1], x), B[i + 1])))
+     A.append(_np.linalg.inv(T[-1]))

460-488: Optimize the curvature calculation and knot insertion logic.

The curvature calculation and knot insertion logic can be optimized for better performance.
[PERFORMANCE]

- curv = []
- for i in par_value:
-     curv.append(round(_np.linalg.norm(trajectory.derivative([i], [2])), 2))
- max_curv = max(curv)
- max_indices = [i for i, x in enumerate(curv) if x == max_curv]
- insertion_values = []
- par_values = par_value.ravel()
- for maxi in max_indices:
-     if maxi == 0:
-         insertion_values.append((par_values[maxi] + par_values[maxi + 1]) / 2)
-     elif maxi == len(par_values) - 1:
-         insertion_values.append((par_values[maxi] + par_values[maxi - 1]) / 2)
-     else:
-         insertion_values.append((par_values[maxi] + par_values[maxi - 1]) / 2)
-         insertion_values.append((par_values[maxi] + par_values[maxi + 1]) / 2)
+ curv = _np.array([round(_np.linalg.norm(trajectory.derivative([i], [2])), 2) for i in par_value])
+ max_curv = curv.max()
+ max_indices = _np.where(curv == max_curv)[0]
+ par_values = par_value.ravel()
+ insertion_values = _np.unique([(par_values[maxi] + par_values[maxi + 1]) / 2 if maxi == 0 else (par_values[maxi] + par_values[maxi - 1]) / 2 for maxi in max_indices])

503-543: Optimize the sweeping process.

The sweeping process can be optimized by reducing redundant operations.
[PERFORMANCE]

- evals = trajectory.evaluate(par_value)
- cross_para_center = _np.mean(cross_section.parametric_bounds, axis=0)
- cs_center = cross_section.evaluate(cross_para_center.reshape(-1, cross_section.para_dim)).ravel()
- cross_section.control_points -= cs_center
- A, R = transformation_matrices(trajectory, par_value)
- swept_spline_cps = []
- for index, eval_point in enumerate(evals):
-     temp_csp = []
-     for cscp in cross_section.control_points:
-         normal_cscp = _np.matmul(R, cscp)
-         normal_cscp = _np.matmul(A[index], normal_cscp)
-         normal_cscp += eval_point
-         temp_csp.append(normal_cscp)
-     swept_spline_cps.append(_np.array(temp_csp))
+ evals = trajectory.evaluate(par_value)
+ cross_para_center = _np.mean(cross_section.parametric_bounds, axis=0)
+ cs_center = cross_section.evaluate(cross_para_center.reshape(-1, cross_section.para_dim)).ravel()
+ cross_section.control_points -= cs_center
+ A, R = transformation_matrices(trajectory, par_value)
+ swept_spline_cps = [_np.array([_np.matmul(A[index], _np.matmul(R, cscp)) + eval_point for cscp in cross_section.control_points]) for index, eval_point in enumerate(evals)]

@tataratat tataratat deleted a comment from coderabbitai bot Jul 2, 2024
@OberGue
Copy link
Member Author

OberGue commented Jul 2, 2024

@coderabbitai generate pytest-file for the sweeping routine

This comment was marked as outdated.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between cc32216 and 045a493.

Files selected for processing (2)
  • examples/show_swept.py (1 hunks)
  • splinepy/helpme/create.py (1 hunks)
Additional comments not posted (7)
examples/show_swept.py (5)

1-7: Imports look good!

The import statements are correct and necessary for the functionality.


8-89: Trajectory definition is clear and logical.

The trajectory is defined correctly using a dictionary and the splinepy.BSpline class. The commented-out alternative definitions provide useful examples.


97-116: Cross-section definition is clear and logical.

The cross-section is defined correctly using the splinepy.helpme.create.surface_circle function and is embedded into a NURBS spline.


122-143: Sweeping process is clear and logical.

The sweeping process is defined correctly using the splinepy.helpme.create.swept function and visualization options are set appropriately.


144-187: Export process is clear and logical.

The export process is defined correctly using the splinepy.io.mfem.export function to export the projection of the swept surface.

splinepy/helpme/create.py (2)

301-306: Add type hints for function parameters and return type.

Adding type hints improves code readability and helps with static analysis.

def swept(
    cross_section: Spline,
    trajectory: Spline,
    cross_section_normal: np.ndarray = None,
    auto_refinement: bool = False,
) -> Spline:

449-461: Optimize the rotation matrix computation.

The rotation matrix computation can be optimized by using numpy functions directly.

- angle_of_cs_normal = _np.arctan2(cross_section_normal[2], cross_section_normal[0])
- R = _np.array([
-     [_np.cos(angle_of_cs_normal), 0, _np.sin(angle_of_cs_normal)],
-     [0, 1, 0],
-     [_np.sin(angle_of_cs_normal), 0, _np.cos(angle_of_cs_normal)],
- ])
- R = _np.where(_np.abs(R) < 1e-10, 0, R)
+ R = _np.eye(3)
+ angle_of_cs_normal = _np.arctan2(cross_section_normal[2], cross_section_normal[0])
+ R[0, 0], R[0, 2] = _np.cos(angle_of_cs_normal), _np.sin(angle_of_cs_normal)
+ R[2, 0], R[2, 2] = -_np.sin(angle_of_cs_normal), _np.cos(angle_of_cs_normal)

Likely invalid or redundant comment.

splinepy/helpme/create.py Outdated Show resolved Hide resolved
splinepy/helpme/create.py Outdated Show resolved Hide resolved
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between 045a493 and 3262829.

Files selected for processing (2)
  • splinepy/helpme/create.py (1 hunks)
  • tests/helpme/test_create.py (1 hunks)
Files not reviewed due to errors (1)
  • splinepy/helpme/create.py (no review received)
Additional comments not posted (4)
tests/helpme/test_create.py (4)

348-365: Basic functionality test looks good.

The test correctly checks if the result is not None and verifies the shape of the control points.


368-384: Custom normal vector test looks good.

The test correctly checks if the result is not None when a custom normal vector is provided.


386-404: Invalid inputs test looks good.

The test correctly uses pytest.raises to check for expected exceptions when invalid inputs are provided.


406-421: Rational splines test looks good.

The test correctly checks if the result is not None and verifies that the result is rational.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between 3262829 and adf3ae7.

Files selected for processing (1)
  • splinepy/bspline.py (1 hunks)
Additional comments not posted (1)
splinepy/bspline.py (1)

Line range hint 202-220: Confirm removal of logging statements from remove_knots method.

The removal of logging statements might impact debugging and monitoring of knot removal operations. Consider maintaining a minimal level of logging or providing an alternative method for monitoring these operations.

Verification successful

Logging statements have been successfully removed from the remove_knots method.

  • No logging statements were found in the remove_knots method.
Scripts executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Description: Verify the removal of logging statements from the remove_knots method.

# Test: Search for logging statements in the remove_knots method. Expect: No logging statements.
rg --type python --pcre2 'def remove_knots\(.+\)(?:\n.*){0,20}?self\._logd\('

Length of output: 110



Script:

#!/bin/bash
# Description: Verify the removal of logging statements from the remove_knots method.

# Test: Search for logging statements in the remove_knots method. Expect: No logging statements.
rg 'def remove_knots\(.+\)(?:\n.*){0,20}?self\._logd\('

Length of output: 249



Script:

#!/bin/bash
# Description: Verify the removal of logging statements from the remove_knots method.

# Test: Search for logging statements in the remove_knots method. Expect: No logging statements.
rg --multiline 'def remove_knots\(.+\)(?:\n.*){0,20}?self\._logd\('

Length of output: 69

examples/show_swept.py Show resolved Hide resolved
splinepy/bspline.py Outdated Show resolved Hide resolved
splinepy/helpme/create.py Show resolved Hide resolved
splinepy/helpme/create.py Outdated Show resolved Hide resolved
splinepy/helpme/create.py Outdated Show resolved Hide resolved
tests/helpme/test_create.py Show resolved Hide resolved
tests/helpme/test_create.py Outdated Show resolved Hide resolved
control_points=[[0, 0], [0.5, 1], [1, 0]],
knot_vectors=[[0, 0, 0, 1, 1, 1]],
)
invalid_trajectory = "invalid_trajectory"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looking at this, I think error message should be more informative

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is no error message (you probably know that).
Otherwise, I don't know exactly what you mean. Should I raise an error message with the pytest.raises?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tests/helpme/test_create.py Outdated Show resolved Hide resolved
tests/helpme/test_create.py Outdated Show resolved Hide resolved
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between adf3ae7 and 8790d3d.

Files selected for processing (2)
  • examples/show_swept.py (1 hunks)
  • splinepy/helpme/create.py (1 hunks)
Additional comments not posted (2)
examples/show_swept.py (1)

10-10: Clean up the example.

Remove commented-out code to improve readability.

- ### TRAJECTORY ###

Likely invalid or redundant comment.

splinepy/helpme/create.py (1)

301-337: Ensure consistency in parameter naming and documentation.

Ensure that the parameter cross_section in the documentation matches the function signature.

- cross_section : Spline
+ cross_section : Spline

Likely invalid or redundant comment.

examples/show_swept.py Outdated Show resolved Hide resolved
examples/show_swept.py Outdated Show resolved Hide resolved
examples/show_swept.py Outdated Show resolved Hide resolved
examples/show_swept.py Outdated Show resolved Hide resolved
splinepy/helpme/create.py Outdated Show resolved Hide resolved
splinepy/helpme/create.py Outdated Show resolved Hide resolved
splinepy/helpme/create.py Outdated Show resolved Hide resolved
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between 8790d3d and ca27460.

Files selected for processing (2)
  • examples/show_swept.py (1 hunks)
  • splinepy/helpme/create.py (1 hunks)
Additional comments not posted (5)
examples/show_swept.py (3)

143-143: Remove unnecessary sys.exit() call.

The sys.exit() call is unnecessary and can be removed to allow further code execution if needed.

- sys.exit()

Likely invalid or redundant comment.


1-6: Organize imports for better readability.

Group standard library imports together, followed by third-party imports, and then local imports.

import sys

import numpy as np
import gustaf as gus

import splinepy

Likely invalid or redundant comment.


186-188: Clarify export file name.

The file name testmeshmesh.mesh appears to be a typo. Consider renaming it to a more meaningful name.

- splinepy.io.mfem.export("testmeshmesh.mesh", projection)
+ splinepy.io.mfem.export("swept_surface.mesh", projection)

Likely invalid or redundant comment.

splinepy/helpme/create.py (2)

354-358: Check trajectory para_dim before type check.

Check the trajectory.para_dim before type checking for better error handling.

- if not trajectory.para_dim == 1:
-     raise NotImplementedError("Trajectory must be 1D")
- if not len(cross_section_normal) == 3:
-     raise ValueError("Cross section normal must be 3D")
+ if trajectory.para_dim != 1:
+     raise ValueError("Trajectory must have a parametric dimension of 1")
+ if cross_section_normal is not None and len(cross_section_normal) != 3:
+     raise ValueError("Cross section normal must be a 3D vector")

301-343: Correct the typo in the documentation.

The parameter crossection in the documentation should be corrected to cross_section to match the function signature.

- cross_section : Spline
+ cross_section : Spline

Likely invalid or redundant comment.

examples/show_swept.py Outdated Show resolved Hide resolved
examples/show_swept.py Show resolved Hide resolved
splinepy/helpme/create.py Outdated Show resolved Hide resolved
splinepy/helpme/create.py Outdated Show resolved Hide resolved
splinepy/helpme/create.py Show resolved Hide resolved
splinepy/helpme/create.py Outdated Show resolved Hide resolved
splinepy/helpme/create.py Outdated Show resolved Hide resolved
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between ca27460 and 73a3839.

Files selected for processing (1)
  • splinepy/helpme/create.py (1 hunks)

splinepy/helpme/create.py Outdated Show resolved Hide resolved
splinepy/helpme/create.py Outdated Show resolved Hide resolved
splinepy/helpme/create.py Outdated Show resolved Hide resolved
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between 73a3839 and ca5adbf.

Files selected for processing (1)
  • splinepy/helpme/create.py (1 hunks)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between ca5adbf and 1855ad4.

Files selected for processing (2)
  • examples/show_swept.py (1 hunks)
  • splinepy/helpme/create.py (1 hunks)
Additional comments not posted (2)
examples/show_swept.py (2)

186-188: Clarify export file name.

The file name testmeshmesh.mesh appears to be a typo. Consider renaming it to a more meaningful name.

- splinepy.io.mfem.export("testmeshmesh.mesh", projection)
+ splinepy.io.mfem.export("swept_surface.mesh", projection)

Likely invalid or redundant comment.


27-88: Remove commented-out code or move it to documentation.

The commented-out code for different trajectory examples can be removed or moved to a separate documentation or example file.

- # closed 3D questionmark

Likely invalid or redundant comment.

examples/show_swept.py Outdated Show resolved Hide resolved
examples/show_swept.py Outdated Show resolved Hide resolved
examples/show_swept.py Outdated Show resolved Hide resolved
examples/show_swept.py Outdated Show resolved Hide resolved
splinepy/helpme/create.py Outdated Show resolved Hide resolved
splinepy/helpme/create.py Outdated Show resolved Hide resolved
splinepy/helpme/create.py Outdated Show resolved Hide resolved
splinepy/helpme/create.py Outdated Show resolved Hide resolved
splinepy/helpme/create.py Show resolved Hide resolved
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between 1855ad4 and 87c5193.

Files selected for processing (2)
  • splinepy/helpme/create.py (1 hunks)
  • tests/helpme/test_create.py (1 hunks)

tests/helpme/test_create.py Outdated Show resolved Hide resolved
tests/helpme/test_create.py Show resolved Hide resolved
splinepy/helpme/create.py Show resolved Hide resolved
splinepy/helpme/create.py Outdated Show resolved Hide resolved
splinepy/helpme/create.py Outdated Show resolved Hide resolved
splinepy/helpme/create.py Outdated Show resolved Hide resolved
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between 87c5193 and bfcebfa.

Files selected for processing (1)
  • tests/helpme/test_create.py (1 hunks)

tests/helpme/test_create.py Outdated Show resolved Hide resolved
tests/helpme/test_create.py Outdated Show resolved Hide resolved
tests/helpme/test_create.py Outdated Show resolved Hide resolved
tests/helpme/test_create.py Outdated Show resolved Hide resolved
tests/helpme/test_create.py Outdated Show resolved Hide resolved
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between bfcebfa and 40a6d8e.

Files selected for processing (2)
  • examples/show_swept.py (1 hunks)
  • splinepy/helpme/create.py (1 hunks)
Additional comments not posted (5)
splinepy/helpme/create.py (5)

359-362: Improve error messages for input type checks.

The error messages could be more informative about what types are expected.

- raise NotImplementedError("Sweep only works for splines")
+ raise TypeError("cross_section must be an instance of Spline")
- raise NotImplementedError("Sweep only works for splines")
+ raise TypeError("trajectory must be an instance of Spline")

373-377: Improve debug message for setting default value.

The debug message for setting the default value of cross_section_normal could be more informative.

- _log.debug("No cross section normal given. Defaulting to [0, 0, 1].")
+ _log.debug("No cross_section_normal given. Defaulting to [0, 0, 1].")

387-472: Refactor the transformation_matrices function for better readability and maintainability.

The nested function is lengthy and complex. Consider breaking it down into smaller, more manageable sub-functions.

def calculate_tangent_vector(traj, par_value):
    x = traj.derivative([par_value[0]], [1])
    return (x / _np.linalg.norm(x)).ravel()

def calculate_normal_vector(x):
    vec = [-x[1], x[0], -x[2]]
    if _np.linalg.norm(_np.cross(x, vec)) > _settings.TOLERANCE:
        return _np.cross(x, vec) / _np.linalg.norm(_np.cross(x, vec))
    else:
        vec = [x[2], -x[1], x[0]]
        return _np.cross(x, vec) / _np.linalg.norm(_np.cross(x, vec))

def calculate_rotation_matrix(cross_section_normal):
    angle_of_cs_normal = _np.arctan2(cross_section_normal[2], cross_section_normal[0])
    R = _np.array([
        [_np.cos(angle_of_cs_normal), 0, _np.sin(angle_of_cs_normal)],
        [0, 1, 0],
        [_np.sin(angle_of_cs_normal), 0, _np.cos(angle_of_cs_normal)],
    ])
    return _np.where(_np.abs(R) < _settings.TOLERANCE, 0, R)

def transformation_matrices(traj, par_value, cross_section_normal):
    x = calculate_tangent_vector(traj, par_value)
    B = [calculate_normal_vector(x)]
    T, A = [], []

    for i in range(len(par_value)):
        x = calculate_tangent_vector(traj, [par_value[i]])
        B.append(B[i] - _np.dot(B[i], x) * x)
        B[i + 1] = B[i + 1] / _np.linalg.norm(B[i + 1])
        z = B[i + 1]
        y = _np.cross(z, x)
        T.append(_np.vstack((x, y, z)))
        A.append(_np.linalg.inv(T[i]))

    R = calculate_rotation_matrix(cross_section_normal)
    return A, R

483-503: Optimize the rotation matrix computation.

The rotation matrix computation can be optimized by using numpy functions directly.

- angle_of_cs_normal = _np.arctan2(cross_section_normal[2], cross_section_normal[0])
- R = _arr.rotation_matrix_around_axis(
-     axis=[0, 1, 0], rotation=angle_of_cs_normal, degree=False
- )
+ R = _np.eye(3)
+ angle_of_cs_normal = _np.arctan2(cross_section_normal[2], cross_section_normal[0])
+ R[0, 0], R[0, 2] = _np.cos(angle_of_cs_normal), _np.sin(angle_of_cs_normal)
+ R[2, 0], R[2, 2] = -_np.sin(angle_of_cs_normal), _np.cos(angle_of_cs_normal)

- R = _np.where(_np.abs(R) < _settings.TOLERANCE, 0, R)

505-573: Refactor the sweeping process for better readability and maintainability.

The sweeping process is lengthy and complex. Consider breaking it down into smaller, more manageable sub-functions.

def translate_to_origin(cross_section):
    cross_para_center = _np.mean(cross_section.parametric_bounds, axis=0)
    cs_center = cross_section.evaluate(cross_para_center.reshape(-1, cross_section.para_dim)).ravel()
    cross_section.control_points -= cs_center
    return cross_section

def set_cross_section_control_points(trajectory, cross_section, par_value, A, R, set_on_trajectory):
    swept_spline_cps = []
    for i, par_val in enumerate(par_value):
        temp_csp = []
        if set_on_trajectory:
            evals = trajectory.evaluate([par_val]).ravel()
        for cscp in cross_section.control_points:
            normal_cscp = _np.matmul(R, cscp)
            normal_cscp = _np.matmul(A[i], normal_cscp)
            if set_on_trajectory:
                normal_cscp += evals
            else:
                normal_cscp += trajectory.control_points[i]
            temp_csp.append(normal_cscp)
        swept_spline_cps.append(_np.array(temp_csp))
    return swept_spline_cps

cross_section = translate_to_origin(cross_section)
A, R = transformation_matrices(trajectory, par_value, cross_section_normal)
swept_spline_cps = set_cross_section_control_points(trajectory, cross_section, par_value, A, R, set_on_trajectory)

examples/show_swept.py Show resolved Hide resolved
examples/show_swept.py Outdated Show resolved Hide resolved
examples/show_swept.py Outdated Show resolved Hide resolved
examples/show_swept.py Outdated Show resolved Hide resolved
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post

Actionable comments posted: 1

Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between 77a9d75 and 17324e1.

Files selected for processing (2)
  • examples/show_swept.py (1 hunks)
  • splinepy/helpme/create.py (1 hunks)
Additional comments not posted (5)
examples/show_swept.py (5)

10-32: Trajectory definition is clear and well-implemented.

The trajectory for the swept surface is defined clearly using a dictionary that specifies the degrees, knot vectors, and control points, which is then used to create a BSpline object. This is a standard and effective way to define splines in the splinepy library.


39-46: Cross-section creation is effectively handled.

The script effectively creates various types of cross-sections using helper functions from splinepy.helpme.create, which simplifies the creation process. Converting these to NURBS is appropriate for ensuring compatibility with operations that may involve rational splines.


55-101: Sweeping process is versatile and well-demonstrated.

The script showcases the versatility of the swept function by creating multiple swept surfaces and solids with various configurations. Each swept object is created with clear and appropriate parameters, effectively demonstrating the capabilities of the sweeping functionality.


113-138: Visualization is effectively implemented.

The script uses the gustaf library to visualize the trajectory, cross-sections, and swept surfaces. Different visualization configurations are applied to different objects, which helps in clearly distinguishing between them and effectively demonstrates the results of the sweeping process.


1-5: Organize imports for better readability.

Group standard library imports together, followed by third-party imports, and then local imports. This improves readability and maintainability.

import gustaf as gus
import numpy as np
import splinepy

Likely invalid or redundant comment.

Comments failed to post (1)
splinepy/helpme/create.py (1)

301-578: Comprehensive review of the swept function.

  1. Correctness and Logic:

    • The function handles the creation of a swept spline by manipulating control points along a trajectory. The logic follows expected operations for such functionality, including transformations and rotations.
  2. Performance:

    • The function performs multiple operations on arrays and matrices. Consider using numpy built-in functions for potential performance improvements.
  3. Readability and Maintainability:

    • The function is complex and lengthy, which could affect maintainability. Consider breaking it down into smaller, more manageable sub-functions.
  4. Error Handling:

    • Proper checks are in place for the input types. More detailed error messages could be helpful, particularly when inputs do not meet expected conditions.
  5. Documentation:

    • The documentation is thorough, but ensure that the parameter cross_section in the documentation is corrected to match the function signature.
  6. Security/PII Leakage:

    • No personal identifiable information or security-sensitive operations appear to be involved.
  7. Unit Tests:

    • Unit tests are noted as missing in the PR checklist. It's crucial to add comprehensive tests, especially given the mathematical complexity of the operations involved.
  8. Best Practices:

    • The use of more numpy built-in functions for array manipulations could simplify the code and potentially improve performance.

Action Items:

  • Correct the typo in the documentation.
  • Consider refactoring to improve readability and maintainability.
  • Add unit tests to ensure the function's correctness across various scenarios.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between 17324e1 and 016b7c6.

Files selected for processing (4)
  • examples/show_swept.py (1 hunks)
  • splinepy/bspline.py (1 hunks)
  • splinepy/helpme/create.py (1 hunks)
  • tests/helpme/test_create.py (2 hunks)
Additional comments not posted (8)
examples/show_swept.py (5)

1-5: Imports are appropriate and well-organized.

The imports are essential for the functionality demonstrated in the script and are correctly structured.


10-33: Trajectory definition is clear and effectively utilizes the splinepy library.

The trajectory is defined in a structured manner using a dictionary, which is then used to create a BSpline object. This method is clear and allows for easy adjustments to the trajectory parameters.


39-51: Cross-section definitions are well-implemented and demonstrate the flexibility of the swept function.

The script effectively uses helper functions from splinepy to create various types of cross-sections, showcasing the versatility of the swept function. This approach not only simplifies the creation process but also ensures consistency across different types of cross-sections.


55-92: Sweeping operations are correctly implemented and effectively demonstrate the capabilities of the swept function.

The script performs multiple sweeping operations using different combinations of trajectories and cross-sections. The use of parameters like cross_section_normal and set_on_trajectory effectively demonstrates the customization options available in the swept function.


113-138: Visualization section is well-implemented and provides clear visual outputs of the sweeping results.

The script uses the gustaf library to visualize the results of the sweeping operations. Different visualization calls are made for different types of results, effectively demonstrating the outcomes of the sweeping operations.

splinepy/helpme/create.py (1)

301-578: Comprehensive Review of the swept Function

The swept function introduces significant functionality to the splinepy library, enabling the creation of complex geometries by sweeping a cross-section along a trajectory. Here's a detailed review based on various aspects:

  1. Correctness and Logic:

    • The function handles the creation of a swept spline by manipulating control points along a trajectory. The logic generally follows the expected operations for such functionality, including transformations and rotations.
    • The use of transformation matrices and knot insertion based on curvature is appropriate for creating smooth transitions along the trajectory.
  2. Performance:

    • The function performs multiple operations on arrays and matrices, including inversions and multiplications. Consider optimizing these operations if profiling indicates a bottleneck, especially for large splines.
  3. Readability and Maintainability:

    • The function is lengthy and complex, which could affect maintainability. Consider breaking it down into smaller, more manageable sub-functions, especially the parts handling matrix transformations and knot insertion.
  4. Error Handling:

    • Proper checks are in place for the input types. However, more detailed error messages could be helpful, particularly when inputs do not meet expected conditions.
  5. Documentation:

    • The documentation is thorough, providing clear descriptions of parameters and the return type. Ensure that the parameter cross_section in the documentation is corrected to cross_section to match the function signature.
  6. Security/PII Leakage:

    • No personal identifiable information or security-sensitive operations appear to be involved.
  7. Unit Tests:

    • Unit tests are noted as missing in the PR checklist. It's crucial to add comprehensive tests, especially given the mathematical complexity of the operations involved.
  8. Best Practices:

    • The function could benefit from the use of more numpy built-in functions for array manipulations to simplify the code and potentially improve performance.

Action Items:

  • Correct the typo in the documentation.
  • Consider refactoring to improve readability and maintainability.
  • Add unit tests to ensure the function's correctness across various scenarios.
splinepy/bspline.py (1)

Line range hint 1-1: Approved: Minor stylistic improvement.

The removal of the blank line before the return statement in the remove_knots method improves code cleanliness without affecting functionality.

tests/helpme/test_create.py (1)

402-423: Approved: Effective comparison between swept and extruded surfaces.

The test function test_swept_to_extruded effectively compares the control points of linear swept and extruded surfaces, ensuring consistency between the swept and extruded methods under similar conditions.

tests/helpme/test_create.py Outdated Show resolved Hide resolved
tests/helpme/test_create.py Outdated Show resolved Hide resolved
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between 016b7c6 and 43e48d4.

Files selected for processing (1)
  • tests/helpme/test_create.py (2 hunks)
Additional comments not posted (3)
tests/helpme/test_create.py (3)

402-423: LGTM!

The test function test_swept_to_extruded effectively compares the control points of a linear swept surface to those of an extruded surface derived from the same cross-section. This serves to confirm that both methods produce equivalent results under specific conditions, thereby reinforcing the correctness of the implementation.


425-512: LGTM!

The test function test_swept_control_point_placing effectively checks the placement of control points within the swept surface. It ensures that the normal vector of the cross-section is consistent with the tangent vector of the trajectory, which is vital for maintaining the accuracy of geometric transformations during the sweeping process.


514-538: LGTM!

The test function test_swept_cs_centering effectively checks that the center of the cross-section aligns with the trajectory, validating the spatial correctness of the swept surface. This test evaluates the coordinates of the swept spline against the trajectory at a designated parameter value, ensuring adherence to expected geometric properties.

tests/helpme/test_create.py Show resolved Hide resolved
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between 43e48d4 and 255b874.

Files selected for processing (1)
  • examples/show_swept.py (1 hunks)
Additional comments not posted (2)
examples/show_swept.py (2)

39-46: LGTM!

The cross-section creation code is well-implemented and the comments clearly explain each cross-section.


116-123: LGTM!

The visualization code segments are well-implemented and provide a clear demonstration of the swept surfaces and solids. The comments clearly explain the purpose of each visualization window.

Also applies to: 131-147, 150-157

examples/show_swept.py Show resolved Hide resolved
examples/show_swept.py Show resolved Hide resolved
examples/show_swept.py Outdated Show resolved Hide resolved
examples/show_swept.py Show resolved Hide resolved
@j042
Copy link
Member

j042 commented Sep 12, 2024

Good progress! If you are done with comments, can you please resolve the conversations? Feel free to leave a note if needed

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between 255b874 and 4207da0.

Files selected for processing (1)
  • examples/show_swept.py (1 hunks)
Additional comments not posted (7)
examples/show_swept.py (7)

1-4: LGTM!

The imports are correctly used and necessary for the script's functionality.


39-46: LGTM!

The cross-section creation is correctly implemented using the appropriate helper functions from the splinepy library. The variable names are descriptive and convey the shape of the cross-sections.


48-51: LGTM!

The custom normal vector is correctly defined, and the comment provides a clear explanation of when to use a custom normal vector.


115-123: LGTM!

The visualization of the swept surface is correctly implemented using the gustaf library. The variable names are descriptive and convey the objects being visualized.


149-157: LGTM!

The visualization of the swept solids with rectangular cross-sections is correctly implemented using the gustaf library. The variable names are descriptive and convey the objects being visualized.


6-35: Improve variable naming for clarity.

The trajectory definition is clear and well-structured. However, consider renaming the variable dict_trajectory to a more descriptive name, such as hook_trajectory_dict, to clearly convey the shape of the trajectory.

-dict_trajectory = {
+hook_trajectory_dict = {

Likely invalid or redundant comment.


126-147: Use consistent naming for visualization options.

The visualization options are correctly adjusted using the show_options dictionary. However, consider using the vis_options key instead of show_options for consistency with the rest of the codebase.

-swept_solid_disk_2.show_options["alpha"] = 0.3
-swept_solid_disk_3.show_options["alpha"] = 0.3
-trajectory.show_options["control_points"] = False
+swept_solid_disk_2.vis_options["alpha"] = 0.3
+swept_solid_disk_3.vis_options["alpha"] = 0.3
+trajectory.vis_options["show_control_points"] = False

Likely invalid or redundant comment.

examples/show_swept.py Show resolved Hide resolved
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between 5751704 and 53851c7.

Files selected for processing (1)
  • splinepy/helpme/create.py (1 hunks)
Additional comments not posted (7)
splinepy/helpme/create.py (7)

360-381: Input validation looks good!

The input validation section thoroughly checks the types of the input parameters and raises appropriate exceptions. It also sets a default value for cross_section_normal if not provided.


384-390: Initialization is correct.

The initialization section embeds the trajectory and cross_section into 3D space and initializes the par_value using Greville abscissae, which is the correct approach.


392-508: Transformation matrices calculation is comprehensive.

The transformation matrices calculation section handles the computation of tangent and normal vectors, closed trajectories, and the rotation matrix for the cross-section normal vector. It also applies an additional rotation if rotation_adaption is provided.

The logic is correct and follows the mathematical formulas from the NURBS book. The use of _settings.TOLERANCE for numerical comparisons is a good practice.


510-545: Sweeping process is well-structured.

The sweeping process section translates the cross-section to the origin and iterates over the trajectory, applying the calculated transformations to the cross-section control points. The transformed control points are collected for spline creation.

The logic is correct and follows the expected steps for sweeping a cross-section along a trajectory.


546-578: Spline creation handles rational splines correctly.

The spline creation section constructs a spline dictionary with the necessary components (degrees, knot vectors, control points) and handles rational splines by adding weights. It instantiates the appropriate spline type (NURBS or BSpline) based on the rationality of the input splines.

The logic is correct and ensures that the resulting spline maintains the properties of the input splines.


301-578: Docstring is comprehensive and includes an example.

The docstring for the swept function is well-written and provides a clear explanation of the function's purpose, parameters, and return value. It also includes a helpful example demonstrating how to use the function.

The inclusion of the mathematical reference from the NURBS book is a nice touch for users who want to dive deeper into the underlying theory.


301-301: Reminder: Address the missing unit tests.

The PR checklist indicates that unit tests have been added, but it's important to ensure that the swept function is thoroughly tested, given the complexity of the operations involved.

To verify the existence and comprehensiveness of the unit tests, you can run the following script:

If the tests are missing or inadequate, consider adding more test cases to cover different scenarios and edge cases.

Verification successful

Unit tests for the swept function are comprehensive and well-structured.

The existing unit tests in tests/helpme/test_create.py cover various aspects of the swept function, including:

  • Basic functionality
  • Comparison with related functions (e.g., extruded surfaces)
  • Geometric properties (control point placement and cross-section centering)
  • Different input types (NURBS and BSpline)
  • Randomized inputs for diverse scenarios

These tests appear to address the complexity of the swept function and cover potential edge cases. The PR checklist's indication that unit tests have been added is accurate and satisfactory.

Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the existence and comprehensiveness of unit tests for the `swept` function.

# Test: Check if a test file for the `swept` function exists.
# Expect: At least one test file matching the pattern `test_swept*.py`.
fd -e py -p "test_swept.*\.py$"

# Test: Check if the test file contains test cases for various scenarios.
# Expect: Test cases covering different input types, edge cases, and expected outputs.
rg -i -C 5 "def test_swept_" $(fd -e py -p "test_swept.*\.py$")

Length of output: 2710

splinepy/helpme/create.py Outdated Show resolved Hide resolved
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between 53851c7 and ba5264d.

Files selected for processing (1)
  • splinepy/helpme/create.py (1 hunks)
Additional comments not posted (4)
splinepy/helpme/create.py (4)

359-381: Comprehensive input validation and default value handling.

The function performs thorough checks on the input types and dimensions, raising appropriate exceptions for invalid inputs. It also handles the default value for cross_section_normal correctly.


383-392: Initialization steps are correct.

The function correctly embeds the trajectory and cross_section into 3D space and initializes the par_value using Greville abscissae.


511-546: Sweeping process is implemented correctly.

The function correctly translates the cross-section to the origin and iterates over the trajectory points, applying the necessary transformations to the cross-section control points. It also handles the positioning of the cross-section based on the set_on_trajectory flag.


547-579: Consider adding more detailed error handling for spline creation.

The spline creation logic is correct, but adding more detailed error handling can improve robustness.

- dict_swept_spline = {
-     "degrees": [*cross_section.degrees, *trajectory.degrees],
-     "knot_vectors": [
-         *cross_section.knot_vectors,
-         *trajectory.knot_vectors,
-     ],
-     "control_points": _np.asarray(swept_spline_cps).reshape(-1, cross_section.dim),
- }
+ try:
+     dict_swept_spline = {
+         "degrees": [*cross_section.degrees, *trajectory.degrees],
+         "knot_vectors": [
+             *cross_section.knot_vectors,
+             *trajectory.knot_vectors,
+         ],
+         "control_points": _np.asarray(swept_spline_cps).reshape(-1, cross_section.dim),
+     }
+ except Exception as e:
+     raise ValueError(f"Error creating swept spline dictionary: {e}")

Likely invalid or redundant comment.

Comment on lines 393 to 509
"Division by zero occurred. Using alternative vector for B."
)

# initialize transformation matrices and tangent-vector-collection
T = []
A = []
tang_collection = []

# evaluating transformation matrices for each trajectory point
for i in range(len(par_value)):
# calculation according to NURBS Book, eq. 10.27
# tangent vector e1 on trajectory at parameter value i
e1 = trajectory.derivative([par_value[i]], [1])
e1 = (e1 / _np.linalg.norm(e1)).ravel()
# collecting tangent vectors for later use
tang_collection.append(e1)

# projecting B_(i) onto the plane normal to e1
B.append(B[i] - _np.dot(B[i], e1) * e1)
B[i + 1] = B[i + 1] / _np.linalg.norm(B[i + 1])

# defining e2 and e3 vectors
e3 = B[i + 1]
e2 = _np.cross(e3, e1)

# array of transformation matrices from global to local coordinates
T.append(_np.vstack((e1, e2, e3)))

# array of transformation matrices from local to global coordinates
A.append(_np.linalg.inv(T[i]))

# separate procedure, if trajectory is closed and B[0] != B[-1]
# recalculate B-vector and middle the values between B and B_rec
# according to NURBS Book, Piegl & Tiller, 2nd edition, p. 483
is_trajectory_closed = _np.array_equal(
trajectory.evaluate([[0]]), trajectory.evaluate([par_value[-1]])
)
is_B_start_equal_B_end = _np.array_equal(B[0], B[-1])

if is_trajectory_closed and not is_B_start_equal_B_end:
# reset transformation matrices
T = []
A = []
# preallocate B_rec
B_rec = [None] * len(B)
# make sure start of B_rec is equal to the end of B
B_rec[0] = B[-1]
# redo the calculation of B using tang_collection from before
# in order to avoid recalculating the tangent vectors;
# calculation according to NURBS Book, eq. 10.27
for i in range(len(par_value)):
B_rec[i + 1] = (
B_rec[i]
- _np.dot(B_rec[i], tang_collection[i]) * tang_collection[i]
)
B_rec[i + 1] = B_rec[i + 1] / _np.linalg.norm(B_rec[i + 1])
# middle point between B and B_rec
B_rec[i + 1] = (B[i + 1] + B_rec[i + 1]) * 0.5
# normalizing B_rec
B_rec[i + 1] = B_rec[i + 1] / _np.linalg.norm(B_rec[i + 1])
# defining e2 and e3 axis-vectors
e3 = B_rec[i + 1]
e2 = _np.cross(e3, tang_collection[i])

# array of transformation matrices from global to local coordinates
T.append(_np.vstack((tang_collection[i], e2, e3)))

# array of transformation matrices from local to global coordinates
A.append(_np.linalg.inv(T[i]))

# check if the beginning and the end of the B-vector are the same
if not _np.allclose(B_rec[0], B_rec[-1], rtol=1e-3):
_log.warning(
"Vector calculation is not exact due to the "
"trajectory being closed in an uncommon way."
)

### ROTATION MATRIX ###

# calculate angle of cross section normal vector around e2-axis
angle_of_cs_normal = _np.arctan2(
cross_section_normal[2], cross_section_normal[0]
)

# calculate rotation matrix for cross section normal vector
R = _arr.rotation_matrix_around_axis(
axis=[0, 1, 0], rotation=angle_of_cs_normal, degree=False
)

# rotate cross section around trajectory tangent vector if wanted
if rotation_adaption is not None:
R = _np.matmul(
_arr.rotation_matrix_around_axis(
axis=[1, 0, 0], rotation=rotation_adaption, degree=False
),
R,
)

# remove numerical noise
R = _np.where(_np.abs(R) < _settings.TOLERANCE, 0, R)
Copy link

@coderabbitai coderabbitai bot Sep 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider refactoring the transformation matrices calculation.

The transformation matrices calculation is lengthy and complex. Consider breaking it down into smaller, more manageable sub-functions for better readability and maintainability.

def calculate_tangent_vector(traj, par_value):
    x = traj.derivative([par_value[0]], [1])
    return (x / _np.linalg.norm(x)).ravel()

def calculate_normal_vector(x):
    vec = [-x[1], x[0], -x[2]]
    if _np.linalg.norm(_np.cross(x, vec)) > _settings.TOLERANCE:
        return _np.cross(x, vec) / _np.linalg.norm(_np.cross(x, vec))
    else:
        vec = [x[2], -x[1], x[0]]
        return _np.cross(x, vec) / _np.linalg.norm(_np.cross(x, vec))

def calculate_rotation_matrix(cross_section_normal, rotation_adaption):
    angle_of_cs_normal = _np.arctan2(cross_section_normal[2], cross_section_normal[0])
    R = _arr.rotation_matrix_around_axis(axis=[0, 1, 0], rotation=angle_of_cs_normal, degree=False)
    if rotation_adaption is not None:
        R = _np.matmul(_arr.rotation_matrix_around_axis(axis=[1, 0, 0], rotation=rotation_adaption, degree=False), R)
    return _np.where(_np.abs(R) < _settings.TOLERANCE, 0, R)

def transformation_matrices(traj, par_value, cross_section_normal, rotation_adaption):
    x = calculate_tangent_vector(traj, par_value)
    B = [calculate_normal_vector(x)]
    T, A = [], []

    for i in range(len(par_value)):
        x = calculate_tangent_vector(traj, [par_value[i]])
        B.append(B[i] - _np.dot(B[i], x) * x)
        B[i + 1] = B[i + 1] / _np.linalg.norm(B[i + 1])
        z = B[i + 1]
        y = _np.cross(z, x)
        T.append(_np.vstack((x, y, z)))
        A.append(_np.linalg.inv(T[i]))

    R = calculate_rotation_matrix(cross_section_normal, rotation_adaption)
    return A, R

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@j042 what do you think about it?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

splinepy/helpme/create.py Outdated Show resolved Hide resolved
@OberGue
Copy link
Member Author

OberGue commented Sep 16, 2024

@j042 Can/should I add an entry about swept to the Readme?

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between ba5264d and b2a3d5e.

Files selected for processing (1)
  • splinepy/helpme/create.py (1 hunks)
Additional comments not posted (3)
splinepy/helpme/create.py (3)

359-380: Input validation looks good!

The input validation section thoroughly checks the types and values of the input parameters, ensuring that they meet the expected criteria. The error messages are informative and help identify issues with the input.


382-391: Initialization and setup look good!

The initialization and setup section correctly prepares the input splines and initializes the necessary parameters for the sweeping process. The use of create.embedded ensures that both splines are in 3D space, which is required for the subsequent calculations.


561-593: Spline creation is handled correctly!

The spline creation section correctly assembles the necessary components (degrees, knot vectors, control points, and weights) to create the swept spline. The handling of rational splines ensures that the weights are properly incorporated into the swept spline. The determination of the spline type based on the rationality of the input splines is a good approach.

Comment on lines +392 to +486
### TRANSFORMATION MATRICES ###

# tangent vector 'e1' of trajectory at parameter value 0
e1 = trajectory.derivative([par_value[0]], [1])
e1 = (e1 / _np.linalg.norm(e1)).ravel()

# evaluating a vector normal to e1
vec = [-e1[1], e1[0], -e1[2]]
B = []
# avoid dividing by zero
if _np.linalg.norm(_np.cross(e1, vec)) > _settings.TOLERANCE:
B.append(_np.cross(e1, vec) / _np.linalg.norm(_np.cross(e1, vec)))
else:
vec = [e1[2], -e1[1], e1[0]]
B.append(_np.cross(e1, vec) / _np.linalg.norm(_np.cross(e1, vec)))
# add debug message
_log.debug(
"Division by zero occurred. Using alternative vector for B."
)

# initialize transformation matrices and tangent-vector-collection
T = []
A = []
tang_collection = []

# evaluating transformation matrices for each trajectory point
for i in range(len(par_value)):
# calculation according to NURBS Book, eq. 10.27
# tangent vector e1 on trajectory at parameter value i
e1 = trajectory.derivative([par_value[i]], [1])
e1 = (e1 / _np.linalg.norm(e1)).ravel()
# collecting tangent vectors for later use
tang_collection.append(e1)

# projecting B_(i) onto the plane normal to e1
B.append(B[i] - _np.dot(B[i], e1) * e1)
B[i + 1] = B[i + 1] / _np.linalg.norm(B[i + 1])

# defining e2 and e3 vectors
e3 = B[i + 1]
e2 = _np.cross(e3, e1)

# array of transformation matrices from global to local coordinates
T.append(_np.vstack((e1, e2, e3)))

# array of transformation matrices from local to global coordinates
A.append(_np.linalg.inv(T[i]))

# separate procedure, if trajectory is closed and B[0] != B[-1]
# recalculate B-vector and middle the values between B and B_rec
# according to NURBS Book, Piegl & Tiller, 2nd edition, p. 483
is_trajectory_closed = _np.array_equal(
trajectory.evaluate([[0]]), trajectory.evaluate([par_value[-1]])
)
is_B_start_equal_B_end = _np.array_equal(B[0], B[-1])

if is_trajectory_closed and not is_B_start_equal_B_end:
# reset transformation matrices
T = []
A = []
# preallocate B_rec
B_rec = [None] * len(B)
# make sure start of B_rec is equal to the end of B
B_rec[0] = B[-1]
# redo the calculation of B using tang_collection from before
# in order to avoid recalculating the tangent vectors;
# calculation according to NURBS Book, eq. 10.27
for i in range(len(par_value)):
B_rec[i + 1] = (
B_rec[i]
- _np.dot(B_rec[i], tang_collection[i]) * tang_collection[i]
)
B_rec[i + 1] = B_rec[i + 1] / _np.linalg.norm(B_rec[i + 1])
# middle point between B and B_rec
B_rec[i + 1] = (B[i + 1] + B_rec[i + 1]) * 0.5
# normalizing B_rec
B_rec[i + 1] = B_rec[i + 1] / _np.linalg.norm(B_rec[i + 1])
# defining e2 and e3 axis-vectors
e3 = B_rec[i + 1]
e2 = _np.cross(e3, tang_collection[i])

# array of transformation matrices from global to local coordinates
T.append(_np.vstack((tang_collection[i], e2, e3)))

# array of transformation matrices from local to global coordinates
A.append(_np.linalg.inv(T[i]))

# check if the beginning and the end of the B-vector are the same
if not _np.allclose(B_rec[0], B_rec[-1], rtol=1e-3):
_log.warning(
"Vector calculation is not exact due to the "
"trajectory being closed in an uncommon way."
)

### ROTATION MATRIX ###
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Transformation matrices calculation is correct but could be refactored.

The transformation matrices calculation section correctly implements the equations and logic described in the NURBS book. The handling of closed trajectories is a nice addition to ensure continuity.

However, the section is lengthy and complex. Consider breaking it down into smaller, more manageable sub-functions for better readability and maintainability.

Here's a suggestion for refactoring the transformation matrices calculation:

def calculate_tangent_vector(traj, par_value):
    x = traj.derivative([par_value[0]], [1])
    return (x / _np.linalg.norm(x)).ravel()

def calculate_normal_vector(x):
    vec = [-x[1], x[0], -x[2]]
    if _np.linalg.norm(_np.cross(x, vec)) > _settings.TOLERANCE:
        return _np.cross(x, vec) / _np.linalg.norm(_np.cross(x, vec))
    else:
        vec = [x[2], -x[1], x[0]]
        return _np.cross(x, vec) / _np.linalg.norm(_np.cross(x, vec))

def transformation_matrices(traj, par_value, cross_section_normal):
    x = calculate_tangent_vector(traj, par_value)
    B = [calculate_normal_vector(x)]
    T, A = [], []

    for i in range(len(par_value)):
        x = calculate_tangent_vector(traj, [par_value[i]])
        B.append(B[i] - _np.dot(B[i], x) * x)
        B[i + 1] = B[i + 1] / _np.linalg.norm(B[i + 1])
        z = B[i + 1]
        y = _np.cross(z, x)
        T.append(_np.vstack((x, y, z)))
        A.append(_np.linalg.inv(T[i]))

    return A

Comment on lines +525 to +560
### SWEEPING PROCESS ###

# evaluate center of cross-section and translate to origin
cross_para_center = _np.mean(cross_section.parametric_bounds, axis=0)
cs_center = cross_section.evaluate(
cross_para_center.reshape(-1, cross_section.para_dim)
).ravel()
cross_section.control_points -= cs_center

# set cross-section control points along trajectory
swept_spline_cps = []
for i, par_val in enumerate(par_value):
temp_csp = []
# evaluate trajectory if user wants to set cross-section on trajectory.
if set_on_trajectory:
evals = trajectory.evaluate([par_val]).ravel()
# place every control point of cross-section separately
for cscp in cross_section.control_points:
# rotate cross-section in trajectory direction
normal_cscp = _np.matmul(R, cscp)
# transform cross-section to global coordinates
normal_cscp = _np.matmul(A[i], normal_cscp)
# check if user wants to place cross-section at
# evaluation points or control points of trajectory
if set_on_trajectory:
# translate cross-section to trajectory evaluation point
normal_cscp += evals
else:
# translate cross-section to trajectory control point
normal_cscp += trajectory.control_points[i]
# append control point to list
temp_csp.append(normal_cscp)

# collect all control points
swept_spline_cps.append(_np.array(temp_csp))

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sweeping process is implemented correctly!

The sweeping process section correctly positions and orients the cross-section along the trajectory. The translation of the cross-section to the origin ensures that the rotation and transformation are applied correctly. The use of the rotation matrix and transformation matrices ensures that the cross-section follows the trajectory smoothly.

To improve readability, you can extract the translation of the cross-section to the origin into a separate function:

def translate_to_origin(cross_section):
    cross_para_center = _np.mean(cross_section.parametric_bounds, axis=0)
    cs_center = cross_section.evaluate(cross_para_center.reshape(-1, cross_section.para_dim)).ravel()
    cross_section.control_points -= cs_center
    return cross_section

cross_section = translate_to_origin(cross_section)

Comment on lines +301 to +593
# calculate angle of cross-section normal vector around e2-axis
angle_of_cs_normal_1 = _np.arctan2(
cross_section_normal[2], cross_section_normal[0]
)

# calculate angle of cross-section normal vector around e3-axis
angle_of_cs_normal_2 = _np.arctan2(
cross_section_normal[1], cross_section_normal[2]
)

# calculate rotation matrix for cross-section normal vector
R1 = _arr.rotation_matrix_around_axis(
axis=[0, 1, 0], rotation=angle_of_cs_normal_1, degree=False
)
R2 = _arr.rotation_matrix_around_axis(
axis=[0, 0, 1], rotation=angle_of_cs_normal_2, degree=False
)
R = _np.matmul(R2, R1)

# rotate cross-section around trajectory tangent vector (e1) if wanted
if rotation_adaption is not None:
R = _np.matmul(
_arr.rotation_matrix_around_axis(
axis=[1, 0, 0], rotation=rotation_adaption, degree=False
),
R,
)

# remove numerical noise
R = _np.where(_np.abs(R) < _settings.TOLERANCE, 0, R)

### SWEEPING PROCESS ###

# evaluate center of cross-section and translate to origin
cross_para_center = _np.mean(cross_section.parametric_bounds, axis=0)
cs_center = cross_section.evaluate(
cross_para_center.reshape(-1, cross_section.para_dim)
).ravel()
cross_section.control_points -= cs_center

# set cross-section control points along trajectory
swept_spline_cps = []
for i, par_val in enumerate(par_value):
temp_csp = []
# evaluate trajectory if user wants to set cross-section on trajectory.
if set_on_trajectory:
evals = trajectory.evaluate([par_val]).ravel()
# place every control point of cross-section separately
for cscp in cross_section.control_points:
# rotate cross-section in trajectory direction
normal_cscp = _np.matmul(R, cscp)
# transform cross-section to global coordinates
normal_cscp = _np.matmul(A[i], normal_cscp)
# check if user wants to place cross-section at
# evaluation points or control points of trajectory
if set_on_trajectory:
# translate cross-section to trajectory evaluation point
normal_cscp += evals
else:
# translate cross-section to trajectory control point
normal_cscp += trajectory.control_points[i]
# append control point to list
temp_csp.append(normal_cscp)

# collect all control points
swept_spline_cps.append(_np.array(temp_csp))

# create spline dictionary
dict_swept_spline = {
"degrees": [*cross_section.degrees, *trajectory.degrees],
"knot_vectors": [
*cross_section.knot_vectors,
*trajectory.knot_vectors,
],
"control_points": _np.asarray(swept_spline_cps).reshape(
-1, cross_section.dim
),
}

# add weights properly if spline is rational
if cross_section.is_rational or trajectory.is_rational:

def weights(spline):
if spline.is_rational:
return spline.weights
return _np.ones(spline.control_points.shape[0])

trajectory_weights = weights(trajectory)
cross_section_weights = weights(cross_section)
dict_swept_spline["weights"] = _np.outer(
trajectory_weights, cross_section_weights
).reshape(-1, 1)
spline_type = _NURBS
else:
spline_type = _BSpline

# create swept spline
swept_spline = spline_type(**dict_swept_spline)

return swept_spline
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comprehensive implementation of the sweeping process!

The swept function is a well-structured and comprehensive implementation of the sweeping process for creating spline surfaces and solids. It follows the mathematical principles described in the NURBS book and handles various cases, such as closed trajectories and rational splines, to ensure correct results.

The code is modular and can be further improved by refactoring complex sections into smaller, reusable functions. Overall, the swept function is a valuable addition to the splinepy library and provides a powerful tool for creating swept surfaces and solids.

To further enhance the code, consider refactoring the complex sections, such as the transformation matrices calculation and the sweeping process, into separate functions. This will improve readability, maintainability, and reusability of the code.

Comment on lines +486 to +523
### ROTATION MATRIX ###
# rotates cross-section normal vector into global e1 direction

# skip calculation if cross-section normal vector is default
if _np.array_equal(cross_section_normal, _np.array([0, 0, 1])):
R = _np.array([[0.0, 0.0, 1.0], [0.0, 1.0, 0.0], [-1.0, 0.0, 0.0]])
# else, calculate rotation matrix for given cross-section normal vector
else:
# calculate angle of cross-section normal vector around e2-axis
angle_of_cs_normal_1 = _np.arctan2(
cross_section_normal[2], cross_section_normal[0]
)

# calculate angle of cross-section normal vector around e3-axis
angle_of_cs_normal_2 = _np.arctan2(
cross_section_normal[1], cross_section_normal[2]
)

# calculate rotation matrix for cross-section normal vector
R1 = _arr.rotation_matrix_around_axis(
axis=[0, 1, 0], rotation=angle_of_cs_normal_1, degree=False
)
R2 = _arr.rotation_matrix_around_axis(
axis=[0, 0, 1], rotation=angle_of_cs_normal_2, degree=False
)
R = _np.matmul(R2, R1)

# rotate cross-section around trajectory tangent vector (e1) if wanted
if rotation_adaption is not None:
R = _np.matmul(
_arr.rotation_matrix_around_axis(
axis=[1, 0, 0], rotation=rotation_adaption, degree=False
),
R,
)

# remove numerical noise
R = _np.where(_np.abs(R) < _settings.TOLERANCE, 0, R)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rotation matrix calculation is correct!

The rotation matrix calculation section correctly computes the rotation matrix for the cross-section normal vector. The handling of the default case simplifies the calculation, and the use of the rotation_matrix_around_axis function ensures that the rotation is performed correctly.

As a small optimization, you can simplify the default case by directly assigning the rotation matrix instead of using _np.array:

- R = _np.array([[0.0, 0.0, 1.0], [0.0, 1.0, 0.0], [-1.0, 0.0, 0.0]])
+ R = _np.eye(3)
+ R[0, 2], R[2, 0] = 1, -1
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
### ROTATION MATRIX ###
# rotates cross-section normal vector into global e1 direction
# skip calculation if cross-section normal vector is default
if _np.array_equal(cross_section_normal, _np.array([0, 0, 1])):
R = _np.array([[0.0, 0.0, 1.0], [0.0, 1.0, 0.0], [-1.0, 0.0, 0.0]])
# else, calculate rotation matrix for given cross-section normal vector
else:
# calculate angle of cross-section normal vector around e2-axis
angle_of_cs_normal_1 = _np.arctan2(
cross_section_normal[2], cross_section_normal[0]
)
# calculate angle of cross-section normal vector around e3-axis
angle_of_cs_normal_2 = _np.arctan2(
cross_section_normal[1], cross_section_normal[2]
)
# calculate rotation matrix for cross-section normal vector
R1 = _arr.rotation_matrix_around_axis(
axis=[0, 1, 0], rotation=angle_of_cs_normal_1, degree=False
)
R2 = _arr.rotation_matrix_around_axis(
axis=[0, 0, 1], rotation=angle_of_cs_normal_2, degree=False
)
R = _np.matmul(R2, R1)
# rotate cross-section around trajectory tangent vector (e1) if wanted
if rotation_adaption is not None:
R = _np.matmul(
_arr.rotation_matrix_around_axis(
axis=[1, 0, 0], rotation=rotation_adaption, degree=False
),
R,
)
# remove numerical noise
R = _np.where(_np.abs(R) < _settings.TOLERANCE, 0, R)
### ROTATION MATRIX ###
# rotates cross-section normal vector into global e1 direction
# skip calculation if cross-section normal vector is default
if _np.array_equal(cross_section_normal, _np.array([0, 0, 1])):
R = _np.eye(3)
R[0, 2], R[2, 0] = 1, -1
# else, calculate rotation matrix for given cross-section normal vector
else:
# calculate angle of cross-section normal vector around e2-axis
angle_of_cs_normal_1 = _np.arctan2(
cross_section_normal[2], cross_section_normal[0]
)
# calculate angle of cross-section normal vector around e3-axis
angle_of_cs_normal_2 = _np.arctan2(
cross_section_normal[1], cross_section_normal[2]
)
# calculate rotation matrix for cross-section normal vector
R1 = _arr.rotation_matrix_around_axis(
axis=[0, 1, 0], rotation=angle_of_cs_normal_1, degree=False
)
R2 = _arr.rotation_matrix_around_axis(
axis=[0, 0, 1], rotation=angle_of_cs_normal_2, degree=False
)
R = _np.matmul(R2, R1)
# rotate cross-section around trajectory tangent vector (e1) if wanted
if rotation_adaption is not None:
R = _np.matmul(
_arr.rotation_matrix_around_axis(
axis=[1, 0, 0], rotation=rotation_adaption, degree=False
),
R,
)
# remove numerical noise
R = _np.where(_np.abs(R) < _settings.TOLERANCE, 0, R)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants