diff options
Diffstat (limited to 'modules/curve_utils.py')
-rw-r--r-- | modules/curve_utils.py | 994 |
1 files changed, 994 insertions, 0 deletions
diff --git a/modules/curve_utils.py b/modules/curve_utils.py new file mode 100644 index 00000000..529ddd0c --- /dev/null +++ b/modules/curve_utils.py @@ -0,0 +1,994 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# <pep8 compliant> + +import bpy + + +def vis_curve_object(): + scene = bpy.data.scenes[0] # weak! + cu = bpy.data.curves.new(name="Line", type='CURVE') + ob = bpy.data.objects.new(name="Test", object_data=cu) + ob.layers = [True] * 20 + base = scene.objects.link(ob) + return ob + + +def vis_curve_spline(p1, h1, p2, h2): + ob = vis_curve_object() + spline = ob.data.splines.new(type='BEZIER') + spline.bezier_points.add(1) + spline.bezier_points[0].co = p1.to_3d() + spline.bezier_points[1].co = p2.to_3d() + + spline.bezier_points[0].handle_right = h1.to_3d() + spline.bezier_points[1].handle_left = h2.to_3d() + + +def vis_circle_object(co, rad=1.0): + import math + scene = bpy.data.scenes[0] # weak! + ob = bpy.data.objects.new(name="Circle", object_data=None) + ob.rotation_euler.x = math.pi / 2 + ob.location = co.to_3d() + ob.empty_draw_size = rad + ob.layers = [True] * 20 + base = scene.objects.link(ob) + return ob + + +def visualize_line(p1, p2, p3=None, rad=None): + pair = p1.to_3d(), p2.to_3d() + + ob = vis_curve_object() + spline = ob.data.splines.new(type='POLY') + spline.points.add(1) + for co, v in zip((pair), spline.points): + v.co.xyz = co + + if p3: + spline = ob.data.splines.new(type='POLY') + spline.points[0].co.xyz = p3.to_3d() + if rad is not None: + vis_circle_object(p3, rad) + + +def treat_points(points, + double_limit=0.0001, + ): + + # first remove doubles + tot_len = 0.0 + if double_limit != 0.0: + i = len(points) - 1 + while i > 0: + length = (points[i] - points[i - 1]).length + if length < double_limit: + del points[i] + if i >= len(points): + i -= 1 + else: + tot_len += length + i -= 1 + return tot_len + + +def solve_curvature(p1, p2, n1, n2, fac, fallback): + """ Add a nice circular curvature on + """ + from mathutils.geometry import (intersect_line_line, + ) + + p1_a = p1 + n1 + p2_a = p2 - n2 + + isect = intersect_line_line(p1, + p1_a, + p2, + p2_a, + ) + + if isect: + corner = isect[0].lerp(isect[1], 0.5) + else: + corner = None + + if corner: + p1_first_order = p1.lerp(corner, fac) + p2_first_order = corner.lerp(p2, fac) + co = p1_first_order.lerp(p2_first_order, fac) + + return co + else: + # cant interpolate. just return interpolated value + return fallback.copy() # p1.lerp(p2, fac) + + +def points_to_bezier(points_orig, + double_limit=0.0001, + kink_tolerance=0.25, + bezier_tolerance=0.05, # error distance, scale dependant + subdiv=8, + angle_span=0.95, # 1.0 tries to evaluate splines of 180d + ): + + import math + from mathutils import Vector + + class Point(object): + __slots__ = ("co", + "angle", + "no", + "is_joint", + "next", + "prev", + ) + + def __init__(self, co): + self.co = co + self.is_joint = False + + def calc_angle(self): + if self.prev is None or self.next is None: + self.angle = 0.0 + else: + va = self.co - self.prev.co + vb = self.next.co - self.co + self.angle = va.angle(vb, 0.0) + + def angle_diff(self): + """ use for detecting joints, detect difference in angle from + surrounding points. + """ + if self.prev is None or self.next is None: + return 0.0 + else: + if (self.angle > self.prev.angle and + self.angle > self.next.angle): + return abs(self.angle - self.prev.angle) / math.pi + else: + return 0.0 + + def calc_normal(self): + v1 = v2 = None + if self.prev and not self.prev.is_joint: + v1 = (self.co - self.prev.co).normalized() + if self.next and not self.next.is_joint: + v2 = (self.next.co - self.co).normalized() + + if v1 and v2: + self.no = (v1 + v2).normalized() + elif v1: + self.no = v1 + elif v2: + self.no = v2 + else: + print("Warning, assigning dummy normal") + self.no = Vector((0.0, 1, 0.0)) + + class Spline(object): + __slots__ = ("points", + "handle_left", + "handle_right", + "next", + "prev", + ) + + def __init__(self, points): + self.points = points + + def link_points(self): + + if hasattr(self.points[0], "prev"): + raise Exception("already linked") + + p_prev = None + for p in self.points: + p.prev = p_prev + p_prev = p + + p_prev = None + for p in reversed(self.points): + p.next = p_prev + p_prev = p + + def split(self, i, is_joint=False): + prev = self.prev + next = self.next + + if is_joint: + self.points[i].is_joint = True + + # share a point + spline_a = Spline(self.points[:i + 1]) + spline_b = Spline(self.points[i:]) + + # invalidate self, dont reuse! + self.points = None + + spline_a.next = spline_b + spline_b.prev = spline_a + + spline_a.prev = prev + spline_b.next = next + if prev: + prev.next = spline_a + if next: + next.prev = spline_b + + return spline_a, spline_b + + def calc_angle(self): + for p in self.points: + p.calc_angle() + + def calc_normal(self): + for p in self.points: + p.calc_normal() + + def calc_all(self): + self.link_points() + self.calc_angle() + self.calc_normal() + + #~ def total_angle(self): + #~ return abs(sum((p.angle for p in self.points))) + + def redistribute(self, segment_length, smooth=False): + if len(self.points) == 1: + return + + from mathutils.geometry import intersect_line_sphere + + p_line = p = self.points[0] + points = [(p.co.copy(), p.co.copy())] + p = p.next + + def point_add(co, p=None): + co = co.copy() + co_smooth = co.copy() + + if smooth: + if p is None: + pass # works ok but no smoothing + elif (p.prev.no - p.no).length < 0.001: + pass # normals are too similar, paralelle + elif (p.angle > 0.0) != (p.prev.angle > 0.0): + pass + else: + # visualize_line(p.co, p.co + p.no) + + # this assumes co is on the line + fac = ((p.prev.co - co).length / + (p.prev.co - p.co).length) + + assert(fac >= 0.0 and fac <= 1.0) + + co_smooth = solve_curvature(p.prev.co, + p.co, + p.prev.no, + p.no, + fac, + co, + ) + + points.append((co, co_smooth)) + + def point_step(p): + if p.is_joint or p.next is None: + point_add(p.co) + return None + else: + return p.next + + print("START") + while p: + # we want the first pont past the segment size + + #if p.is_joint: + # vis_circle_object(p.co) + + length = (points[-1][0] - p.co).length + + if abs(length - segment_length) < 0.00001: + # close enough to be considered on the circle bounds + point_add(p.co) + p_line = p + p = point_step(p) + elif length < segment_length: + p = point_step(p) + else: + # the point is further then the segment width + p_start = points[-1][0] if p.prev is p_line else p.prev.co + + if (p_start - points[-1][0]).length > segment_length: + raise Exception("eek2") + if (p.co - points[-1][0]).length < segment_length: + raise Exception("eek3") + + # print(p_start, p.co, points[-1][0], segment_length) + i1, i2 = intersect_line_sphere(p_start, + p.co, + points[-1][0], + segment_length, + ) + # print() + # print(i1, i2) + # assert(i1 is not None) + if i1 is not None: + point_add(i1, p) + p_line = p.prev + elif i2: + raise Exception("err") + + elif i1 is None and i2 is None: + visualize_line(p_start, + p.co, + points[-1][0], + segment_length, + ) + + # XXX FIXME + # raise Exception("BAD!s") + point_add(p.co) + p_line = p + p = point_step(p) + + joint = self.points[0].is_joint, self.points[-1].is_joint + + self.points = [Point(p[1]) for p in points] + + self.points[0].is_joint, self.points[-1].is_joint = joint + + self.calc_all() + # raise Exception("END") + + def intersect_line(self, l1, l2, reverse=False): + """ Spectial kind of intersection, works in 3d on the plane + defimed by the points normal and the line. + """ + + from mathutils.geometry import (intersect_point_line, + ) + + if reverse: + p_first = self.points[-1] + no = -self.points[-1].no + point_iter = reversed(self.points[:-1]) + else: + p_first = self.points[0] + no = self.points[0].no + point_iter = self.points[1:] + + # calculate the line right angles to the line + bi_no = (no - no.project(l2 - l1)).normalized() + + bi_l1 = p_first.co + bi_l2 = p_first.co + bi_no + + for p_apex in point_iter: + ix, fac = intersect_point_line(p_apex.co, bi_l1, bi_l2) + + if fac < 0.0001: + + if reverse: + p_apex_other = p_apex.next + else: + p_apex_other = p_apex.prev + + # find the exact point on the line between the apex and + # the middle + p_test_1 = intersect_point_line(p_apex.co, + l1, + l2)[0] + p_test_2 = intersect_point_line(p_apex_other.co, + l1, + l2)[0] + + w1 = (p_test_1 - p_apex.co).length + w2 = (p_test_2 - p_apex_other.co).length + + #assert(w1 + w2 != 0) + try: + fac = w1 / (w1 + w2) + except ZeroDivisionError: + fac = 0.5 + assert(fac >= 0.0 and fac <= 1.0) + + p_apex_co = p_apex.co.lerp(p_apex_other.co, fac) + p_apex_no = p_apex.no.lerp(p_apex_other.no, fac) + p_apex_no.normalize() + + # visualize_line(p_mid.to_3d(), corner.to_3d()) + # visualize_line(p_apex.co.to_3d(), p_apex_co.to_3d()) + + return p_apex_co, p_apex_no, p_apex + + # intersection not found + return None, None, None + + + @staticmethod + def bez_solve(p0, p1, p2, p3, u, v): + ui = 1.0 - u + vi = 1.0 - v + u_p3 = u * u * u + v_p3 = v * v * v + ui_p3 = ui * ui * ui + vi_p3 = vi * vi * vi + + a = 3.0 * ui * ui * u + b = 3.0 * ui * u * u + c = 3.0 * vi * vi * v + d = 3.0 * vi * v * v + det = a * d - b * c + + if det == 0.0: + assert(0) + return 0 + + q1 = p1 - (ui_p3 * p0 + u_p3 * p3) + q2 = p2 - (vi_p3 * p0 + v_p3 * p3) + + return ((d * q1 - b * q2) / det, + (-c * q1 + a * q2) / det + ) + + def bezier_solve__math1(self): + """ Calculate bezier handles, + assume the splines have been broken up. + + http://polymathprogrammer.com/ + """ + + def get(f, min=0.0, max=1.0): + f = (f * (max - min) + min) + return self.points[int((len(self.points) - 1) * f)].co + + + p1 = get(0.0) + p2 = get(1.0) + i1 = get(1/3) + i2 = get(2/3) + + pos = __class__.bez_solve(p1, i1, i2, p2, 1.0 / 3.0, 2.0 / 3.0) + self.handle_left = self.points[0].co + (pos[0] - self.points[0].co) + self.handle_right = self.points[-1].co + (pos[1] - self.points[-1].co) + + def bezier_solve__math2(self): + + def get(f, min=0.0, max=1.0): + f = (f * (max - min) + min) + return self.points[int((len(self.points) - 1) * f)].co + + p1 = get(0.0, min=0.0, max=0.5) + p2 = get(1.0, min=0.0, max=0.5) + i1 = get(1/3, min=0.0, max=0.5) + i2 = get(2/3, min=0.0, max=0.5) + + pos_a = __class__.bez_solve(p1, i1, i2, p2, 1.0 / 3.0, 2.0 / 3.0) + + p1 = get(0.0, min=0.5, max=1.0) + p2 = get(1.0, min=0.5, max=1.0) + i1 = get(1/3, min=0.5, max=1.0) + i2 = get(2/3, min=0.5, max=1.0) + + pos_b = __class__.bez_solve(p1, i1, i2, p2, 1.0 / 3.0, 2.0 / 3.0) + + self.handle_left = self.points[0].co + (pos_a[0] - self.points[0].co) * 2 + self.handle_right = self.points[-1].co + (pos_b[1] - self.points[-1].co) * 2 + + def bezier_solve__inkscape(self): + + # static void + # estimate_bi(Point bezier[4], unsigned const ei, + # Point const data[], double const u[], unsigned const len) + def estimate_bi(bezier, ei, data, u): + + def B0(u): return ( ( 1.0 - u ) * ( 1.0 - u ) * ( 1.0 - u ) ) + def B1(u): return ( 3 * u * ( 1.0 - u ) * ( 1.0 - u ) ) + def B2(u): return ( 3 * u * u * ( 1.0 - u ) ) + def B3(u): return ( u * u * u ) + + # assert( not (1 <= ei and ei <= 2)) + oi = 3 - ei + num = [0.0, 0.0, 0.0] + den = 0.0 + + for i in range(len(data)): + ui = u[i]; + b = [ + B0(ui), + B1(ui), + B2(ui), + B3(ui) + ] + + for d in range(3): + num[d] += (b[ei] * (b[0] * bezier[0][d] + + b[oi] * bezier[oi][d] + + b[3] * bezier[3][d] + + - data[i][d])) + + den -= b[ei] * b[ei] + + if den: + for d in range(3): + bezier[ei][d] = num[d] / den + else: + bezier[ei] = (oi * bezier[0] + ei * bezier[3]) / 3.0 + bezier = [ + self.points[0].co, + self.points[0].co.lerp(self.points[-1].co, 1/3), + self.points[0].co.lerp(self.points[-1].co, 2/3), + self.points[-1].co, + ] + data = [p.co for p in self.points] + u = [i / len(self.points) for i in range(len(self.points))] + estimate_bi(bezier, 1, data, u) + estimate_bi(bezier, 2, data, u) + estimate_bi(bezier, 1, data, u) + estimate_bi(bezier, 2, data, u) + estimate_bi(bezier, 1, data, u) + estimate_bi(bezier, 2, data, u) + estimate_bi(bezier, 1, data, u) + estimate_bi(bezier, 2, data, u) + + self.handle_left = bezier[1] + self.handle_right = bezier[2] + + def bezier_solve_ideasman42(self): + from mathutils.geometry import (intersect_point_line, + intersect_line_line, + ) + + # get a line + p1 = self.points[0] + p2 = self.points[-1] + + # ------ + # take 2 + p_vec = (p2.co - p1.co).normalized() + + # vector between line and point directions + l1_no = (p1.no + p_vec).normalized() + l2_no = ((-p2.no) - p_vec).normalized() + + l1_co = p1.co + l1_no + l2_co = p2.co + l2_no + + # visualize_line(p1.co, l1_co) + # visualize_line(p2.co, l2_co) + + line_ix_p1_co, line_ix_p1_no, line_ix_p1 = \ + self.intersect_line(p1.co, + l1_co, + ) + line_ix_p2_co, line_ix_p2_no, line_ix_p2 = \ + self.intersect_line(p2.co, + l2_co, + reverse=True, + ) + if line_ix_p1_co is None: + line_ix_p1_co, line_ix_p1_no, line_ix_p1 = \ + p1.next.co, p1.next.no, p1.next + if line_ix_p2_co is None: + line_ix_p2_co, line_ix_p2_no, line_ix_p2 = \ + p2.prev.co, p2.prev.no, p2.prev + + # vis_circle_object(line_ix_p1_co) + # vis_circle_object(line_ix_p2_co) + + l1_max = 0.0 + p1_apex_co = None + p = self.points[1] + while p and (not p.is_joint) and p != line_ix_p1: + ix = intersect_point_line(p.co, p1.co, l1_co)[0] + length = (ix - p.co).length + if length > l1_max: + l1_max = length + p1_apex_co = p.co + p = p.next + + l2_max = 0.0 + p2_apex_co = None + p = self.points[-2] + while p and (not p.is_joint) and p != line_ix_p2: + ix = intersect_point_line(p.co, p2.co, l2_co)[0] + length = (ix - p.co).length + if length > l2_max: + l2_max = length + p2_apex_co = p.co + p = p.prev + + if p1_apex_co is None: + p1_apex_co = p1.next.co + if p2_apex_co is None: + p2_apex_co = p2.prev.co + + l1_tan = (p1.no - p1.no.project(l1_no)).normalized() + l2_tan = -(p2.no - p2.no.project(l2_no)).normalized() + + # values are good! + visualize_line(p1.co, p1.co + l1_tan) + visualize_line(p2.co, p2.co + l2_tan) + + visualize_line(p1.co, p1.co + l1_no) + visualize_line(p2.co, p2.co + l2_no) + + # calculate bias based on the position of the other point allong + # the tangent. + + # first need to reflect the second normal for angle comparison + # first fist need the reflection normal + + # angle between - 0 - 1 + from math import pi + no_ref = p_vec.cross(p2.no).cross(p_vec).normalized() + l2_no_ref = p2.no.reflect(no_ref).normalized() + no_angle = p1.no.angle(l2_no_ref) / pi + del no_ref + + # This could be tweaked but seems to work well + + # fac_fac = 1.0 + + print("angle", "%.6f" % no_angle) + + fac_1 = intersect_point_line(p2_apex_co, + p1.co, + p1.co + l1_tan * (p1.co - p1_apex_co).length, + )[1] * (1.0 + no_angle) + fac_2 = intersect_point_line(p1_apex_co, + p2.co, + p2.co + l2_tan * (p2.co - p2_apex_co).length, + )[1] * (1.0 + no_angle) + + h1_fac = abs(fac_1) + h2_fac = abs(fac_2) + + h1 = p1.co + (p1.no * h1_fac) + h2 = p2.co - (p2.no * h2_fac) + + self.handle_left = h1 + self.handle_right = h2 + + ''' + visualize_line(p1.co, p1_apex_co) + visualize_line(p1_apex_co, p2_apex_co) + visualize_line(p2.co, p2_apex_co) + visualize_line(p1.co, p2.co) + ''' + + def bezier_solve(self): + return self.bezier_solve__inkscape() + + def bezier_error(self, error_max=-1.0, test_count=8): + from mathutils.geometry import interpolate_bezier + + test_points = interpolate_bezier(self.points[0].co, + self.handle_left, + self.handle_right, + self.points[-1].co, + test_count, + ) + + from mathutils.geometry import intersect_point_line + + error = 0.0 + + # this is a rough method measuring the error but should be ok + # TODO. dont test against every single point. + for co in test_points: + # initial values + co_best = self.points[0].co + + length_best = (co - co_best).length + for p in self.points[1:]: + # dist to point + length = (co - p.co).length + if length < length_best: + length_best = length + co_best = p.co + + p_ix, fac = intersect_point_line(co, p.co, p.prev.co) + p_ix = p_ix + if fac >= 0.0 and fac <= 1.0: + length = (co - p_ix).length + if length < length_best: + length_best = length + co_best = p_ix + + error += length_best / test_count + + if error_max != -1.0 and error > error_max: + return True + + if error_max != -1.0: + return False + else: + return error + + class Curve(object): + __slots__ = ("splines", + ) + + def __init__(self, splines): + self.splines = splines + + def link_splines(self): + s_prev = None + for s in self.splines: + s.prev = s_prev + s_perv = s + + s_prev = None + for s in reversed(self.splines): + s.next = s_prev + s_perv = s + + def calc_data(self): + for s in self.splines: + s.calc_all() + + self.link_splines() + + def split_func_map_point(self, func, is_joint=False): + """ func takes a point and returns true on split + + return True if any splits are made. + """ + s_index = 0 + s = self.splines[s_index] + while s: + assert(self.splines[s_index] == s) + + for i, p in enumerate(s.points): + + if i == 0 or i >= len(s.points) - 1: + continue + + if func(p): + split_pair = s.split(i, is_joint=is_joint) + # keep list in sync + self.splines[s_index:s_index + 1] = split_pair + + # advance on main while loop + s = split_pair[0] + assert(self.splines[s_index] == s) + break + + s = s.next + s_index += 1 + + def split_func_spline(self, func, is_joint=False, recursive=False): + """ func takes a spline and returns the point index on split or -1 + + return True if any splits are made. + """ + s_index = 0 + s = self.splines[s_index] + while s: + assert(self.splines[s_index] == s) + + i = func(s) + + if i != -1: + split_pair = s.split(i, is_joint=is_joint) + # keep list in sync + self.splines[s_index:s_index + 1] = split_pair + + # advance on main while loop + s = split_pair[0] + assert(self.splines[s_index] == s) + + if recursive: + continue + + s = s.next + s_index += 1 + + def validate(self): + s_prev = None + iii = 0 + for s in self.splines: + assert(s.prev == s_prev) + if s_prev: + assert(s_prev.next == s) + s_prev = s + iii += 1 + + def redistribute(self, segment_length, smooth=False): + for s in self.splines: + s.redistribute(segment_length, smooth) + + def to_blend_data(self): + """ Points to blender data, debugging only + """ + scene = bpy.data.scenes[0] # weak! + for base in scene.object_bases: + base.select = False + cu = bpy.data.curves.new(name="Test", type='CURVE') + for s in self.splines: + spline = cu.splines.new(type='POLY') + spline.points.add(len(s.points) - 1) + for p, v in zip(s.points, spline.points): + v.co.xyz = p.co + + ob = bpy.data.objects.new(name="Test", object_data=cu) + ob.layers = [True] * 20 + base = scene.objects.link(ob) + scene.objects.active = ob + base.select = True + # base.layers = [True] * 20 + print(ob, "Done") + + def to_blend_curve(self, cu=None, cu_matrix=None): + """ return new bezier spline datablock or add to an existing + """ + if not cu: + cu = bpy.data.curves.new(name="Curve", type='CURVE') + + spline = cu.splines.new(type='BEZIER') + spline.bezier_points.add(len(self.splines)) + + s_prev = None + for i, bp in enumerate(spline.bezier_points): + if i < len(self.splines): + s = self.splines[i] + else: + s = None + + if s_prev and s: + pt = s.points[0] + hl = s_prev.handle_right + hr = s.handle_left + elif s: + pt = s.points[0] + hr = s.handle_left + hl = (pt.co + (pt.co - hr)) + elif s_prev: + pt = s_prev.points[-1] + hl = s_prev.handle_right + hr = (pt.co + (pt.co - hl)) + else: + assert(0) + + bp.co.xyz = pt.co + bp.handle_left.xyz = hl + bp.handle_right.xyz = hr + + handle_type = 'FREE' + + if pt.is_joint == False or (s_prev and s) == False: + + # XXX, this should not happen, but since it can + # at least dont allow allignment to break the curve output + if (pt.co - hl).angle(hr - pt.co, 0.0) < 0.1: + + handle_type = 'ALIGNED' + + bp.handle_left_type = bp.handle_right_type = handle_type + s_prev = s + + scene = bpy.data.scenes[0] # weak! + ob = bpy.data.objects.new(name="Test", object_data=cu) + ob.layers = [True] * 20 + base = scene.objects.link(ob) + scene.objects.active = ob + base.select = True + + return cu + + points = list(points_orig) + + # remove doubles + tot_length = treat_points(points) + + # calculate segment spacing + segment_length = (tot_length / len(points)) / subdiv + + curve = Curve([Spline([Point(p) for p in points])]) + + curve.calc_data() + + if kink_tolerance != 0.0: + pass + + curve.split_func_map_point(lambda p: p.angle_diff() > kink_tolerance, + is_joint=True, + ) + + # return + # curve.validate() + + # higher quality but not really needed + ''' + curve.redistribute(segment_length / 4.0, smooth=True) + curve.redistribute(segment_length, smooth=False) + ''' + curve.redistribute(segment_length, smooth=True) + + # debug only! + # to test how good the bezier spline fitting is without corrections + + ''' + for s in curve.splines: + s.bezier_solve() + ''' + + ''' + def angle_point(s): + a = 0.0 + a_best = len(s.points) // 2 + i = 1 + for p in s.points[2:-2]: + if p.angle > a: + a = p.angle + a_best = i + i += 1 + return a_best + ''' + + # or recursively subdivide... + curve.split_func_spline(lambda s: + len(s.points) // 2 # angle_point(s) + if ((s.bezier_solve(), + s.bezier_error(bezier_tolerance))[1] + and (len(s.points))) + else -1, + recursive=True, + ) + + + error = 0.0 + for s in curve.splines: + error += s.bezier_error() + print("%d :: %.6f" % (len(curve.splines), error)) + + # VISUALIZE + # curve.to_blend_data() + curve.to_blend_curve() + + +if __name__ == "__main__": + if 0: + bpy.ops.wm.open_mainfile(filepath="/root/curve_test3.blend") + + for c in "Curve Curve.001 Curve.002 Curve.003 Curve.004 Curve.005".split(): + print("---", c) + ob = bpy.data.objects[c] + points = [p.co.xyz for s in ob.data.splines for p in s.points] + + print("points_to_bezier 1") + points_to_bezier(points) + print("points_to_bezier 2") + else: + bpy.ops.wm.open_mainfile(filepath="/root/curve_test2.blend") + + ob = bpy.data.objects['Curve'] + points = [p.co.xyz for s in ob.data.splines for p in s.points] + + print("points_to_bezier 1") + points_to_bezier(points) + print("points_to_bezier 2") + + bpy.ops.wm.save_as_mainfile(filepath="/root/curve_test_edit.blend", + copy=True) + print("done!") |