-
Notifications
You must be signed in to change notification settings - Fork 233
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4791 from nortikin/fillet_curve_node
"Fillet curve" node
- Loading branch information
Showing
12 changed files
with
737 additions
and
122 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
Fillet Curve | ||
============ | ||
|
||
Functionality | ||
------------- | ||
|
||
This node takes a NURBS (or NURBS-like) Curve object, finds it's "fracture" | ||
points (i.e. points where tangent of the curve does not change continuously), | ||
and makes a smooth fillet in such points. | ||
|
||
For polyline curves, it is possible to make a fillet made of circular arc, and | ||
it is possible to make the arc of user-provided radius. | ||
For other types of curves, it is not possible to automatically calculate | ||
circular fillet based on radius. So, for other types of curves, this node makes | ||
fillets by use of Bezier curves or biarcs. Points where original curve is glued | ||
with the fillet curve are in such case specified in terms of curve's T | ||
parameter space, instead of fillet radius. | ||
|
||
More specifically, what this node does is follows: | ||
|
||
* If it is known that the curve is a polyline: replace all corners of the | ||
polyline with a circular arc of a specified radius. | ||
* For other types of NURBS curves, | ||
|
||
* Find fracture points of the curve. | ||
* Split the curve into segments at these fracture points. | ||
* Of each segment, cut a small (or not small) piece at each end, based on | ||
**CutOffset** input and segment's T parameter span. For example, if | ||
CutOffset is 0.05, and segment's T parameter span is 1.0 - 2.0, then this | ||
will cut the segment at points 1.05 and 1.95, leaving only span of 1.05 - | ||
1.95. So at this step, there will be gaps between segments. | ||
* Place a fillet curve (Bezier curve or BiArc) at each gap. | ||
|
||
This node will automatically detect if the input curve is closed, and, if | ||
necessary, add a fillet at closing point. | ||
|
||
You can also want to take a look at **Blend Curves** node. | ||
|
||
Inputs | ||
------ | ||
|
||
This node has the following inputs: | ||
|
||
* **Curve**. The curve to make fillets for. This input is mandatory. | ||
* **Radius**. This input is only available when **Polylines only** parameter is | ||
checked. This specifies the fillet radius. It is possible to provide a | ||
separate fillet radius for each vertex of the polyline curve. The default | ||
value is 0.2. | ||
* **CutOffset**. This input is only available when **Polylines only** parameter | ||
is not checked. This specifies the proportion of curve segment T parameter | ||
span, which will be cut off of each segment in order to make a place for the | ||
fillet curve. The default value is 0.05. | ||
* **BulgeFactor**. This input is only available when **Polylines only** | ||
parameter is not checked, and **Continuity** parameter is set to **1 - | ||
Tangency**. This defines the strength with which the tangent vector of the | ||
second curve at it's starting point will affect the generated blending curve. | ||
The default value is 0.5. | ||
|
||
Parameters | ||
---------- | ||
|
||
This node has the following parameters: | ||
|
||
* **Polylines only**. If this is checked, the curve will process only | ||
polylines. If you feed it with any other curve, the node will fail. In this | ||
mode, it is possible to make fillets in form of circular arc, and provide the | ||
fillet radius. If this is not checked, the node will process any NURBS or | ||
NURBS-like curve, but it will not be able to make circular arc fillets based | ||
on fillet radius. | ||
* **Continuity**. This defines the order of continuity of the resulting curve, | ||
and the algorithm used to calculate the fillet curves. The available options are: | ||
|
||
* **0 - Position**. This will connect curve segments with a straight line | ||
segment. So, effectively, this does a bevel instead of smooth fillet. | ||
* **1 - Tangency**. The fillet curves are generated so that the tangent of | ||
the curves are equal at points where fillet curves are joined with | ||
original curve segments. The generated fillet curves are cubic Bezier | ||
curves. | ||
* **1 - BiArc**. This option is not available when **Polylines only** | ||
parameter is checked. The fillet curves are generated as biarc_ curves, | ||
i.e. pairs of circular arcs; they are generated so that the tanent | ||
vectors of the segments are equal at their meeting points. Biarc parameter | ||
will be set to 1.0. Note that this option works only when tangents of the | ||
curve at points where it is replaced with fillet are coplanar. I.e., this | ||
will work fine for planar curves, but may fail in other cases. | ||
* **1 - Circular Arc**. This option is only available when **Polylines | ||
only** parameter is not checked. Fillet curves are calculated as circular | ||
arc of radiuses provided in the **Radius** input. | ||
|
||
The default value is **0 - Position**. | ||
|
||
* **Concatenate**. If checked, then the node will output all segments of | ||
initial curve together with generated fillet curves, concatenated into one | ||
curve. Otherwise, original curve segments and fillet curves will be output | ||
as separate Curve objects. Checked by default. | ||
* **Even domains**. This parameter is available in the N panel only. If | ||
checked, give each segment a domain of length 1. This parameter is only | ||
available if **Concatenate** parameter is checked. Unchecked by default. | ||
|
||
.. _biarc: https://en.wikipedia.org/wiki/Biarc | ||
|
||
Outputs | ||
------- | ||
|
||
This node has the following outputs: | ||
|
||
* **Curve**. Generated Curve objects. | ||
* **Centers**. This output is only available when **Polylines only** parameter | ||
is checked, and **Continuity** parameter is set to **1 - Circular Arc**. | ||
Centers of circles used to make fillet arcs. These are matrices, since this | ||
output provides not only centers, but also orientation of the circles. | ||
|
||
Examples of Usage | ||
----------------- | ||
|
||
Make fillets on some curve: | ||
|
||
.. image:: https://user-images.githubusercontent.com/284644/205504044-bdaa43c8-f13f-4ff4-92f4-aca8100c989b.png | ||
|
||
Make circular arc fillets on a polyline: | ||
|
||
.. image:: https://user-images.githubusercontent.com/284644/205504045-aab871b9-c851-484c-a908-230cd463e060.png | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
# This file is part of project Sverchok. It's copyrighted by the contributors | ||
# recorded in the version control history of the file, available from | ||
# its original location https://github.com/nortikin/sverchok/commit/master | ||
# | ||
# SPDX-License-Identifier: GPL3 | ||
# License-Filename: LICENSE | ||
|
||
import numpy as np | ||
|
||
import bpy | ||
from bpy.props import FloatProperty, EnumProperty, BoolProperty, IntProperty | ||
from mathutils import Vector | ||
|
||
from sverchok.node_tree import SverchCustomTreeNode | ||
from sverchok.data_structure import updateNode, zip_long_repeat, repeat_last_for_length, ensure_nesting_level, get_data_nesting_level | ||
from sverchok.utils.curve.core import SvCurve | ||
from sverchok.utils.curve.nurbs import SvNurbsCurve | ||
from sverchok.utils.curve.fillet import ( | ||
SMOOTH_POSITION, SMOOTH_TANGENT, SMOOTH_ARC, SMOOTH_BIARC, SMOOTH_QUAD, SMOOTH_NORMAL, SMOOTH_CURVATURE, | ||
fillet_polyline_from_curve, fillet_nurbs_curve | ||
) | ||
from sverchok.utils.handle_blender_data import keep_enum_reference | ||
|
||
class SvFilletCurveNode(SverchCustomTreeNode, bpy.types.Node): | ||
""" | ||
Triggers: Fillet Curve Bevel | ||
Tooltip: Smooth a NURBS curve (or polyline) by adding fillets or bevels in it's fracture points. | ||
""" | ||
bl_idname = 'SvFilletCurveNode' | ||
bl_label = 'Fillet Curve' | ||
bl_icon = 'OUTLINER_OB_EMPTY' | ||
sv_icon = 'SV_FILLET_POLYLINE' | ||
|
||
radius : FloatProperty( | ||
name = "Radius", | ||
min = 0.0, | ||
default = 0.2, | ||
update = updateNode) | ||
|
||
cut_offset : FloatProperty( | ||
name = "Cut Offset", | ||
default = 0.05, | ||
min = 0.0, | ||
update = updateNode) | ||
|
||
bulge_factor : FloatProperty( | ||
name = "Bulge Factor", | ||
default = 0.5, | ||
min = 0.0, | ||
update = updateNode) | ||
|
||
concat : BoolProperty( | ||
name = "Concatenate", | ||
default = True, | ||
update = updateNode) | ||
|
||
scale_to_unit : BoolProperty( | ||
name = "Even domains", | ||
description = "Give each segment and each arc equal T parameter domain of [0; 1]", | ||
default = False, | ||
update = updateNode) | ||
|
||
@keep_enum_reference | ||
def get_smooth_modes(self, context): | ||
items = [] | ||
items.append((SMOOTH_POSITION, "0 - Position", "Connect segments with straight line segment", 0)) | ||
items.append((SMOOTH_TANGENT, "1 - Tangency", "Connect segments such that their tangents are smoothly joined", 1)) | ||
if not self.is_polyline: | ||
items.append((SMOOTH_BIARC, "1 - Bi Arc", "Connect segments with Bi Arc, such that tangents are smoothly joined", 2)) | ||
#items.append((SMOOTH_NORMAL, "2 - Normals", "Connect segments such that their normals (second derivatives) are smoothly joined", 3)) | ||
#items.append((SMOOTH_CURVATURE, "3 - Curvature", "Connect segments such that their curvatures (third derivatives) are smoothly joined", 4)) | ||
else: | ||
items.append((SMOOTH_ARC, "1 - Circular Arc", "Connect segments with circular arcs", 5)) | ||
return items | ||
|
||
def update_sockets(self, context): | ||
self.inputs['Radius'].hide_safe = not self.is_polyline | ||
self.inputs['CutOffset'].hide_safe = self.is_polyline | ||
self.inputs['BulgeFactor'].hide_safe = self.is_polyline or self.smooth_mode != SMOOTH_TANGENT | ||
self.outputs['Centers'].hide_safe = not (self.is_polyline and self.smooth_mode == SMOOTH_ARC) | ||
self.outputs['Radius'].hide_safe = not (self.is_polyline and self.smooth_mode == SMOOTH_ARC) | ||
updateNode(self, context) | ||
|
||
smooth_mode : EnumProperty( | ||
name = "Continuity", | ||
description = "How smooth should be the curve at points where original curve is replaced with fillet arcs; bigger value give more smooth touching", | ||
items = get_smooth_modes, | ||
update = update_sockets) | ||
|
||
is_polyline : BoolProperty( | ||
name = "Polylines only", | ||
description = "If checked, the node will work with polylines only, but `Circular Arc' option will be available", | ||
default = False, | ||
update = update_sockets) | ||
|
||
def draw_buttons(self, context, layout): | ||
layout.prop(self, 'is_polyline') | ||
layout.label(text="Continuity") | ||
layout.prop(self, 'smooth_mode', text='') | ||
layout.prop(self, "concat") | ||
|
||
def draw_buttons_ext(self, context, layout): | ||
self.draw_buttons(context, layout) | ||
if self.concat: | ||
layout.prop(self, "scale_to_unit") | ||
|
||
def sv_init(self, context): | ||
self.inputs.new('SvCurveSocket', "Curve") | ||
self.inputs.new('SvStringsSocket', "Radius").prop_name = 'radius' | ||
self.inputs.new('SvStringsSocket', "CutOffset").prop_name = 'cut_offset' | ||
self.inputs.new('SvStringsSocket', "BulgeFactor").prop_name = 'bulge_factor' | ||
self.outputs.new('SvCurveSocket', "Curve") | ||
self.outputs.new('SvMatrixSocket', "Centers") | ||
self.outputs.new('SvStringsSocket', "Radius") | ||
self.update_sockets(context) | ||
|
||
def process(self): | ||
if not any(socket.is_linked for socket in self.outputs): | ||
return | ||
|
||
curves_s = self.inputs['Curve'].sv_get() | ||
radius_s = self.inputs['Radius'].sv_get() | ||
cut_offset_s = self.inputs['CutOffset'].sv_get() | ||
bulge_factor_s = self.inputs['BulgeFactor'].sv_get() | ||
|
||
input_level = get_data_nesting_level(curves_s, data_types=(SvCurve,)) | ||
nested_output = input_level > 1 | ||
|
||
curves_s = ensure_nesting_level(curves_s, 2, data_types=(SvCurve,)) | ||
if self.is_polyline: | ||
radius_s = ensure_nesting_level(radius_s, 3) | ||
else: | ||
radius_s = ensure_nesting_level(radius_s, 2) | ||
cut_offset_s = ensure_nesting_level(cut_offset_s, 2) | ||
bulge_factor_s = ensure_nesting_level(bulge_factor_s, 2) | ||
|
||
curves_out = [] | ||
centers_out = [] | ||
radius_out = [] | ||
for params in zip_long_repeat(curves_s, radius_s, cut_offset_s, bulge_factor_s): | ||
new_curves = [] | ||
new_centers = [] | ||
new_radiuses = [] | ||
for curve, radiuses, cut_offset, bulge_factor in zip_long_repeat(*params): | ||
curve = SvNurbsCurve.to_nurbs(curve) | ||
if curve is None: | ||
raise Exception("One of curves is not a NURBS") | ||
if self.is_polyline: | ||
curve, centers, radiuses = fillet_polyline_from_curve(curve, radiuses, | ||
smooth = self.smooth_mode, | ||
concat = self.concat, | ||
scale_to_unit = self.scale_to_unit) | ||
new_centers.append(centers) | ||
new_curves.append(curve) | ||
new_radiuses.append(radiuses) | ||
else: | ||
curve = fillet_nurbs_curve(curve, | ||
smooth = self.smooth_mode, | ||
cut_offset = cut_offset, | ||
bulge_factor = bulge_factor) | ||
new_curves.append(curve) | ||
if nested_output: | ||
curves_out.append(new_curves) | ||
centers_out.append(new_centers) | ||
radius_out.append(new_radiuses) | ||
else: | ||
curves_out.extend(new_curves) | ||
centers_out.extend(new_centers) | ||
radius_out.extend(new_radiuses) | ||
|
||
self.outputs['Curve'].sv_set(curves_out) | ||
self.outputs['Centers'].sv_set(centers_out) | ||
self.outputs['Radius'].sv_set(radius_out) | ||
|
||
def register(): | ||
bpy.utils.register_class(SvFilletCurveNode) | ||
|
||
def unregister(): | ||
bpy.utils.unregister_class(SvFilletCurveNode) | ||
|
Oops, something went wrong.