Skip to content

Commit

Permalink
Merge pull request #4791 from nortikin/fillet_curve_node
Browse files Browse the repository at this point in the history
"Fillet curve" node
  • Loading branch information
portnov authored Dec 11, 2022
2 parents 1688dc0 + 448f114 commit 305dc4b
Show file tree
Hide file tree
Showing 12 changed files with 737 additions and 122 deletions.
123 changes: 123 additions & 0 deletions docs/nodes/curve/fillet_curve.rst
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

1 change: 1 addition & 0 deletions index.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
- SvExNurbsCurveNode
- SvApproxNurbsCurveMk2Node
- SvExInterpolateNurbsCurveNode
- SvFilletCurveNode
- SvDeconstructCurveNode
- SvNurbsCurveNodesNode
- ---
Expand Down
180 changes: 180 additions & 0 deletions nodes/curve/fillet_curve.py
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)

Loading

0 comments on commit 305dc4b

Please sign in to comment.