diff --git a/docs/nodes/viz/viewer_bezier_curve.rst b/docs/nodes/viz/viewer_bezier_curve.rst new file mode 100644 index 0000000000000000000000000000000000000000..deffdd20642ee42154dda3b52391474ccd5b18ca --- /dev/null +++ b/docs/nodes/viz/viewer_bezier_curve.rst @@ -0,0 +1,80 @@ +Bezier Curve Viewer +=================== + +Functionality +------------- + +This node generates Blender Curve objects of Bezier type. Blender's curve +bevelling feature is supported as well. + +This node can be used to bring Sverchok's Bezier, NURBS or NURBS-lie curves +into Blender scene. + +Note that Blender's Bezier curves can have only one possible degree of 3. +However, Sverchok can automatically elevate degree of the curve, so this node +can also take curves of degree 1 or 2 as input. + +Inputs +------ + +This node has the following inputs: + +* **Curve**. Sverchok's Curve object to be generated as Blender object. This + input is available and mandatory only if **Input mode** parameter is set to + **Bezier curve** or **BSpline curve**. +* **ControlPoints**. Control points of curve to be generated. This input is + available and mandatory only when **Input mode** parameter is set to **Curve + control points** or **Segment control points**. +* **Matrix**. Transformation matrix to be applied to the generated object. By + default, identity matrix is used (no transformation). +* **Radius**. Bevel radius. The node can process either single value per curve, + or a specific value per Bezier control point. Default value is 0.0. +* **Tilt**. Tilt value. The node can process either single value per curve, + or a specific value per Bezier control point. Default value is 0.0. +* **BevelObject**. Blender's Curve object to be used as Bevel object. If not + specified and **Bevel Depth** is not zero, then round profile will be used. +* **TaperObject**. Blender's Curve object to be used as Taper object. This + input is optional. + +Parameters +---------- + +This node has the following parameters: + +- **Live** - Processing only happens if *update* is ticked +- **Hide View** - Hides current meshes from view +- **Hide Select** - Disables the ability to select these meshes +- **Hide Render** - Disables the renderability of these meshes +- **Base Name** - Base name for Objects and Meshes made by this node +- **Select** - Select every object in 3dview that was created by this Node +- **Material Select** - Assign materials to Objects made by this node +- **Add material** - It creates new material and assigns to generated objects +- **Bevel depth** - Changes the size of the bevel. To disable bevelling, you + have to set this to 0 and unset **BevelObject** input / parameter. +- **Input mode**. This defines how input curves are provided. The available options are: + + * **Segment control points**. In the **ControlPoints** input, the node will + expect a list of lists, each of which consists of exactly 4 control points + for each Bezier segment. + * **Curve control points**. In the **ControlPoints** input, the node will + expect a single list of all (concatenated) Bezier segments. + * **Bezier curve**. In the **Curve** input, the node will expect a + Sverchok's Bezier Curve object. Such an object can be generated, for + example, by **Bezier Spline** node. + * **BSpline curve**. In the **Curve** input, the node will expect Sverchok's + BSpline curve, i.e. non-rational NURBS curve or NURBS-like curve. + + The default option is **BSpline curve**. + +Outputs +------- + +This node has the following output: + +* **Objects**. Generated Blender's Curve objects. + +Example of usage +---------------- + +.. image:: https://user-images.githubusercontent.com/284644/136706135-6fb22e96-420b-4c03-9e6c-af1e369c76bf.png + diff --git a/docs/nodes/viz/viz_index.rst b/docs/nodes/viz/viz_index.rst index d4dbb302d08d21d17c6efb2d679c45cad184dd4e..98f0fe53a2fb514c32e817998fc37ffe76641723 100644 --- a/docs/nodes/viz/viz_index.rst +++ b/docs/nodes/viz/viz_index.rst @@ -12,6 +12,7 @@ Viz viewer_idx28 polyline_viewer viewer_curves + viewer_bezier_curve viewer_nurbs_curve viewer_nurbs_surface viewer_metaball diff --git a/index.md b/index.md index 2f5b7440ee9ea1731aa4d210fe1c3e5530e82ee3..bc3ee59eeb46ad99eb716c2ef763f904583f3025 100644 --- a/index.md +++ b/index.md @@ -596,6 +596,7 @@ SvTypeViewerNodeV28 SvSkinViewerNodeV28 SvMetaballOutNode + SvBezierCurveOutNode SvNurbsCurveOutNode SvNurbsSurfaceOutNode --- diff --git a/nodes/curve/curve_segment.py b/nodes/curve/curve_segment.py index 03ba5e6bf0bbb7cbf51195d90e95146c6c9b760a..365257b59c0cd7b98f41f75ae83d34cc2db4f6f0 100644 --- a/nodes/curve/curve_segment.py +++ b/nodes/curve/curve_segment.py @@ -34,6 +34,12 @@ class SvCurveSegmentNode(bpy.types.Node, SverchCustomTreeNode): default = False, update = updateNode) + join : BoolProperty( + name = "Join", + description = "Output single flat list of curves", + default = True, + update = updateNode) + def sv_init(self, context): self.inputs.new('SvCurveSocket', "Curve") self.inputs.new('SvStringsSocket', "TMin").prop_name = 't_min' @@ -41,7 +47,8 @@ class SvCurveSegmentNode(bpy.types.Node, SverchCustomTreeNode): self.outputs.new('SvCurveSocket', "Segment") def draw_buttons(self, context, layout): - layout.prop(self, "rescale", toggle=True) + layout.prop(self, "join") + layout.prop(self, "rescale") def process(self): if not any(socket.is_linked for socket in self.outputs): @@ -58,9 +65,14 @@ class SvCurveSegmentNode(bpy.types.Node, SverchCustomTreeNode): curve_out = [] for curves, tmins, tmaxs in zip_long_repeat(curve_s, tmin_s, tmax_s): + new_curves = [] for curve, t_min, t_max in zip_long_repeat(curves, tmins, tmaxs): new_curve = curve_segment(curve, t_min, t_max, self.rescale) - curve_out.append(new_curve) + new_curves.append(new_curve) + if self.join: + curve_out.extend(new_curves) + else: + curve_out.append(new_curves) self.outputs['Segment'].sv_set(curve_out) diff --git a/nodes/viz/viewer_bezier_curve.py b/nodes/viz/viewer_bezier_curve.py new file mode 100644 index 0000000000000000000000000000000000000000..8f1d388853b617d252ddc96d6a9a40dbfd437ae9 --- /dev/null +++ b/nodes/viz/viewer_bezier_curve.py @@ -0,0 +1,335 @@ +# 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 mathutils import Matrix, Vector +from bpy.props import StringProperty, BoolProperty, IntProperty, EnumProperty, FloatProperty + +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import Matrix_generate, match_long_repeat, updateNode, get_data_nesting_level, ensure_nesting_level, describe_data_shape, zip_long_repeat, numpy_full_list +from sverchok.utils.sv_obj_helper import SvObjHelper +from sverchok.utils.curve.core import SvCurve + +def _split_points(vertices_s): + result = [] + for vertices in vertices_s: + out = [] + n = len(vertices) + if n != 4 and n % 4 != 3: + raise Exception("Invalid control points count") + + idx = 3 + points = vertices[:4] + out.append(points) + while idx + 3 < n: + points = vertices[i : i+3] + out.append(points) + i += 3 + result.append(out) + return result + +def _split_nurbs(curves_s): + result = [] + for curve in curves_s: + if curve.get_degree() > 3: + raise Exception("Only curves with degree <= 3 are supported") + if curve.get_degree() < 3: + curve = curve.elevate_degree(target=3) + if not hasattr(curve, 'to_bezier_segments'): + raise Exception("Curve can not be converted to a list of Bezier segments") + segments = curve.to_bezier_segments() + out = [segment.get_control_points().tolist() for segment in segments] + result.append(out) + return result + +def _split_bezier(curves_s): + result = [] + for segments in curves_s: + out = [] + for segment in segments: + if hasattr(segment, 'is_rational') and segment.is_rational(): + raise Exception("Rational curves are not supported") + if segment.get_degree() > 3: + raise Exception("Only curves with degree <= 3 are supported") + if segment.get_degree() < 3: + segment = segment.elevate_degree(target=3) + + pts = segment.get_control_points().tolist() + out.append(pts) + + result.append(out) + return result + +class SvBezierCurveOutNode(bpy.types.Node, SverchCustomTreeNode, SvObjHelper): + """ + Triggers: Output Bezier Curve + Tooltip: Create Blender's Bezier Curve object + """ + + bl_idname = 'SvBezierCurveOutNode' + bl_label = 'Bezier Curve Out' + bl_icon = 'CURVE_NCURVE' + + data_kind: StringProperty(default='CURVE') + + def update_sockets(self, context): + self.inputs['ControlPoints'].hide_safe = self.input_mode not in ['SEGMENT_PTS', 'CURVE_PTS'] + self.inputs['Curve'].hide_safe = self.input_mode not in ['BEZIER', 'NURBS'] + + input_modes = [ + ('SEGMENT_PTS', "Segment control points", "List of 4 control points for each Bezier segment", 0), + ('CURVE_PTS', "Curve control points", "Single list of all (concatenated) splines", 1), + ('BEZIER', "Bezier curve", "List of cubic Bezier Curve objects", 2), + ('NURBS', "BSpline curve", "Non-rational NURBS (BSpline) curve object (made of several Bezier segments)", 3) + ] + + input_mode : EnumProperty( + name = "Input mode", + items = input_modes, + default = 'NURBS', + update = update_sockets) + + bevel_radius : FloatProperty( + name = "Radius", + description = "Bevel radius", + default = 0.0, + update = updateNode) + + tilt : FloatProperty( + name = "Tilt", + default = 0.0, + update = updateNode) + + caps: BoolProperty( + update = updateNode, + description="Seals the ends of a beveled curve") + + bevel_depth: FloatProperty( + name = "Bevel depth", + description = "Changes the size of the bevel", + min = 0.0, + default = 0.2, + update = updateNode) + + taper_radius_modes = [ + ('OVERRIDE', "Override", "Override the radius of the spline point with the taper radius", 0), + ('MULTIPLY', "Multiply", "Multiply the radius of the spline point by the taper radius", 1), + ('ADD', "Add", "Add the radius of the bevel point to the taper radius", 2) + ] + + taper_radius_mode : EnumProperty( + name = "Taper radius mode", + description = "Determine how the effective radius of the spline point is computed when a taper object is specified", + items = taper_radius_modes, + default = 'OVERRIDE', + update = updateNode) + + resolution: IntProperty( + name = "Bevel Resolution", + description = "Alters the smoothness of the bevel", + min = 0, + default = 3, + update = updateNode) + + preview_resolution_u: IntProperty( + name = "Resolution Preview U", + default = 12, + min = 1, + max = 64, + update = updateNode, + description = "The resolution property defines the number of points that are" + " computed between every pair of control points.") + + use_smooth: BoolProperty( + name = "Smooth shading", + update = updateNode, + default = True) + + show_wire: BoolProperty( + name = "Show Wire", + default = False, + update = updateNode) + + def get_curve_name(self, index): + return f'{self.basedata_name}.{index:04d}' + + def create_curve(self, index, matrix=None, bevel=None, taper=None): + object_name = self.get_curve_name(index) + curve_data = bpy.data.curves.new(object_name, 'CURVE') + curve_data.dimensions = '3D' + curve_object = bpy.data.objects.get(object_name) + if not curve_object: + curve_object = self.create_object(object_name, index, curve_data) + + if matrix is not None: + curve_object.matrix_local = matrix + + if bevel is not None: + curve_object.data.bevel_mode = 'OBJECT' + curve_object.data.bevel_object = bevel + else: + curve_object.data.bevel_mode = 'ROUND' + + if taper is not None: + curve_object.data.taper_object = taper + else: + curve_object.data.taper_object = None + + curve_object.data.taper_radius_mode = self.taper_radius_mode + + curve_object.data.bevel_depth = self.bevel_depth + curve_object.data.bevel_resolution = self.resolution + curve_object.data.resolution_u = self.preview_resolution_u + curve_object.data.use_fill_caps = self.caps + curve_object.show_wire = self.show_wire + + if self.material_pointer: + curve_object.data.materials.clear() + curve_object.data.materials.append(self.material_pointer) + + return curve_object + + def create_spline(self, curve_object, control_points, radiuses=None, tilts=None): + curve_object.data.splines.clear() + + spline = curve_object.data.splines.new(type='BEZIER') + spline.bezier_points.add(len(control_points)) + + first_point = start_point = spline.bezier_points[0] + for idx, segment in enumerate(control_points): + end_point = spline.bezier_points[idx+1] + + start_point.co = Vector(segment[0]) + start_point.handle_right = Vector(segment[1]) + + end_point.handle_left = Vector(segment[2]) + end_point.co = Vector(segment[3]) + + start_point = end_point + + first_point.handle_left = first_point.co + end_point.handle_right = end_point.co + + if radiuses is not None: + spline.bezier_points.foreach_set('radius', numpy_full_list(radiuses, len(spline.bezier_points))) + if tilts is not None: + spline.bezier_points.foreach_set('tilt', numpy_full_list(tilts, len(spline.bezier_points))) + + spline.use_smooth = self.use_smooth + + return spline + + def find_curve(self, index): + object_name = self.get_curve_name(index) + return bpy.data.objects.get(object_name) + + def draw_label(self): + return f"Bezier Curve {self.basedata_name}" + + def draw_bevel_object_props(self, socket, context, layout): + row = layout.row(align=True) + if not socket.is_linked: + row.prop_search(socket, 'object_ref_pointer', bpy.data, 'objects', + text=f'{socket.name}. {socket.objects_number if socket.objects_number else ""}') + else: + row.label(text=f'{socket.name}. {socket.objects_number if socket.objects_number else ""}') + row = row.row(align=True) + row.ui_units_x = 0.6 + row.prop(self, 'caps', text='C', toggle=True) + + def draw_taper_object_props(self, socket, context, layout): + row = layout.row(align=True) + if not socket.is_linked: + row.prop_search(socket, 'object_ref_pointer', bpy.data, 'objects', + text=f'{socket.name}. {socket.objects_number if socket.objects_number else ""}') + else: + row.label(text=f'{socket.name}. {socket.objects_number if socket.objects_number else ""}') + + def sv_init(self, context): + self.inputs.new('SvVerticesSocket', 'ControlPoints') + self.inputs.new('SvCurveSocket', 'Curve') + self.inputs.new('SvMatrixSocket', 'Matrix') + self.inputs.new('SvStringsSocket', 'Radius').prop_name = 'bevel_radius' + self.inputs.new('SvStringsSocket', 'Tilt').prop_name = 'tilt' + + obj_socket = self.inputs.new('SvObjectSocket', 'BevelObject') + obj_socket.custom_draw = 'draw_bevel_object_props' + obj_socket.object_kinds = "CURVE" + + obj_socket = self.inputs.new('SvObjectSocket', 'TaperObject') + obj_socket.custom_draw = 'draw_taper_object_props' + obj_socket.object_kinds = "CURVE" + + self.outputs.new('SvObjectSocket', "Objects") + self.update_sockets(context) + + def draw_buttons(self, context, layout): + self.draw_live_and_outliner(context, layout) + self.draw_object_buttons(context, layout) + layout.prop(self, 'bevel_depth') + + layout.label(text="Input mode:") + layout.prop(self, 'input_mode', text='') + + def draw_buttons_ext(self, context, layout): + col = layout.column() + col.prop(self, 'show_wire') + col.prop(self, 'use_smooth') + col.prop(self, "preview_resolution_u") + col.prop(self, 'resolution') + col.prop(self, 'taper_radius_mode') + + def process(self): + if not self.activate: + return + + if not self.inputs['ControlPoints'].is_linked and not self.inputs['Curve'].is_linked: + return + + if self.inputs['ControlPoints'].is_linked: + vertices_s = self.inputs['ControlPoints'].sv_get(default=[[]]) + if self.input_mode == 'SEGMENT_PTS': + vertices_s = ensure_nesting_level(vertices_s, 4) + control_points_s = vertices_s + else: + vertices_s = ensure_nesting_level(vertices_s, 3) + control_points_s = _split_points(vertices_s) + if self.inputs['Curve'].is_linked: + curves_s = self.inputs['Curve'].sv_get(default=[[]]) + if self.input_mode == 'NURBS': + curves_s = ensure_nesting_level(curves_s, 1, data_types=(SvCurve,)) + control_points_s = _split_nurbs(curves_s) + elif self.input_mode == 'BEZIER': + curves_s = ensure_nesting_level(curves_s, 2, data_types=(SvCurve,)) + control_points_s = _split_bezier(curves_s) + + matrix_s = self.inputs['Matrix'].sv_get(deepcopy=False, default=[None]) + radius_s = self.inputs['Radius'].sv_get(deepcopy=False) + tilt_s = self.inputs['Tilt'].sv_get(deepcopy=False) + bevel_s = self.inputs['BevelObject'].sv_get(deepcopy=False, default=[None]) + taper_s = self.inputs['TaperObject'].sv_get(deepcopy=False, default=[None]) + + objects_out = [] + object_index = 0 + for matrix, control_points, radiuses, tilts, bevel, taper in zip_long_repeat(matrix_s, control_points_s, radius_s, tilt_s, bevel_s, taper_s): + object_index += 1 + + curve_object = self.create_curve(object_index, matrix, bevel, taper) + self.debug("Object: %s", curve_object) + if not curve_object: + continue + self.create_spline(curve_object, control_points, radiuses, tilts) + + objects_out.append(curve_object) + + self.outputs['Objects'].sv_set(objects_out) + +classes = [SvBezierCurveOutNode] +register, unregister = bpy.utils.register_classes_factory(classes) + diff --git a/nodes/viz/viewer_nurbs_curve.py b/nodes/viz/viewer_nurbs_curve.py index f736eb616dbd8c64aae3328ac5e18e81780ffdd4..74349ed779c6d5b61191bce3b11572bdb3d8356f 100644 --- a/nodes/viz/viewer_nurbs_curve.py +++ b/nodes/viz/viewer_nurbs_curve.py @@ -6,8 +6,6 @@ # License-Filename: LICENSE import numpy as np -from itertools import zip_longest -import traceback import bpy from mathutils import Matrix, Vector diff --git a/tests/curve_tests.py b/tests/curve_tests.py index 0da9cd7496e43d404413721587f6802c45565288..1aea7e8aa1c0e4cfba0ff38d6956bb821a2b2faa 100644 --- a/tests/curve_tests.py +++ b/tests/curve_tests.py @@ -11,14 +11,14 @@ class TaylorTests(SverchokTestCase): square_coeffs = curve.square().get_coefficients()[:,0] expected_coeffs = np.array([16, 24, 25, 20, 10, 4, 1]) - self.assert_numpy_arrays_equal(square_coeffs, expected_coeffs) + self.assert_numpy_arrays_equal(square_coeffs, expected_coeffs, precision=6) def test_square_2(self): coeffs = np.array([[-2, 1, 0], [4, 0, 0]], dtype=np.float64) curve = SvTaylorCurve.from_coefficients(coeffs) square_coeffs = curve.square().get_coefficients()[:,0] expected_coeffs = np.array([5, -16, 16]) - self.assert_numpy_arrays_equal(square_coeffs, expected_coeffs) + self.assert_numpy_arrays_equal(square_coeffs, expected_coeffs, precision=6) def test_to_nurbs_1(self): coeffs = np.array([[-2, 1, 0], [4, 0, 0]], dtype=np.float64) @@ -27,7 +27,7 @@ class TaylorTests(SverchokTestCase): cpts = curve.to_nurbs().get_control_points() expected_cpts = np.array([[-2, 1, 0], [2, 1, 0]]) - self.assert_numpy_arrays_equal(cpts, expected_cpts) + self.assert_numpy_arrays_equal(cpts, expected_cpts, precision=6) def test_to_nurbs_2(self): coeffs = np.array([[5,0,0], [-16,0,0], [16,0,0]], dtype=np.float64) @@ -36,4 +36,5 @@ class TaylorTests(SverchokTestCase): cpts = curve.to_nurbs().get_control_points() expected_cpts = np.array([[5,0,0], [-3,0,0], [5,0,0]]) - self.assert_numpy_arrays_equal(cpts, expected_cpts) + self.assert_numpy_arrays_equal(cpts, expected_cpts, precision=6) + diff --git a/tests/nurbs_tests.py b/tests/nurbs_tests.py index ece40ce639ed8da2ef95d1431f87270b8506b302..c82e688592b18df8d8e170e0bfb47186a82aae84 100644 --- a/tests/nurbs_tests.py +++ b/tests/nurbs_tests.py @@ -359,22 +359,22 @@ class OtherNurbsTests(SverchokTestCase): self_degree = 1 result = elevate_bezier_degree(self_degree, points) expected = np.array([[0, 0, 0], [0.5, 0, 0], [1, 0, 0]]) - self.assert_numpy_arrays_equal(result, expected, fail_fast=False) + self.assert_numpy_arrays_equal(result, expected, fail_fast=False, precision=8) def test_elevate_bezier_degree_2(self): points = np.array([[0, 0, 0], [1, 0, 0]]) self_degree = 1 result = elevate_bezier_degree(self_degree, points, delta=3) expected = np.array([[0, 0, 0], [0.25, 0, 0], [0.5, 0, 0], [0.75, 0, 0], [1, 0, 0]]) - self.assert_numpy_arrays_equal(result, expected, fail_fast=False) + self.assert_numpy_arrays_equal(result, expected, fail_fast=False, precision=8) def test_from_homogenous(self): points = np.array([[0, 0, 1, 1], [0, 0, 4, 2], [0, 0, 9, 3]]) result, weights = from_homogenous(points) expected_points = np.array([[0, 0, 1], [0, 0, 2], [0, 0, 3]]) expected_weights = np.array([1, 2, 3]) - self.assert_numpy_arrays_equal(weights, expected_weights) - self.assert_numpy_arrays_equal(result, expected_points) + self.assert_numpy_arrays_equal(weights, expected_weights, precision=8) + self.assert_numpy_arrays_equal(result, expected_points, precision=8) def test_insert_1(self): points = np.array([[0, 0, 0], [1, 0, 0]]) @@ -386,7 +386,7 @@ class OtherNurbsTests(SverchokTestCase): ts = np.array([0, 0.25, 0.5, 0.75, 1.0]) expected = np.array([[0,0,0], [0.25,0,0], [0.5,0,0], [0.75,0,0], [1,0,0]]) result = curve.evaluate_array(ts) - self.assert_numpy_arrays_equal(result, expected) + self.assert_numpy_arrays_equal(result, expected, precision=8) def test_insert_2(self): points = np.array([[0, 0, 0], [1, 1, 0], [2, 0, 0]]) @@ -398,7 +398,7 @@ class OtherNurbsTests(SverchokTestCase): ts = np.array([0, 0.25, 0.5, 0.75, 1.0]) expected = curve.evaluate_array(ts) result = inserted.evaluate_array(ts) - self.assert_numpy_arrays_equal(result, expected) + self.assert_numpy_arrays_equal(result, expected, precision=8) def test_insert_3(self): points = np.array([[0, 0, 0], [1, 1, 0], [2,1,0], [3, 0, 0]]) @@ -416,7 +416,7 @@ class OtherNurbsTests(SverchokTestCase): ts = np.array([0, 0.25, 0.5, 0.75, 1.0]) expected = curve.evaluate_array(ts) result = inserted.evaluate_array(ts) - self.assert_numpy_arrays_equal(result, expected) + self.assert_numpy_arrays_equal(result, expected, precision=8) @unittest.skip def test_remove_1(self): @@ -433,16 +433,16 @@ class OtherNurbsTests(SverchokTestCase): knot = 0.5 inserted = curve.insert_knot(knot, 2) self.assertEquals(len(inserted.get_control_points()), len(points)+2) - self.assert_numpy_arrays_equal(inserted.evaluate_array(ts), orig_pts) + self.assert_numpy_arrays_equal(inserted.evaluate_array(ts), orig_pts, precision=8) expected_inserted_kv = np.array([0, 0, 0, 0, 0.5, 0.5, 1, 1, 1, 1]) inserted_kv = inserted.get_knotvector() - self.assert_numpy_arrays_equal(inserted_kv, expected_inserted_kv) + self.assert_numpy_arrays_equal(inserted_kv, expected_inserted_kv, precision=8) removed = inserted.remove_knot(knot, 2) expected_removed_kv = kv - self.assert_numpy_arrays_equal(removed.get_knotvector(), expected_removed_kv) - self.assert_numpy_arrays_equal(removed.evaluate_array(ts), orig_pts) + self.assert_numpy_arrays_equal(removed.get_knotvector(), expected_removed_kv, precision=8) + self.assert_numpy_arrays_equal(removed.evaluate_array(ts), orig_pts, precision=8) def test_remove_2(self): points = np.array([[0, 0, 0], @@ -464,14 +464,14 @@ class OtherNurbsTests(SverchokTestCase): self.assertEquals(len(inserted.get_control_points()), len(points)+1) expected_inserted_kv = np.array([0, 0, 0, 0, 0.1, 0.25, 0.75, 1, 1, 1, 1]) - self.assert_numpy_arrays_equal(inserted.get_knotvector(), expected_inserted_kv) + self.assert_numpy_arrays_equal(inserted.get_knotvector(), expected_inserted_kv, precision=8) inserted_kv = inserted.get_knotvector() k = np.searchsorted(inserted_kv, knot, side='right')-1 s = sv_knotvector.find_multiplicity(inserted_kv, knot) print("K:", k, "S:", s) removed = inserted.remove_knot(knot, 1) - self.assert_numpy_arrays_equal(removed.get_knotvector(), kv) + self.assert_numpy_arrays_equal(removed.get_knotvector(), kv, precision=8) @unittest.skip("Until https://github.com/orbingol/NURBS-Python/issues/135 is resolved") @requires(geomdl) @@ -486,18 +486,18 @@ class OtherNurbsTests(SverchokTestCase): orig_pts = curve.evaluate_array(ts) inserted = curve.insert_knot(0.5, 1) - self.assert_numpy_arrays_equal(inserted.evaluate_array(ts), orig_pts) + self.assert_numpy_arrays_equal(inserted.evaluate_array(ts), orig_pts, precision=8) expected_inserted_kv = np.array([0, 0, 0, 0.5, 1, 1, 1]) - self.assert_numpy_arrays_equal(inserted.get_knotvector(), expected_inserted_kv) + self.assert_numpy_arrays_equal(inserted.get_knotvector(), expected_inserted_kv, precision=8) removed = inserted.remove_knot(0.5, 1) - self.assert_numpy_arrays_equal(removed.get_knotvector(), kv) + self.assert_numpy_arrays_equal(removed.get_knotvector(), kv, precision=8) #self.assert_numpy_arrays_equal(removed.evaluate_array(ts), orig_pts) #print("CP", removed.get_control_points()) #print("W", removed.get_weights()) - self.assert_numpy_arrays_equal(removed.get_control_points(), points) + self.assert_numpy_arrays_equal(removed.get_control_points(), points, precision=8) def test_split_1(self): points = np.array([[0, 0, 0], [1, 0, 0]]) @@ -509,19 +509,19 @@ class OtherNurbsTests(SverchokTestCase): expected_pts1 = np.array([[0, 0, 0], [0.5, 0, 0]]) pts1 = curve1.get_control_points() - self.assert_numpy_arrays_equal(pts1, expected_pts1) + self.assert_numpy_arrays_equal(pts1, expected_pts1, precision=8) expected_pts2 = np.array([[0.5, 0, 0.0], [1, 0, 0]]) pts2 = curve2.get_control_points() - self.assert_numpy_arrays_equal(pts2, expected_pts2) + self.assert_numpy_arrays_equal(pts2, expected_pts2, precision=8) expected_kv1 = np.array([0,0, 0.5,0.5]) kv1 = curve1.get_knotvector() - self.assert_numpy_arrays_equal(kv1, expected_kv1) + self.assert_numpy_arrays_equal(kv1, expected_kv1, precision=8) expected_kv2 = np.array([0.5,0.5, 1,1]) kv2 = curve2.get_knotvector() - self.assert_numpy_arrays_equal(kv2, expected_kv2) + self.assert_numpy_arrays_equal(kv2, expected_kv2, precision=8) #@unittest.skip def test_split_2(self): @@ -534,21 +534,21 @@ class OtherNurbsTests(SverchokTestCase): expected_kv1 = np.array([0,0,0, 0.5,0.5,0.5]) kv1 = curve1.get_knotvector() - self.assert_numpy_arrays_equal(kv1, expected_kv1) + self.assert_numpy_arrays_equal(kv1, expected_kv1, precision=8) expected_kv2 = np.array([0.5,0.5,0.5, 1,1,1]) kv2 = curve2.get_knotvector() - self.assert_numpy_arrays_equal(kv2, expected_kv2) + self.assert_numpy_arrays_equal(kv2, expected_kv2, precision=8) expected_pts1 = np.array([[0, 0, 0], [0.5, 0.5, 0], [1, 0.5, 0]]) pts1 = curve1.get_control_points() #print("Pts1", pts1) - self.assert_numpy_arrays_equal(pts1, expected_pts1) + self.assert_numpy_arrays_equal(pts1, expected_pts1, precision=8) expected_pts2 = np.array([[1, 0.5, 0], [1.5, 0.5, 0], [2, 0, 0]]) pts2 = curve2.get_control_points() #print("Pts2", pts2) - self.assert_numpy_arrays_equal(pts2, expected_pts2) + self.assert_numpy_arrays_equal(pts2, expected_pts2, precision=8) def test_split_3(self): points = np.array([[0, 0, 0], @@ -617,12 +617,12 @@ class OtherNurbsTests(SverchokTestCase): expected_kv1 = np.array([0, 0, 0, 0, 0.25, 0.25, 0.25, 0.25]) expected_kv2 = np.array([0.25, 0.25, 0.25, 0.25, 0.75, 1, 1, 1, 1]) - self.assert_numpy_arrays_equal(curve1.get_knotvector(), expected_kv1) - self.assert_numpy_arrays_equal(curve2.get_knotvector(), expected_kv2) + self.assert_numpy_arrays_equal(curve1.get_knotvector(), expected_kv1, precision=8) + self.assert_numpy_arrays_equal(curve2.get_knotvector(), expected_kv2, precision=8) result = curve1.concatenate(curve2, remove_knots=True) expected_result_kv = knotvector - self.assert_numpy_arrays_equal(result.get_knotvector(), expected_result_kv) + self.assert_numpy_arrays_equal(result.get_knotvector(), expected_result_kv, precision=8) def test_single_1(self): points = np.array([[0, 0, 0], [1, 1, 0], [2, 0, 0]]) @@ -727,19 +727,19 @@ class KnotvectorTests(SverchokTestCase): pairs = [(0, 3), (1, 3)] kv = sv_knotvector.from_multiplicity(pairs) expected = np.array([0, 0, 0, 1, 1, 1]) - self.assert_numpy_arrays_equal(kv, expected) + self.assert_numpy_arrays_equal(kv, expected, precision=8) def test_from_multiplicity_2(self): pairs = [(0, 3), (0.5, 1), (1, 3)] kv = sv_knotvector.from_multiplicity(pairs) expected = np.array([0, 0, 0, 0.5, 1, 1, 1]) - self.assert_numpy_arrays_equal(kv, expected) + self.assert_numpy_arrays_equal(kv, expected, precision=8) def test_elevate_knotvector(self): kv = np.array([0, 0, 0, 1, 1, 1], dtype=np.float64) result = sv_knotvector.elevate_degree(kv) expected = np.array([0, 0, 0, 0, 1, 1, 1, 1]) - self.assert_numpy_arrays_equal(result, expected) + self.assert_numpy_arrays_equal(result, expected, precision=8) def test_diff_1(self): kv1 = np.array([0, 1, 2]) @@ -767,14 +767,14 @@ class KnotvectorTests(SverchokTestCase): kv2 = np.array([0, 1, 2]) result = sv_knotvector.merge(kv1, kv2) expected = np.array([0, 1, 2]) - self.assert_numpy_arrays_equal(result, expected) + self.assert_numpy_arrays_equal(result, expected, precision=8) def test_merge_2(self): kv1 = np.array([0, 0.5, 2]) kv2 = np.array([0, 1.5, 2]) result = sv_knotvector.merge(kv1, kv2) expected = np.array([0, 0.5, 1.5, 2]) - self.assert_numpy_arrays_equal(result, expected) + self.assert_numpy_arrays_equal(result, expected, precision=8) def test_from_tknots(self): tknots = np.array([0, 5, 9, 14, 17.0]) / 17.0 @@ -826,10 +826,10 @@ class TaylorTests(SverchokTestCase): taylor = curve.bezier_to_taylor() coeffs = taylor.get_coefficients() - self.assert_numpy_arrays_equal(coeffs[0], np.array([0,0,0,1])) - self.assert_numpy_arrays_equal(coeffs[1], np.array([3,0,0,0])) - self.assert_numpy_arrays_equal(coeffs[2], np.array([-3,3,0,0])) - self.assert_numpy_arrays_equal(coeffs[3], np.array([2,-2,0,0])) + self.assert_numpy_arrays_equal(coeffs[0], np.array([0,0,0,1]), precision=8) + self.assert_numpy_arrays_equal(coeffs[1], np.array([3,0,0,0]), precision=8) + self.assert_numpy_arrays_equal(coeffs[2], np.array([-3,3,0,0]), precision=8) + self.assert_numpy_arrays_equal(coeffs[3], np.array([2,-2,0,0]), precision=8) def test_bezier_to_taylor_2(self): cpts = np.array([[0,0,0], [1,0,0], [1,1,0], [2,1,0]], dtype=np.float64) @@ -841,7 +841,7 @@ class TaylorTests(SverchokTestCase): taylor = curve.bezier_to_taylor() nurbs = taylor.to_nurbs() - self.assert_numpy_arrays_equal(nurbs.get_control_points(), cpts) + self.assert_numpy_arrays_equal(nurbs.get_control_points(), cpts, precision=8) def test_outside_sphere_1(self): cpts = np.array([[-2, 1, 0], [2, 1, 0]]) diff --git a/utils/curve/bezier.py b/utils/curve/bezier.py index 1ddf3429c5e0b2752ebea5e33dd1b82e6b7ac5e7..e9c34c2b19321ba4e000011f578523cdbd08eb35 100644 --- a/utils/curve/bezier.py +++ b/utils/curve/bezier.py @@ -278,6 +278,9 @@ class SvBezierCurve(SvCurve): def get_degree(self): return self.degree + def is_rational(self): + return False + def get_control_points(self): return self.points @@ -434,6 +437,9 @@ class SvCubicBezierCurve(SvCurve): def get_degree(self): return 3 + def is_rational(self): + return False + def get_control_points(self): return np.array([self.p0, self.p1, self.p2, self.p3])