diff --git a/index.yaml b/index.yaml index c0b039efb1..1533901db2 100644 --- a/index.yaml +++ b/index.yaml @@ -150,7 +150,7 @@ - SvExCrossCurvePlaneNode - SvExCrossCurveSurfaceNode - --- - - SvAdaptivePlotCurveNode + - SvAdaptivePlotCurveMk2Node - SvExEvalCurveNode - Surfaces: diff --git a/menus/full_by_data_type.yaml b/menus/full_by_data_type.yaml index a0fd8fbdc6..fd7ba1586b 100644 --- a/menus/full_by_data_type.yaml +++ b/menus/full_by_data_type.yaml @@ -331,7 +331,7 @@ - SvExMSquaresOnSurfaceNode - --- - SvAdaptivePlotNurbsCurveNode - - SvAdaptivePlotCurveNode + - SvAdaptivePlotCurveMk2Node - SvExEvalCurveNode - Surfaces: diff --git a/menus/full_nortikin.yaml b/menus/full_nortikin.yaml index 4c7227ab95..466487166a 100644 --- a/menus/full_nortikin.yaml +++ b/menus/full_nortikin.yaml @@ -861,7 +861,7 @@ - X CURVES: - icon_name: OUTLINER_OB_CURVE - SvAdaptivePlotNurbsCurveNode - - SvAdaptivePlotCurveNode + - SvAdaptivePlotCurveMk2Node - SvExEvalCurveNode - X SURFACES: - icon_name: SURFACE_DATA diff --git a/nodes/curve/adaptive_plot_mk2.py b/nodes/curve/adaptive_plot_mk2.py new file mode 100644 index 0000000000..3797c48753 --- /dev/null +++ b/nodes/curve/adaptive_plot_mk2.py @@ -0,0 +1,127 @@ +import bpy +from bpy.props import FloatProperty, EnumProperty, BoolProperty, IntProperty + +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode, zip_long_repeat, ensure_nesting_level +from sverchok.utils.curve import SvCurve +from sverchok.utils.adaptive_curve import populate_curve + +class SvAdaptivePlotCurveMk2Node(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Adaptive Plot Curve + Tooltip: Adaptive Plot Curve + """ + bl_idname = 'SvAdaptivePlotCurveMk2Node' + bl_label = 'Adaptive Plot Curve' + bl_icon = 'CURVE_NCURVE' + + def update_sockets(self, context): + self.inputs['Seed'].hide_safe = not self.random + self.inputs['Resolution'].hide_safe = not (self.by_length or self.by_curvature) + updateNode(self, context) + + count : IntProperty( + name = "Count", + description = "Total number of points", + min = 2, default = 50, + update = updateNode) + + resolution : IntProperty( + name = "Resolution", + description = "Length and curvature calculation resolution", + min = 3, default = 100, + update = updateNode) + + random : BoolProperty( + name = "Random", + description = "Distribute points randomly", + default = False, + update = update_sockets) + + seed : IntProperty( + name = "Seed", + description = "Random Seed value", + default = 0, + update = updateNode) + + by_curvature : BoolProperty( + name = "By Curvature", + description = "Use curve curvature value to distribute additional points on the curve: places with greater curvature value will receive more points", + default = True, + update = update_sockets) + + by_length : BoolProperty( + name = "By Length", + description = "Use segment lengths to distribute additional points on the curve: segments with greater length will receive more points", + default = False, + update = update_sockets) + + curvature_clip : FloatProperty( + name = "Curvature Clip", + min = 0.0, + default = 100.0, + update = updateNode) + + def draw_buttons(self, context, layout): + row = layout.row(align=True) + row.prop(self, 'by_curvature', toggle=True) + row.prop(self, 'by_length', toggle=True) + layout.prop(self, 'random') + + def draw_buttons_ext(self, context, layout): + self.draw_buttons(context, layout) + layout.prop(self, 'curvature_clip') + + def sv_init(self, context): + self.inputs.new('SvCurveSocket', "Curve") + self.inputs.new('SvStringsSocket', "Count").prop_name = 'count' + self.inputs.new('SvStringsSocket', "Resolution").prop_name = 'resolution' + self.inputs.new('SvStringsSocket', "Seed").prop_name = 'seed' + self.outputs.new('SvVerticesSocket', "Vertices") + self.outputs.new('SvStringsSocket', "Edges") + self.outputs.new('SvStringsSocket', "T") + self.update_sockets(context) + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + curve_s = self.inputs['Curve'].sv_get() + curve_s = ensure_nesting_level(curve_s, 2, data_types = (SvCurve,)) + count_s = self.inputs['Count'].sv_get() + resolution_s = self.inputs['Resolution'].sv_get() + seed_s = self.inputs['Seed'].sv_get() + + verts_out = [] + edges_out = [] + ts_out = [] + inputs = zip_long_repeat(curve_s, count_s, resolution_s, seed_s) + for curves, count_i, resolution_i, seed_i in inputs: + objects = zip_long_repeat(curves, count_i, resolution_i, seed_i) + for curve, count, resolution, seed in objects: + if not self.random: + seed = None + new_t = populate_curve(curve, count, + resolution = resolution, + by_length = self.by_length, + by_curvature = self.by_curvature, + curvature_clip = self.curvature_clip, + random = self.random, + seed = seed) + n = len(new_t) + ts_out.append(new_t.tolist()) + new_verts = curve.evaluate_array(new_t).tolist() + verts_out.append(new_verts) + new_edges = [(i,i+1) for i in range(n-1)] + edges_out.append(new_edges) + + self.outputs['Vertices'].sv_set(verts_out) + self.outputs['Edges'].sv_set(edges_out) + self.outputs['T'].sv_set(ts_out) + +def register(): + bpy.utils.register_class(SvAdaptivePlotCurveMk2Node) + +def unregister(): + bpy.utils.unregister_class(SvAdaptivePlotCurveMk2Node) + diff --git a/nodes/curve/adaptive_plot.py b/old_nodes/adaptive_plot.py similarity index 96% rename from nodes/curve/adaptive_plot.py rename to old_nodes/adaptive_plot.py index 8330d3105e..f73dba0ec9 100644 --- a/nodes/curve/adaptive_plot.py +++ b/old_nodes/adaptive_plot.py @@ -4,7 +4,7 @@ from sverchok.node_tree import SverchCustomTreeNode from sverchok.data_structure import updateNode, zip_long_repeat, ensure_nesting_level from sverchok.utils.curve import SvCurve -from sverchok.utils.adaptive_curve import populate_curve, MinMaxPerSegment, TotalCount +from sverchok.utils.adaptive_curve import populate_curve_old, MinMaxPerSegment, TotalCount class SvAdaptivePlotCurveNode(SverchCustomTreeNode, bpy.types.Node): """ @@ -15,6 +15,8 @@ class SvAdaptivePlotCurveNode(SverchCustomTreeNode, bpy.types.Node): bl_label = 'Adaptive Plot Curve' bl_icon = 'CURVE_NCURVE' + replacement_nodes = [('SvAdaptivePlotCurveMk2Node', None, None)] + sample_size : IntProperty( name = "Segments", description = "Number of initial subdivisions", @@ -138,7 +140,7 @@ def process(self): controller = MinMaxPerSegment(min_ppe, max_ppe) else: controller = TotalCount(count) - new_t = populate_curve(curve, samples+1, + new_t = populate_curve_old(curve, samples+1, by_length = self.by_length, by_curvature = self.by_curvature, population_controller = controller, diff --git a/utils/adaptive_curve.py b/utils/adaptive_curve.py index 50bb1dfc38..278195dca7 100644 --- a/utils/adaptive_curve.py +++ b/utils/adaptive_curve.py @@ -11,6 +11,8 @@ from sverchok.utils.sv_logging import sv_logger from sverchok.utils.math import distribute_int +from sverchok.utils.geom import CubicSpline +from sverchok.utils.integrate import TrapezoidIntegral from sverchok.utils.curve import SvCurveLengthSolver @@ -92,7 +94,7 @@ def populate_t_segment(key_ts, target_count): result.update(ts) return np.asarray(list(sorted(result))) -def populate_curve(curve, samples_t, by_length = False, by_curvature = True, population_controller = None, curvature_clip = 100, seed = None): +def populate_curve_old(curve, samples_t, by_length = False, by_curvature = True, population_controller = None, curvature_clip = 100, seed = None): if population_controller is None: population_controller = MinMaxPerSegment(1, 5) @@ -172,3 +174,37 @@ def populate_curve(curve, samples_t, by_length = False, by_curvature = True, pop new_t = np.sort(new_t) return new_t +def populate_curve(curve, n_points, resolution=100, by_length = False, by_curvature = True, curvature_clip=100.0, random=False, seed=None): + t_min, t_max = curve.get_u_bounds() + factors = np.zeros((resolution,)) + ts = np.linspace(t_min, t_max, num=resolution) + if by_length: + lengths = SvCurveLengthSolver(curve).calc_length_segments(ts) + lengths = np.cumsum(np.insert(lengths, 0, 0)) + factors += lengths / lengths[-1] + if by_curvature: + curvatures = curve.curvature_array(ts) + curvatures = np.clip(curvatures, 0.0, curvature_clip) + integral = TrapezoidIntegral(ts, ts, np.sqrt(curvatures)) + #integral = TrapezoidIntegral(ts, ts, curvatures) + integral.calc() + factors += integral.summands + if not by_length and not by_curvature: + factors = np.linspace(0.0, 1.0, num=resolution) + factors /= factors[-1] + cpts = np.zeros((resolution, 3)) + cpts[:,0] = factors + cpts[:,1] = ts + spline = CubicSpline(cpts, metric='X', is_cyclic=False) + if random: + if seed is None: + seed = 12345 + np.random.seed(seed) + factor_values = np.random.uniform(0.0, 1.0, size=n_points) + factor_values = np.append(factor_values, [0.0, 1.0]) + factor_values = np.sort(factor_values) + else: + factor_values = np.linspace(0.0, 1.0, num=n_points) + new_ts = spline.eval(factor_values)[:,1] + return new_ts + diff --git a/utils/curve/algorithms.py b/utils/curve/algorithms.py index 267096ba8c..99bd820211 100644 --- a/utils/curve/algorithms.py +++ b/utils/curve/algorithms.py @@ -1171,6 +1171,7 @@ def __init__(self, curve, resolution, rescale_t = False, rescale_curvature = Fal ts = (ts - t_min) / (t_max - t_min) if rescale_curvature: ys = ys / ys[-1] + self.values = ys zeros = np.zeros(len(ts)) cpts = np.vstack((ts, ys, zeros)).T self.prime_spline = CubicSpline(cpts, tknots = ts, is_cyclic=False)