From d06af2fed07190c6482293542155615c205c0552 Mon Sep 17 00:00:00 2001 From: gandalf3 Date: Fri, 23 Jun 2017 18:53:07 -0700 Subject: [PATCH] Clean up tests No need to test on complete addons. This commit adds a few test cases and some automated test generation. --- tests/test_blenderpack.py | 62 +- .../addons/add_curve_extra_objects.zip | Bin 51971 -> 0 bytes .../add_curve_aceous_galore.py | 1462 -------------- .../add_curve_braid.py | 261 --- .../add_curve_celtic_links.py | 284 --- .../add_curve_curly.py | 491 ----- .../add_curve_simple.py | 1747 ----------------- .../add_curve_spirals.py | 424 ---- .../add_curve_spirofit_bouncespline.py | 1014 ---------- .../add_curve_torus_knots.py | 726 ------- .../add_surface_plane_cone.py | 398 ---- .../beveltaper_curve.py | 422 ---- .../123AA_sort_this_dir_first/__init__.py | 0 .../__init__.py | 0 tests/test_helpers/addons/dir_addon/other.py | 0 tests/test_helpers/addons/invalid_addon.py | 12 + tests/test_helpers/addons/not_an_addon.py | 0 .../{add_curve_ivygen.py => real_addon.py} | 0 tests/test_helpers/addons/repo.json | 70 + tests/test_helpers/addons/zipped_addon.zip | Bin 0 -> 4291 bytes ...ra_objects_blinfo.txt => dir_addon_output} | 0 ...vy_gen_blinfo.txt => real_addon.py_output} | 0 tests/test_helpers/zipped_addon.zip_output | 1 + 23 files changed, 123 insertions(+), 7251 deletions(-) delete mode 100644 tests/test_helpers/addons/add_curve_extra_objects.zip delete mode 100644 tests/test_helpers/addons/add_curve_extra_objects/add_curve_aceous_galore.py delete mode 100644 tests/test_helpers/addons/add_curve_extra_objects/add_curve_braid.py delete mode 100644 tests/test_helpers/addons/add_curve_extra_objects/add_curve_celtic_links.py delete mode 100644 tests/test_helpers/addons/add_curve_extra_objects/add_curve_curly.py delete mode 100644 tests/test_helpers/addons/add_curve_extra_objects/add_curve_simple.py delete mode 100644 tests/test_helpers/addons/add_curve_extra_objects/add_curve_spirals.py delete mode 100644 tests/test_helpers/addons/add_curve_extra_objects/add_curve_spirofit_bouncespline.py delete mode 100644 tests/test_helpers/addons/add_curve_extra_objects/add_curve_torus_knots.py delete mode 100644 tests/test_helpers/addons/add_curve_extra_objects/add_surface_plane_cone.py delete mode 100644 tests/test_helpers/addons/add_curve_extra_objects/beveltaper_curve.py create mode 100644 tests/test_helpers/addons/dir_addon/123AA_sort_this_dir_first/__init__.py rename tests/test_helpers/addons/{add_curve_extra_objects => dir_addon}/__init__.py (100%) create mode 100644 tests/test_helpers/addons/dir_addon/other.py create mode 100644 tests/test_helpers/addons/invalid_addon.py create mode 100644 tests/test_helpers/addons/not_an_addon.py rename tests/test_helpers/addons/{add_curve_ivygen.py => real_addon.py} (100%) create mode 100644 tests/test_helpers/addons/repo.json create mode 100644 tests/test_helpers/addons/zipped_addon.zip rename tests/test_helpers/{extra_objects_blinfo.txt => dir_addon_output} (100%) rename tests/test_helpers/{ivy_gen_blinfo.txt => real_addon.py_output} (100%) create mode 100644 tests/test_helpers/zipped_addon.zip_output diff --git a/tests/test_blenderpack.py b/tests/test_blenderpack.py index fd11cac..0d3528f 100644 --- a/tests/test_blenderpack.py +++ b/tests/test_blenderpack.py @@ -7,38 +7,56 @@ import blenderpack class test_blenderpack_make_repo(unittest.TestCase): helper_path = os.path.join('tests', 'test_helpers') - - def test_extract_blinfo_from_file(self): - with open(os.path.join(self.helper_path, 'ivy_gen_blinfo.txt'), 'r') as f: - expectation = f.read() - - reality = str(blenderpack.extract_blinfo(os.path.join(self.helper_path, 'addons', 'add_curve_ivygen.py'))) - self.assertEqual(expectation, reality) - - def test_extract_blinfo_from_zip(self): - with open(os.path.join(self.helper_path, 'extra_objects_blinfo.txt'), 'r') as f: - expectation = f.read() - - reality = str(blenderpack.extract_blinfo(os.path.join(self.helper_path, 'addons', 'add_curve_extra_objects.zip'))) - self.assertEqual(expectation, reality) - - def test_extract_blinfo_from_dir(self): - with open(os.path.join(self.helper_path, 'extra_objects_blinfo.txt'), 'r') as f: - expectation = f.read() - - reality = str(blenderpack.extract_blinfo(os.path.join(self.helper_path, 'addons', 'add_curve_extra_objects/'))) - self.assertEqual(expectation, reality) + addon_path = os.path.join(helper_path, 'addons') def test_extract_blinfo_from_nonexistent(self): + test_file = 'file_that_doesnt_exist' self.assertRaises( FileNotFoundError, - lambda: blenderpack.extract_blinfo(os.path.join(self.helper_path, 'addons', 'notathing')) + blenderpack.extract_blinfo, + os.path.join(self.addon_path, test_file) ) + def test_extract_blinfo_from_nonaddon(self): + test_file = 'not_an_addon.py' + self.assertRaises( + blenderpack.BadAddon, + blenderpack.extract_blinfo, + os.path.join(self.addon_path, test_file) + ) def test_make_repo_valid(self): blenderpack.make_repo(os.path.join(self.helper_path, 'addons')) + repojson = os.path.join(self.helper_path, 'addons', 'repo.json') + self.assertTrue(os.path.exists(repojson)) + with open(repojson, 'r') as f: + json.loads(f.read()) + + os.remove(repojson) + self.fail('unfinished test') def test_make_repo_from_nonexistent(self): blenderpack.make_repo(os.path.join(self.helper_path, 'addons')) + self.fail('unfinished test') + + +# testname: filename +bl_info_tests = { + 'test_extract_blinfo_from_file': 'real_addon.py', + 'test_extract_blinfo_from_zip': 'zipped_addon.zip', + 'test_extract_blinfo_from_dir': 'dir_addon', +} + +def generate_test(test_file): + def test(self): + reality = str(blenderpack.extract_blinfo(os.path.join(self.addon_path, test_file))) + with open(os.path.join(self.helper_path, test_file + '_output'), 'r') as f: + expectation = f.read() + self.assertEqual(expectation, reality) + return test + +for name, param in bl_info_tests.items(): + test_func = generate_test(param) + setattr(test_blenderpack_make_repo, 'test_{}'.format(name), test_func) + diff --git a/tests/test_helpers/addons/add_curve_extra_objects.zip b/tests/test_helpers/addons/add_curve_extra_objects.zip deleted file mode 100644 index c08089b0740d330333cc4e79d3d55db618aafee4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 51971 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 Add > Curve", - "description": "Adds many different types of Curves", - "warning": "", - "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/" - "Scripts/Curve/Curves_Galore", - "category": "Add Curve", -} -""" - -import bpy -from bpy.props import ( - BoolProperty, - EnumProperty, - FloatProperty, - IntProperty, - ) -from mathutils import Matrix -from bpy.types import Operator -from math import ( - sin, cos, pi - ) -import mathutils.noise as Noise - - -# ------------------------------------------------------------ -# Some functions to use with others: -# ------------------------------------------------------------ - -# ------------------------------------------------------------ -# Generate random number: -def randnum(low=0.0, high=1.0, seed=0): - """ - randnum( low=0.0, high=1.0, seed=0 ) - - Create random number - Parameters: - low - lower range - (type=float) - high - higher range - (type=float) - seed - the random seed number, if seed is 0, the current time will be used instead - (type=int) - Returns: - a random number - (type=float) - """ - - Noise.seed_set(seed) - rnum = Noise.random() - rnum = rnum * (high - low) - rnum = rnum + low - return rnum - - -# ------------------------------------------------------------ -# Make some noise: -def vTurbNoise(x, y, z, iScale=0.25, Size=1.0, Depth=6, Hard=0, Basis=0, Seed=0): - """ - vTurbNoise((x,y,z), iScale=0.25, Size=1.0, Depth=6, Hard=0, Basis=0, Seed=0 ) - - Create randomised vTurbulence noise - - Parameters: - xyz - (x,y,z) float values. - (type=3-float tuple) - iScale - noise intensity scale - (type=float) - Size - noise size - (type=float) - Depth - number of noise values added. - (type=int) - Hard - noise hardness: 0 - soft noise; 1 - hard noise - (type=int) - basis - type of noise used for turbulence - (type=int) - Seed - the random seed number, if seed is 0, the current time will be used instead - (type=int) - Returns: - the generated turbulence vector. - (type=3-float list) - """ - rand = randnum(-100, 100, Seed) - if Basis is 9: - Basis = 14 - vTurb = Noise.turbulence_vector((x / Size + rand, y / Size + rand, z / Size + rand), - Depth, Hard, Basis) - tx = vTurb[0] * iScale - ty = vTurb[1] * iScale - tz = vTurb[2] * iScale - return tx, ty, tz - - -# ------------------------------------------------------------------- -# 2D Curve shape functions: -# ------------------------------------------------------------------- - -# ------------------------------------------------------------ -# 2DCurve: Profile: L, H, T, U, Z -def ProfileCurve(type=0, a=0.25, b=0.25): - """ - ProfileCurve( type=0, a=0.25, b=0.25 ) - - Create profile curve - - Parameters: - type - select profile type, L, H, T, U, Z - (type=int) - a - a scaling parameter - (type=float) - b - b scaling parameter - (type=float) - Returns: - a list with lists of x,y,z coordinates for curve points, [[x,y,z],[x,y,z],...n] - (type=list) - """ - - newpoints = [] - if type is 1: - # H: - a *= 0.5 - b *= 0.5 - newpoints = [ - [-1.0, 1.0, 0.0], [-1.0 + a, 1.0, 0.0], - [-1.0 + a, b, 0.0], [1.0 - a, b, 0.0], [1.0 - a, 1.0, 0.0], - [1.0, 1.0, 0.0], [1.0, -1.0, 0.0], [1.0 - a, -1.0, 0.0], - [1.0 - a, -b, 0.0], [-1.0 + a, -b, 0.0], [-1.0 + a, -1.0, 0.0], - [-1.0, -1.0, 0.0] - ] - elif type is 2: - # T: - a *= 0.5 - newpoints = [ - [-1.0, 1.0, 0.0], [1.0, 1.0, 0.0], - [1.0, 1.0 - b, 0.0], [a, 1.0 - b, 0.0], [a, -1.0, 0.0], - [-a, -1.0, 0.0], [-a, 1.0 - b, 0.0], [-1.0, 1.0 - b, 0.0] - ] - elif type is 3: - # U: - a *= 0.5 - newpoints = [ - [-1.0, 1.0, 0.0], [-1.0 + a, 1.0, 0.0], - [-1.0 + a, -1.0 + b, 0.0], [1.0 - a, -1.0 + b, 0.0], [1.0 - a, 1.0, 0.0], - [1.0, 1.0, 0.0], [1.0, -1.0, 0.0], [-1.0, -1.0, 0.0] - ] - elif type is 4: - # Z: - a *= 0.5 - newpoints = [ - [-0.5, 1.0, 0.0], [a, 1.0, 0.0], - [a, -1.0 + b, 0.0], [1.0, -1.0 + b, 0.0], [1.0, -1.0, 0.0], - [-a, -1.0, 0.0], [-a, 1.0 - b, 0.0], [-1.0, 1.0 - b, 0.0], - [-1.0, 1.0, 0.0] - ] - else: - # L: - newpoints = [ - [-1.0, 1.0, 0.0], [-1.0 + a, 1.0, 0.0], - [-1.0 + a, -1.0 + b, 0.0], [1.0, -1.0 + b, 0.0], - [1.0, -1.0, 0.0], [-1.0, -1.0, 0.0] - ] - return newpoints - - -# ------------------------------------------------------------ -# 2DCurve: Arrow -def ArrowCurve(type=1, a=1.0, b=0.5): - """ - ArrowCurve( type=1, a=1.0, b=0.5, c=1.0 ) - - Create arrow curve - - Parameters: - type - select type, Arrow1, Arrow2 - (type=int) - a - a scaling parameter - (type=float) - b - b scaling parameter - (type=float) - Returns: - a list with lists of x,y,z coordinates for curve points, [[x,y,z],[x,y,z],...n] - (type=list) - """ - - newpoints = [] - if type is 0: - # Arrow1: - a *= 0.5 - b *= 0.5 - newpoints = [ - [-1.0, b, 0.0], [-1.0 + a, b, 0.0], - [-1.0 + a, 1.0, 0.0], [1.0, 0.0, 0.0], - [-1.0 + a, -1.0, 0.0], [-1.0 + a, -b, 0.0], - [-1.0, -b, 0.0] - ] - elif type is 1: - # Arrow2: - newpoints = [[-a, b, 0.0], [a, 0.0, 0.0], [-a, -b, 0.0], [0.0, 0.0, 0.0]] - else: - # diamond: - newpoints = [[0.0, b, 0.0], [a, 0.0, 0.0], [0.0, -b, 0.0], [-a, 0.0, 0.0]] - return newpoints - - -# ------------------------------------------------------------ -# 2DCurve: Square / Rectangle -def RectCurve(type=1, a=1.0, b=0.5, c=1.0): - """ - RectCurve( type=1, a=1.0, b=0.5, c=1.0 ) - - Create square / rectangle curve - - Parameters: - type - select type, Square, Rounded square 1, Rounded square 2 - (type=int) - a - a scaling parameter - (type=float) - b - b scaling parameter - (type=float) - c - c scaling parameter - (type=float) - Returns: - a list with lists of x,y,z coordinates for curve points, [[x,y,z],[x,y,z],...n] - (type=list) - """ - - newpoints = [] - if type is 1: - # Rounded Rectangle: - newpoints = [ - [-a, b - b * 0.2, 0.0], [-a + a * 0.05, b - b * 0.05, 0.0], [-a + a * 0.2, b, 0.0], - [a - a * 0.2, b, 0.0], [a - a * 0.05, b - b * 0.05, 0.0], [a, b - b * 0.2, 0.0], - [a, -b + b * 0.2, 0.0], [a - a * 0.05, -b + b * 0.05, 0.0], [a - a * 0.2, -b, 0.0], - [-a + a * 0.2, -b, 0.0], [-a + a * 0.05, -b + b * 0.05, 0.0], [-a, -b + b * 0.2, 0.0] - ] - elif type is 2: - # Rounded Rectangle II: - newpoints = [] - x = a - y = b - r = c - if r > x: - r = x - 0.0001 - if r > y: - r = y - 0.0001 - if r > 0: - newpoints.append([-x + r, y, 0]) - newpoints.append([x - r, y, 0]) - newpoints.append([x, y - r, 0]) - newpoints.append([x, -y + r, 0]) - newpoints.append([x - r, -y, 0]) - newpoints.append([-x + r, -y, 0]) - newpoints.append([-x, -y + r, 0]) - newpoints.append([-x, y - r, 0]) - else: - newpoints.append([-x, y, 0]) - newpoints.append([x, y, 0]) - newpoints.append([x, -y, 0]) - newpoints.append([-x, -y, 0]) - else: - # Rectangle: - newpoints = [[-a, b, 0.0], [a, b, 0.0], [a, -b, 0.0], [-a, -b, 0.0]] - return newpoints - - -# ------------------------------------------------------------ -# 2DCurve: Star: -def StarCurve(starpoints=8, innerradius=0.5, outerradius=1.0, twist=0.0): - """ - StarCurve( starpoints=8, innerradius=0.5, outerradius=1.0, twist=0.0 ) - - Create star shaped curve - - Parameters: - starpoints - the number of points - (type=int) - innerradius - innerradius - (type=float) - outerradius - outerradius - (type=float) - twist - twist amount - (type=float) - Returns: - a list with lists of x,y,z coordinates for curve points, [[x,y,z],[x,y,z],...n] - (type=list) - """ - - newpoints = [] - step = 2.0 / starpoints - i = 0 - while i < starpoints: - t = i * step - x1 = cos(t * pi) * outerradius - y1 = sin(t * pi) * outerradius - newpoints.append([x1, y1, 0]) - x2 = cos(t * pi + (pi / starpoints + twist)) * innerradius - y2 = sin(t * pi + (pi / starpoints + twist)) * innerradius - newpoints.append([x2, y2, 0]) - i += 1 - return newpoints - - -# ------------------------------------------------------------ -# 2DCurve: Flower: -def FlowerCurve(petals=8, innerradius=0.5, outerradius=1.0, petalwidth=2.0): - """ - FlowerCurve( petals=8, innerradius=0.5, outerradius=1.0, petalwidth=2.0 ) - - Create flower shaped curve - - Parameters: - petals - the number of petals - (type=int) - innerradius - innerradius - (type=float) - outerradius - outerradius - (type=float) - petalwidth - width of petals - (type=float) - Returns: - a list with lists of x,y,z coordinates for curve points, [[x,y,z],[x,y,z],...n] - (type=list) - """ - - newpoints = [] - step = 2.0 / petals - pet = (step / pi * 2) * petalwidth - i = 0 - while i < petals: - t = i * step - x1 = cos(t * pi - (pi / petals)) * innerradius - y1 = sin(t * pi - (pi / petals)) * innerradius - newpoints.append([x1, y1, 0]) - x2 = cos(t * pi - pet) * outerradius - y2 = sin(t * pi - pet) * outerradius - newpoints.append([x2, y2, 0]) - x3 = cos(t * pi + pet) * outerradius - y3 = sin(t * pi + pet) * outerradius - newpoints.append([x3, y3, 0]) - i += 1 - return newpoints - - -# ------------------------------------------------------------ -# 2DCurve: Arc,Sector,Segment,Ring: -def ArcCurve(sides=6, startangle=0.0, endangle=90.0, innerradius=0.5, outerradius=1.0, type=3): - """ - ArcCurve( sides=6, startangle=0.0, endangle=90.0, innerradius=0.5, outerradius=1.0, type=3 ) - - Create arc shaped curve - - Parameters: - sides - number of sides - (type=int) - startangle - startangle - (type=float) - endangle - endangle - (type=float) - innerradius - innerradius - (type=float) - outerradius - outerradius - (type=float) - type - select type Arc,Sector,Segment,Ring - (type=int) - Returns: - a list with lists of x,y,z coordinates for curve points, [[x,y,z],[x,y,z],...n] - (type=list) - """ - - newpoints = [] - sides += 1 - angle = 2.0 * (1.0 / 360.0) - endangle -= startangle - step = (angle * endangle) / (sides - 1) - i = 0 - while i < sides: - t = (i * step) + angle * startangle - x1 = sin(t * pi) * outerradius - y1 = cos(t * pi) * outerradius - newpoints.append([x1, y1, 0]) - i += 1 - - # if type == 1: - # Arc: turn cyclic curve flag off! - - # Segment: - if type is 2: - newpoints.append([0, 0, 0]) - # Ring: - elif type is 3: - j = sides - 1 - while j > -1: - t = (j * step) + angle * startangle - x2 = sin(t * pi) * innerradius - y2 = cos(t * pi) * innerradius - newpoints.append([x2, y2, 0]) - j -= 1 - return newpoints - - -# ------------------------------------------------------------ -# 2DCurve: Cog wheel: -def CogCurve(theeth=8, innerradius=0.8, middleradius=0.95, outerradius=1.0, bevel=0.5): - """ - CogCurve( theeth=8, innerradius=0.8, middleradius=0.95, outerradius=1.0, bevel=0.5 ) - - Create cog wheel shaped curve - - Parameters: - theeth - number of theeth - (type=int) - innerradius - innerradius - (type=float) - middleradius - middleradius - (type=float) - outerradius - outerradius - (type=float) - bevel - bevel amount - (type=float) - Returns: - a list with lists of x,y,z coordinates for curve points, [[x,y,z],[x,y,z],...n] - (type=list) - """ - - newpoints = [] - step = 2.0 / theeth - pet = step / pi * 2 - bevel = 1.0 - bevel - i = 0 - while i < theeth: - t = i * step - x1 = cos(t * pi - (pi / theeth) - pet) * innerradius - y1 = sin(t * pi - (pi / theeth) - pet) * innerradius - newpoints.append([x1, y1, 0]) - x2 = cos(t * pi - (pi / theeth) + pet) * innerradius - y2 = sin(t * pi - (pi / theeth) + pet) * innerradius - newpoints.append([x2, y2, 0]) - x3 = cos(t * pi - pet) * middleradius - y3 = sin(t * pi - pet) * middleradius - newpoints.append([x3, y3, 0]) - x4 = cos(t * pi - (pet * bevel)) * outerradius - y4 = sin(t * pi - (pet * bevel)) * outerradius - newpoints.append([x4, y4, 0]) - x5 = cos(t * pi + (pet * bevel)) * outerradius - y5 = sin(t * pi + (pet * bevel)) * outerradius - newpoints.append([x5, y5, 0]) - x6 = cos(t * pi + pet) * middleradius - y6 = sin(t * pi + pet) * middleradius - newpoints.append([x6, y6, 0]) - i += 1 - return newpoints - - -# ------------------------------------------------------------ -# 2DCurve: nSide: -def nSideCurve(sides=6, radius=1.0): - """ - nSideCurve( sides=6, radius=1.0 ) - - Create n-sided curve - - Parameters: - sides - number of sides - (type=int) - radius - radius - (type=float) - Returns: - a list with lists of x,y,z coordinates for curve points, [[x,y,z],[x,y,z],...n] - (type=list) - """ - - newpoints = [] - step = 2.0 / sides - i = 0 - while i < sides: - t = i * step - x = sin(t * pi) * radius - y = cos(t * pi) * radius - newpoints.append([x, y, 0]) - i += 1 - return newpoints - - -# ------------------------------------------------------------ -# 2DCurve: Splat: -def SplatCurve(sides=24, scale=1.0, seed=0, basis=0, radius=1.0): - """ - SplatCurve( sides=24, scale=1.0, seed=0, basis=0, radius=1.0 ) - - Create splat curve - - Parameters: - sides - number of sides - (type=int) - scale - noise size - (type=float) - seed - noise random seed - (type=int) - basis - noise basis - (type=int) - radius - radius - (type=float) - Returns: - a list with lists of x,y,z coordinates for curve points, [[x,y,z],[x,y,z],...n] - (type=list) - """ - - newpoints = [] - step = 2.0 / sides - i = 0 - while i < sides: - t = i * step - turb = vTurbNoise(t, t, t, 1.0, scale, 6, 0, basis, seed) - turb = turb[2] * 0.5 + 0.5 - x = sin(t * pi) * radius * turb - y = cos(t * pi) * radius * turb - newpoints.append([x, y, 0]) - i += 1 - return newpoints - - -# ----------------------------------------------------------- -# Cycloid curve -def CycloidCurve(number=100, type=0, R=4.0, r=1.0, d=1.0): - """ - CycloidCurve( number=100, type=0, a=4.0, b=1.0 ) - - Create a Cycloid, Hypotrochoid / Hypocycloid or Epitrochoid / Epycycloid type of curve - - Parameters: - number - the number of points - (type=int) - type - types: Cycloid, Hypocycloid, Epicycloid - (type=int) - R = Radius a scaling parameter - (type=float) - r = Radius b scaling parameter - (type=float) - d = Distance scaling parameter - (type=float) - Returns: - a list with lists of x,y,z coordinates for curve points, [[x,y,z],[x,y,z],...n] - (type=list) - """ - - a = R - b = r - newpoints = [] - step = 2.0 / (number - 1) - i = 0 - if type is 1: - # Hypotrochoid / Hypocycloid - while i < number: - t = i * step - x = ((a - b) * cos(t * pi)) + (d * cos(((a + b) / b) * t * pi)) - y = ((a - b) * sin(t * pi)) - (d * sin(((a + b) / b) * t * pi)) - z = 0 - newpoints.append([x, y, z]) - i += 1 - elif type is 2: - # Epitrochoid / Epycycloid - while i < number: - t = i * step - x = ((a + b) * cos(t * pi)) - (d * cos(((a + b) / b) * t * pi)) - y = ((a + b) * sin(t * pi)) - (d * sin(((a + b) / b) * t * pi)) - z = 0 - newpoints.append([x, y, z]) - i += 1 - else: - # Cycloid - while i < number: - t = (i * step * pi) - x = (t - sin(t) * b) * a / pi - y = (1 - cos(t) * b) * a / pi - z = 0 - newpoints.append([x, y, z]) - i += 1 - return newpoints - - -# ----------------------------------------------------------- -# 3D curve shape functions: -# ----------------------------------------------------------- - -# ------------------------------------------------------------ -# 3DCurve: Helix: -def HelixCurve(number=100, height=2.0, startangle=0.0, endangle=360.0, width=1.0, a=0.0, b=0.0): - """ - HelixCurve( number=100, height=2.0, startangle=0.0, endangle=360.0, width=1.0, a=0.0, b=0.0 ) - - Create helix curve - - Parameters: - number - the number of points - (type=int) - height - height - (type=float) - startangle - startangle - (type=float) - endangle - endangle - (type=float) - width - width - (type=float) - a - a - (type=float) - b - b - (type=float) - Returns: - a list with lists of x,y,z coordinates for curve points, [[x,y,z],[x,y,z],...n] - (type=list) - """ - - newpoints = [] - angle = (2.0 / 360.0) * (endangle - startangle) - step = angle / (number - 1) - h = height / angle - start = startangle * 2.0 / 360.0 - a /= angle - i = 0 - while i < number: - t = (i * step + start) - x = sin((t * pi)) * (1.0 + cos(t * pi * a - (b * pi))) * (0.25 * width) - y = cos((t * pi)) * (1.0 + cos(t * pi * a - (b * pi))) * (0.25 * width) - z = (t * h) - h * start - newpoints.append([x, y, z]) - i += 1 - return newpoints - - -# ----------------------------------------------------------- -# 3D Noise curve -def NoiseCurve(type=0, number=100, length=2.0, size=0.5, - scale=[0.5, 0.5, 0.5], octaves=2, basis=0, seed=0): - """ - Create noise curve - - Parameters: - number - number of points - (type=int) - length - curve length - (type=float) - size - noise size - (type=float) - scale - noise intensity scale x,y,z - (type=list) - basis - noise basis - (type=int) - seed - noise random seed - (type=int) - type - noise curve type - (type=int) - Returns: - a list with lists of x,y,z coordinates for curve points, [[x,y,z],[x,y,z],...n] - (type=list) - """ - - newpoints = [] - step = (length / number) - i = 0 - if type is 1: - # noise circle - while i < number: - t = i * step - v = vTurbNoise(t, t, t, 1.0, size, octaves, 0, basis, seed) - x = sin(t * pi) + (v[0] * scale[0]) - y = cos(t * pi) + (v[1] * scale[1]) - z = v[2] * scale[2] - newpoints.append([x, y, z]) - i += 1 - elif type is 2: - # noise knot / ball - while i < number: - t = i * step - v = vTurbNoise(t, t, t, 1.0, 1.0, octaves, 0, basis, seed) - x = v[0] * scale[0] * size - y = v[1] * scale[1] * size - z = v[2] * scale[2] * size - newpoints.append([x, y, z]) - i += 1 - else: - # noise linear - while i < number: - t = i * step - v = vTurbNoise(t, t, t, 1.0, size, octaves, 0, basis, seed) - x = t + v[0] * scale[0] - y = v[1] * scale[1] - z = v[2] * scale[2] - newpoints.append([x, y, z]) - i += 1 - return newpoints - - -# ------------------------------------------------------------ -# calculates the matrix for the new object -# depending on user pref -def align_matrix(context): - - loc = Matrix.Translation(context.scene.cursor_location) - obj_align = context.user_preferences.edit.object_align - - if (context.space_data.type == 'VIEW_3D' and - obj_align == 'VIEW'): - rot = context.space_data.region_3d.view_matrix.to_3x3().inverted().to_4x4() - else: - rot = Matrix() - - align_matrix = loc * rot - return align_matrix - - -# ------------------------------------------------------------ -# Curve creation functions, sets bezierhandles to auto -def setBezierHandles(obj, mode='AUTOMATIC'): - scene = bpy.context.scene - - if obj.type != 'CURVE': - return - - scene.objects.active = obj - bpy.ops.object.mode_set(mode='EDIT', toggle=True) - bpy.ops.curve.select_all(action='SELECT') - bpy.ops.curve.handle_type_set(type=mode) - bpy.ops.object.mode_set(mode='OBJECT', toggle=True) - - -# get array of vertcoordinates acording to splinetype -def vertsToPoints(Verts, splineType): - - # main vars - vertArray = [] - - # array for BEZIER spline output (V3) - if splineType == 'BEZIER': - for v in Verts: - vertArray += v - - # array for nonBEZIER output (V4) - else: - for v in Verts: - vertArray += v - if splineType == 'NURBS': - # for nurbs w=1 - vertArray.append(1) - else: - # for poly w=0 - vertArray.append(0) - return vertArray - - -# create new CurveObject from vertarray and splineType -def createCurve(context, vertArray, self, align_matrix): - scene = context.scene - - # output splineType 'POLY' 'NURBS' 'BEZIER' - splineType = self.outputType - - # GalloreType as name - name = self.ProfileType - - # create curve - newCurve = bpy.data.curves.new(name, type='CURVE') - newSpline = newCurve.splines.new(type=splineType) - - # create spline from vertarray - if splineType == 'BEZIER': - newSpline.bezier_points.add(int(len(vertArray) * 0.33)) - newSpline.bezier_points.foreach_set('co', vertArray) - else: - newSpline.points.add(int(len(vertArray) * 0.25 - 1)) - newSpline.points.foreach_set('co', vertArray) - newSpline.use_endpoint_u = True - - # set curveOptions - newCurve.dimensions = self.shape - newSpline.use_cyclic_u = self.use_cyclic_u - newSpline.use_endpoint_u = self.endp_u - newSpline.order_u = self.order_u - - # create object with newCurve - new_obj = bpy.data.objects.new(name, newCurve) - scene.objects.link(new_obj) - new_obj.select = True - scene.objects.active = new_obj - new_obj.matrix_world = align_matrix - - # set bezierhandles - if splineType == 'BEZIER': - setBezierHandles(new_obj, self.handleType) - - return - - -# ------------------------------------------------------------ -# Main Function -def main(context, self, align_matrix): - # deselect all objects - bpy.ops.object.select_all(action='DESELECT') - - # options - proType = self.ProfileType - splineType = self.outputType - innerRadius = self.innerRadius - middleRadius = self.middleRadius - outerRadius = self.outerRadius - - # get verts - if proType == 'Profile': - verts = ProfileCurve( - self.ProfileCurveType, - self.ProfileCurvevar1, - self.ProfileCurvevar2 - ) - if proType == 'Arrow': - verts = ArrowCurve( - self.MiscCurveType, - self.MiscCurvevar1, - self.MiscCurvevar2 - ) - if proType == 'Rectangle': - verts = RectCurve( - self.MiscCurveType, - self.MiscCurvevar1, - self.MiscCurvevar2, - self.MiscCurvevar3 - ) - if proType == 'Flower': - verts = FlowerCurve( - self.petals, - innerRadius, - outerRadius, - self.petalWidth - ) - if proType == 'Star': - verts = StarCurve( - self.starPoints, - innerRadius, - outerRadius, - self.starTwist - ) - if proType == 'Arc': - verts = ArcCurve( - self.arcSides, - self.startAngle, - self.endAngle, - innerRadius, - outerRadius, - self.arcType - ) - if proType == 'Cogwheel': - verts = CogCurve( - self.teeth, - innerRadius, - middleRadius, - outerRadius, - self.bevel - ) - if proType == 'Nsided': - verts = nSideCurve( - self.Nsides, - outerRadius - ) - if proType == 'Splat': - verts = SplatCurve( - self.splatSides, - self.splatScale, - self.seed, - self.basis, - outerRadius - ) - if proType == 'Cycloid': - verts = CycloidCurve( - self.cycloPoints, - self.cycloType, - self.cyclo_a, - self.cyclo_b, - self.cyclo_d - ) - if proType == 'Helix': - verts = HelixCurve( - self.helixPoints, - self.helixHeight, - self.helixStart, - self.helixEnd, - self.helixWidth, - self.helix_a, - self.helix_b - ) - if proType == 'Noise': - verts = NoiseCurve( - self.noiseType, - self.noisePoints, - self.noiseLength, - self.noiseSize, - [self.noiseScaleX, self.noiseScaleY, self.noiseScaleZ], - self.noiseOctaves, - self.noiseBasis, - self.noiseSeed - ) - - # turn verts into array - vertArray = vertsToPoints(verts, splineType) - - # create object - createCurve(context, vertArray, self, align_matrix) - - return - - -class Curveaceous_galore(Operator): - bl_idname = "mesh.curveaceous_galore" - bl_label = "Curve Profiles" - bl_description = "Construct many types of curves" - bl_options = {'REGISTER', 'UNDO', 'PRESET'} - - # align_matrix for the invoke - align_matrix = None - - # general properties - ProfileType = EnumProperty( - name="Type", - description="Form of Curve to create", - items=[ - ('Arc', "Arc", "Arc"), - ('Arrow', "Arrow", "Arrow"), - ('Cogwheel', "Cogwheel", "Cogwheel"), - ('Cycloid', "Cycloid", "Cycloid"), - ('Flower', "Flower", "Flower"), - ('Helix', "Helix (3D)", "Helix"), - ('Noise', "Noise (3D)", "Noise"), - ('Nsided', "Nsided", "Nsided"), - ('Profile', "Profile", "Profile"), - ('Rectangle', "Rectangle", "Rectangle"), - ('Splat', "Splat", "Splat"), - ('Star', "Star", "Star")] - ) - outputType = EnumProperty( - name="Output splines", - description="Type of splines to output", - items=[ - ('POLY', "Poly", "Poly Spline type"), - ('NURBS', "Nurbs", "Nurbs Spline type"), - ('BEZIER', "Bezier", "Bezier Spline type")] - ) - # Curve Options - shape = EnumProperty( - name="2D / 3D", - description="2D or 3D Curve", - items=[ - ('2D', "2D", "2D"), - ('3D', "3D", "3D") - ] - ) - use_cyclic_u = BoolProperty( - name="Cyclic", - default=True, - description="make curve closed" - ) - endp_u = BoolProperty( - name="Use endpoint u", - default=True, - description="stretch to endpoints" - ) - order_u = IntProperty( - name="Order u", - default=4, - min=2, soft_min=2, - max=6, soft_max=6, - description="Order of nurbs spline" - ) - handleType = EnumProperty( - name="Handle type", - default='AUTOMATIC', - description="Bezier handles type", - items=[ - ('VECTOR', "Vector", "Vector type Bezier handles"), - ('AUTOMATIC', "Auto", "Automatic type Bezier handles")] - ) - # ProfileCurve properties - ProfileCurveType = IntProperty( - name="Type", - min=1, - max=5, - default=1, - description="Type of Curve's Profile" - ) - ProfileCurvevar1 = FloatProperty( - name="Variable 1", - default=0.25, - description="Variable 1 of Curve's Profile" - ) - ProfileCurvevar2 = FloatProperty( - name="Variable 2", - default=0.25, - description="Variable 2 of Curve's Profile" - ) - # Arrow, Rectangle, MiscCurve properties - MiscCurveType = IntProperty( - name="Type", - min=0, - max=3, - default=0, - description="Type of Curve" - ) - MiscCurvevar1 = FloatProperty( - name="Variable 1", - default=1.0, - description="Variable 1 of Curve" - ) - MiscCurvevar2 = FloatProperty( - name="Variable 2", - default=0.5, - description="Variable 2 of Curve" - ) - MiscCurvevar3 = FloatProperty( - name="Variable 3", - default=0.1, - min=0, - description="Variable 3 of Curve" - ) - # Common properties - innerRadius = FloatProperty( - name="Inner radius", - default=0.5, - min=0, - description="Inner radius" - ) - middleRadius = FloatProperty( - name="Middle radius", - default=0.95, - min=0, - description="Middle radius" - ) - outerRadius = FloatProperty( - name="Outer radius", - default=1.0, - min=0, - description="Outer radius" - ) - # Flower properties - petals = IntProperty( - name="Petals", - default=8, - min=2, - description="Number of petals" - ) - petalWidth = FloatProperty( - name="Petal width", - default=2.0, - min=0.01, - description="Petal width" - ) - # Star properties - starPoints = IntProperty( - name="Star points", - default=8, - min=2, - description="Number of star points" - ) - starTwist = FloatProperty( - name="Twist", - default=0.0, - description="Twist" - ) - # Arc properties - arcSides = IntProperty( - name="Arc sides", - default=6, - min=1, - description="Sides of arc" - ) - startAngle = FloatProperty( - name="Start angle", - default=0.0, - description="Start angle" - ) - endAngle = FloatProperty( - name="End angle", - default=90.0, - description="End angle" - ) - arcType = IntProperty( - name="Arc type", - default=3, - min=1, - max=3, - description="Sides of arc" - ) - # Cogwheel properties - teeth = IntProperty( - name="Teeth", - default=8, - min=2, - description="number of teeth" - ) - bevel = FloatProperty( - name="Bevel", - default=0.5, - min=0, - max=1, - description="Bevel" - ) - # Nsided property - Nsides = IntProperty( - name="Sides", - default=8, - min=3, - description="Number of sides" - ) - # Splat properties - splatSides = IntProperty( - name="Splat sides", - default=24, - min=3, - description="Splat sides" - ) - splatScale = FloatProperty( - name="Splat scale", - default=1.0, - min=0.0001, - description="Splat scale" - ) - seed = IntProperty( - name="Seed", - default=0, - min=0, - description="Seed" - ) - basis = IntProperty( - name="Basis", - default=0, - min=0, - max=14, - description="Basis" - ) - # Helix properties - helixPoints = IntProperty( - name="Resolution", - default=100, - min=3, - description="Resolution" - ) - helixHeight = FloatProperty( - name="Height", - default=2.0, - min=0, - description="Helix height" - ) - helixStart = FloatProperty( - name="Start angle", - default=0.0, - description="Helix start angle" - ) - helixEnd = FloatProperty( - name="Endangle", - default=360.0, - description="Helix end angle" - ) - helixWidth = FloatProperty( - name="Width", - default=1.0, - description="Helix width" - ) - helix_a = FloatProperty( - name="Variable 1", - default=0.0, - description="Helix Variable 1" - ) - helix_b = FloatProperty( - name="Variable 2", - default=0.0, - description="Helix Variable 2" - ) - # Cycloid properties - cycloPoints = IntProperty( - name="Resolution", - default=100, - min=3, - soft_min=3, - description="Resolution" - ) - cycloType = IntProperty( - name="Type", - default=1, - min=0, - max=2, - description="Type: Cycloid , Hypocycloid / Hypotrochoid , Epicycloid / Epitrochoid" - ) - cyclo_a = FloatProperty( - name="R", - default=1.0, - min=0.01, - description="Cycloid: R radius a" - ) - cyclo_b = FloatProperty( - name="r", - default=0.25, - min=0.01, - description="Cycloid: r radius b" - ) - cyclo_d = FloatProperty( - name="d", - default=0.25, - description="Cycloid: d distance" - ) - # Noise properties - noiseType = IntProperty( - name="Type", - default=0, - min=0, - max=2, - description="Noise curve type: Linear, Circular or Knot" - ) - noisePoints = IntProperty( - name="Resolution", - default=100, - min=3, - description="Resolution" - ) - noiseLength = FloatProperty( - name="Length", - default=2.0, - min=0.01, - description="Curve Length" - ) - noiseSize = FloatProperty( - name="Noise size", - default=1.0, - min=0.0001, - description="Noise size" - ) - noiseScaleX = FloatProperty( - name="Noise x", - default=1.0, - min=0.0001, - description="Noise x" - ) - noiseScaleY = FloatProperty( - name="Noise y", - default=1.0, - min=0.0001, - description="Noise y" - ) - noiseScaleZ = FloatProperty( - name="Noise z", - default=1.0, - min=0.0001, - description="Noise z" - ) - noiseOctaves = IntProperty( - name="Octaves", - default=2, - min=1, - max=16, - description="Basis" - ) - noiseBasis = IntProperty( - name="Basis", - default=0, - min=0, - max=9, - description="Basis" - ) - noiseSeed = IntProperty( - name="Seed", - default=1, - min=0, - description="Random Seed" - ) - - def draw(self, context): - layout = self.layout - - # general options - col = layout.column() - col.prop(self, 'ProfileType') - col.label(text=self.ProfileType + " Options:") - - # options per ProfileType - box = layout.box() - col = box.column(align=True) - - if self.ProfileType == 'Profile': - col.prop(self, "ProfileCurveType") - col.prop(self, "ProfileCurvevar1") - col.prop(self, "ProfileCurvevar2") - - elif self.ProfileType == 'Arrow': - col.prop(self, "MiscCurveType") - col.prop(self, "MiscCurvevar1", text="Height") - col.prop(self, "MiscCurvevar2", text="Width") - - elif self.ProfileType == 'Rectangle': - col.prop(self, "MiscCurveType") - col.prop(self, "MiscCurvevar1", text="Width") - col.prop(self, "MiscCurvevar2", text="Height") - if self.MiscCurveType is 2: - col.prop(self, "MiscCurvevar3", text="Corners") - - elif self.ProfileType == 'Flower': - col.prop(self, "petals") - col.prop(self, "petalWidth") - - col = box.column(align=True) - col.prop(self, "innerRadius") - col.prop(self, "outerRadius") - - elif self.ProfileType == 'Star': - col.prop(self, "starPoints") - col.prop(self, "starTwist") - - col = box.column(align=True) - col.prop(self, "innerRadius") - col.prop(self, "outerRadius") - - elif self.ProfileType == 'Arc': - col.prop(self, "arcType") - col.prop(self, "arcSides") - - col = box.column(align=True) - col.prop(self, "startAngle") - col.prop(self, "endAngle") - - col = box.column(align=True) - col.prop(self, "innerRadius") - col.prop(self, "outerRadius") - - elif self.ProfileType == 'Cogwheel': - col.prop(self, "teeth") - col.prop(self, "bevel") - - col = box.column(align=True) - col.prop(self, "innerRadius") - col.prop(self, "middleRadius") - col.prop(self, "outerRadius") - - elif self.ProfileType == 'Nsided': - col.prop(self, "Nsides") - col.prop(self, "outerRadius") - - elif self.ProfileType == 'Splat': - col.prop(self, "splatSides") - col.prop(self, "outerRadius") - - col = box.column(align=True) - col.prop(self, "splatScale") - col.prop(self, "seed") - col.prop(self, "basis") - - elif self.ProfileType == 'Cycloid': - col.prop(self, "cycloType") - col.prop(self, "cycloPoints") - - col = box.column(align=True) - col.prop(self, "cyclo_a") - col.prop(self, "cyclo_b") - if self.cycloType is not 0: - col.prop(self, "cyclo_d") - - elif self.ProfileType == 'Helix': - col.prop(self, "helixPoints") - col.prop(self, "helixHeight") - col.prop(self, "helixWidth") - - col = box.column(align=True) - col.prop(self, "helixStart") - col.prop(self, "helixEnd") - - col = box.column(align=True) - col.prop(self, "helix_a") - col.prop(self, "helix_b") - - elif self.ProfileType == 'Noise': - col.prop(self, "noiseType") - col.prop(self, "noisePoints") - col.prop(self, "noiseLength") - - col = box.column(align=True) - col.prop(self, "noiseSize") - col.prop(self, "noiseScaleX") - col.prop(self, "noiseScaleY") - col.prop(self, "noiseScaleZ") - - col = box.column(align=True) - col.prop(self, "noiseOctaves") - col.prop(self, "noiseBasis") - col.prop(self, "noiseSeed") - - col = layout.column() - col.label(text="Output Curve Type:") - col.row().prop(self, "outputType", expand=True) - - # output options - if self.outputType == 'NURBS': - col.prop(self, 'order_u') - elif self.outputType == 'BEZIER': - col.row().prop(self, 'handleType', expand=True) - - @classmethod - def poll(cls, context): - return context.scene is not None - - def execute(self, context): - # turn off undo - undo = context.user_preferences.edit.use_global_undo - context.user_preferences.edit.use_global_undo = False - - # deal with 2D - 3D curve differences - if self.ProfileType in ['Helix', 'Cycloid', 'Noise']: - self.shape = '3D' - else: - self.shape = '2D' - - if self.ProfileType in ['Helix', 'Noise', 'Cycloid']: - self.use_cyclic_u = False - if self.ProfileType in ['Cycloid']: - if self.cycloType is 0: - self.use_cyclic_u = False - else: - self.use_cyclic_u = True - else: - if self.ProfileType == 'Arc' and self.arcType is 1: - self.use_cyclic_u = False - else: - self.use_cyclic_u = True - - # main function - main(context, self, self.align_matrix or Matrix()) - - # restore pre operator undo state - context.user_preferences.edit.use_global_undo = undo - - return {'FINISHED'} - - def invoke(self, context, event): - # store creation_matrix - self.align_matrix = align_matrix(context) - self.execute(context) - - return {'FINISHED'} diff --git a/tests/test_helpers/addons/add_curve_extra_objects/add_curve_braid.py b/tests/test_helpers/addons/add_curve_extra_objects/add_curve_braid.py deleted file mode 100644 index 90b41e5..0000000 --- a/tests/test_helpers/addons/add_curve_extra_objects/add_curve_braid.py +++ /dev/null @@ -1,261 +0,0 @@ -# gpl: author Jared Forsyth - -""" -bl_info = { - "name": "New Braid", - "author": "Jared Forsyth ", - "version": (1, 0, 2), - "blender": (2, 6, 0), - "location": "View3D > Add > Mesh > New Braid", - "description": "Adds a new Braid", - "warning": "", - "wiki_url": "", - "category": "Add Mesh"} -""" - -import bpy -from bpy.props import ( - FloatProperty, - IntProperty, - BoolProperty, - ) -from bpy.types import Operator -from math import ( - sin, cos, - pi, - ) - - -def angle_point(center, angle, distance): - cx, cy = center - x = cos(angle) * distance - y = sin(angle) * distance - return x + cx, y + cy - - -def flat_hump(strands, mx=1, my=1, mz=1, resolution=2): - num = 4 * resolution - dy = 2 * pi / num - dz = 2 * pi * (strands - 1) / num - for i in range(num): - x = i * mx - y = cos(i * dy) * my - z = sin(i * dz) * mz - - yield x, y, z - - -def circle_hump(pos, strands, humps, radius=1, mr=1, mz=.2, resolution=2): - num = 5 * resolution - dt = 2 * pi / humps * strands / num - dr = 2 * pi * (strands - 1) / num - dz = 2 * pi / num - t0 = 2 * pi / humps * pos - - for i in range(num): - x, y = angle_point((0, 0), i * dt + t0, radius + sin(i * dr) * mr) - z = cos(i * dz) * mz - - yield x, y, z - - -def make_strands(strands, humps, radius=1, mr=1, mz=.2, resolution=2): - positions = [0 for x in range(humps)] - last = None - lines = [] - at = 0 - - while 0 in positions: - if positions[at]: - at = positions.index(0) - last = None - hump = list(circle_hump(at, strands, humps, radius, mr, mz, resolution)) - if last is None: - last = hump - lines.append(last) - else: - last.extend(hump) - positions[at] = 1 - at += strands - at %= humps - - return lines - - -def poly_line(curve, points, join=True, type='NURBS'): - polyline = curve.splines.new(type) - polyline.points.add(len(points) - 1) - for num in range(len(points)): - polyline.points[num].co = (points[num]) + (1,) - - polyline.order_u = len(polyline.points) - 1 - if join: - polyline.use_cyclic_u = True - - -def poly_lines(objname, curvename, lines, bevel=None, joins=False, ctype='NURBS'): - curve = bpy.data.curves.new(name=curvename, type='CURVE') - curve.dimensions = '3D' - - obj = bpy.data.objects.new(objname, curve) - obj.location = (0, 0, 0) # object origin - - for i, line in enumerate(lines): - poly_line(curve, line, joins if type(joins) == bool else joins[i], type=ctype) - - if bevel: - curve.bevel_object = bpy.data.objects[bevel] - return obj - - -def nurbs_circle(name, w, h): - pts = [(-w / 2, 0, 0), (0, -h / 2, 0), (w / 2, 0, 0), (0, h / 2, 0)] - return poly_lines(name, name + '_curve', [pts], joins=True) - - -def star_pts(r=1, ir=None, points=5, center=(0, 0)): - """ - Create points for a star. They are 2d - z is always zero - - r: the outer radius - ir: the inner radius - """ - if not ir: - ir = r / 5 - pts = [] - dt = pi * 2 / points - for i in range(points): - t = i * dt - ti = (i + .5) * dt - pts.append(angle_point(center, t, r) + (0,)) - pts.append(angle_point(center, ti, ir) + (0,)) - return pts - - -def defaultCircle(w=.6): - circle = nurbs_circle('braid_circle', w, w) - circle.hide = True - return circle - - -def defaultStar(): - star = poly_lines('star', 'staz', [tuple(star_pts(points=5, r=.5, ir=.05))], type='NURBS') - star.hide = True - return star - - -def awesome_braid(strands=3, sides=5, bevel='braid_circle', pointy=False, **kwds): - lines = make_strands(strands, sides, **kwds) - types = {True: 'POLY', False: 'NURBS'}[pointy] - return poly_lines('Braid', 'Braid_c', lines, bevel=bevel, joins=True, ctype=types) - - -class Braid(Operator): - bl_idname = "mesh.add_braid" - bl_label = "New Braid" - bl_description = ("Construct a new Braid\n" - "Creates two objects - the hidden one is used as the Bevel control") - bl_options = {'REGISTER', 'UNDO', 'PRESET'} - - strands = IntProperty( - name="Strands", - description="Number of Strands", - min=2, max=100, - default=3 - ) - sides = IntProperty( - name="Sides", - description="Number of Knot sides", - min=2, max=100, - default=5 - ) - radius = FloatProperty( - name="Radius", - description="Increase / decrease the diameter in X,Y axis", - default=1 - ) - thickness = FloatProperty( - name="Thickness", - description="The ratio between inner and outside diameters", - default=.3 - ) - strandsize = FloatProperty( - name="Bevel Depth", - description="Individual strand diameter (similar to Curve's Bevel depth)", - default=.3, - min=.01, max=10 - ) - width = FloatProperty( - name="Width", - description="Stretch the Braids along the Z axis", - default=.2 - ) - resolution = IntProperty( - name="Bevel Resolution", - description="Resolution of the Created curve\n" - "Increasing this value, will produce heavy geometry", - min=1, - max=100, soft_max=24, - default=2 - ) - pointy = BoolProperty( - name="Pointy", - description="Switch between round and sharp corners", - default=False - ) - - def draw(self, context): - layout = self.layout - - box = layout.box() - col = box.column(align=True) - col.label("Settings:") - col.prop(self, "strands") - col.prop(self, "sides") - - col = box.column(align=True) - col.prop(self, "radius") - col.prop(self, "thickness") - col.prop(self, "width") - - col = box.column() - col.prop(self, "pointy") - - box = layout.box() - col = box.column(align=True) - col.label("Geometry Options:") - col.prop(self, "strandsize") - col.prop(self, "resolution") - - def execute(self, context): - circle = defaultCircle(self.strandsize) - context.scene.objects.link(circle) - braid = awesome_braid( - self.strands, self.sides, - bevel=circle.name, - pointy=self.pointy, - radius=self.radius, - mr=self.thickness, - mz=self.width, - resolution=self.resolution - ) - base = context.scene.objects.link(braid) - - for ob in context.scene.objects: - ob.select = False - base.select = True - context.scene.objects.active = braid - - return {'FINISHED'} - - -def register(): - bpy.utils.register_class(Braid) - - -def unregister(): - bpy.utils.unregister_class(Braid) - - -if __name__ == "__main__": - register() diff --git a/tests/test_helpers/addons/add_curve_extra_objects/add_curve_celtic_links.py b/tests/test_helpers/addons/add_curve_extra_objects/add_curve_celtic_links.py deleted file mode 100644 index 66b800b..0000000 --- a/tests/test_helpers/addons/add_curve_extra_objects/add_curve_celtic_links.py +++ /dev/null @@ -1,284 +0,0 @@ -# Blender plugin for generating celtic knot curves from 3d meshes -# -# The MIT License (MIT) -# -# Copyright (c) 2013 Adam Newgas -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -bl_info = { - "name": "Celtic Knot", - "description": "", - "author": "Adam Newgas", - "version": (0, 1, 2), - "blender": (2, 74, 0), - "location": "View3D > Add > Curve", - "warning": "", - "wiki_url": "https://github.com/BorisTheBrave/celtic-knot/wiki", - "category": "Add Curve"} - -import bpy -import bmesh -from bpy.types import Operator -from bpy.props import ( - EnumProperty, - FloatProperty, - ) -from collections import defaultdict -from math import ( - pi, sin, - cos, - ) - - -class CelticKnotOperator(Operator): - bl_idname = "curve.celtic_links" - bl_label = "Celtic Links" - bl_description = "Select a low poly Mesh Object to cover with Knitted Links" - bl_options = {'REGISTER', 'UNDO', 'PRESET'} - - weave_up = FloatProperty( - name="Weave Up", - description="Distance to shift curve upwards over knots", - subtype="DISTANCE", - unit="LENGTH" - ) - weave_down = FloatProperty( - name="Weave Down", - description="Distance to shift curve downward under knots", - subtype="DISTANCE", - unit="LENGTH" - ) - handle_types = [ - ('ALIGNED', "Aligned", "Points at a fixed crossing angle"), - ('AUTO', "Auto", "Automatic control points") - ] - handle_type = EnumProperty( - items=handle_types, - name="Handle Type", - description="Controls what type the bezier control points use", - default='AUTO' - ) - - handle_type_map = {"AUTO": "AUTOMATIC", "ALIGNED": "ALIGNED"} - - crossing_angle = FloatProperty( - name="Crossing Angle", - description="Aligned only: the angle between curves in a knot", - default=pi / 4, - min=0, max=pi / 2, - subtype="ANGLE", - unit="ROTATION" - ) - crossing_strength = FloatProperty( - name="Crossing Strength", - description="Aligned only: strenth of bezier control points", - soft_min=0, - subtype="DISTANCE", - unit="LENGTH" - ) - geo_bDepth = FloatProperty( - name="Bevel Depth", - default=0.04, - min=0, soft_min=0, - description="Bevel Depth", - ) - - @classmethod - def poll(cls, context): - ob = context.active_object - return ((ob is not None) and (ob.mode == "OBJECT") and - (ob.type == "MESH") and (context.mode == "OBJECT")) - - def draw(self, context): - layout = self.layout - layout.prop(self, "handle_type") - - col = layout.column(align=True) - col.prop(self, "weave_up") - col.prop(self, "weave_down") - - col = layout.column(align=True) - col.active = False if self.handle_type == 'AUTO' else True - col.prop(self, "crossing_angle") - col.prop(self, "crossing_strength") - - layout.prop(self, "geo_bDepth") - - def execute(self, context): - # Cache some values - s = sin(self.crossing_angle) * self.crossing_strength - c = cos(self.crossing_angle) * self.crossing_strength - handle_type = self.handle_type - weave_up = self.weave_up - weave_down = self.weave_down - - # Create the new object - orig_obj = obj = context.active_object - curve = bpy.data.curves.new("Celtic", "CURVE") - curve.dimensions = "3D" - curve.twist_mode = "MINIMUM" - curve.fill_mode = "FULL" - curve.bevel_depth = 0.015 - curve.extrude = 0.003 - curve.bevel_resolution = 4 - obj = obj.data - midpoints = [] - - # Compute all the midpoints of each edge - for e in obj.edges.values(): - v1 = obj.vertices[e.vertices[0]] - v2 = obj.vertices[e.vertices[1]] - m = (v1.co + v2.co) / 2.0 - midpoints.append(m) - - bm = bmesh.new() - bm.from_mesh(obj) - # Stores which loops the curve has already passed through - loops_entered = defaultdict(lambda: False) - loops_exited = defaultdict(lambda: False) - # Loops on the boundary of a surface - - def ignorable_loop(loop): - return len(loop.link_loops) == 0 - - # Starting at loop, build a curve one vertex at a time - # until we start where we came from - # Forward means that for any two edges the loop crosses - # sharing a face, it is passing through in clockwise order - # else anticlockwise - - def make_loop(loop, forward): - current_spline = curve.splines.new("BEZIER") - current_spline.use_cyclic_u = True - first = True - # Data for the spline - # It's faster to store in an array and load into blender - # at once - cos = [] - handle_lefts = [] - handle_rights = [] - while True: - if forward: - if loops_exited[loop]: - break - loops_exited[loop] = True - # Follow the face around, ignoring boundary edges - while True: - loop = loop.link_loop_next - if not ignorable_loop(loop): - break - assert loops_entered[loop] is False - loops_entered[loop] = True - v = loop.vert.index - prev_loop = loop - # Find next radial loop - assert loop.link_loops[0] != loop - loop = loop.link_loops[0] - forward = loop.vert.index == v - else: - if loops_entered[loop]: - break - loops_entered[loop] = True - # Follow the face around, ignoring boundary edges - while True: - v = loop.vert.index - loop = loop.link_loop_prev - if not ignorable_loop(loop): - break - assert loops_exited[loop] is False - loops_exited[loop] = True - prev_loop = loop - # Find next radial loop - assert loop.link_loops[-1] != loop - loop = loop.link_loops[-1] - forward = loop.vert.index == v - - if not first: - current_spline.bezier_points.add() - first = False - midpoint = midpoints[loop.edge.index] - normal = loop.calc_normal() + prev_loop.calc_normal() - normal.normalize() - offset = weave_up if forward else weave_down - midpoint = midpoint + offset * normal - cos.extend(midpoint) - - if handle_type != "AUTO": - tangent = loop.link_loop_next.vert.co - loop.vert.co - tangent.normalize() - binormal = normal.cross(tangent).normalized() - if not forward: - tangent *= -1 - s_binormal = s * binormal - c_tangent = c * tangent - handle_left = midpoint - s_binormal - c_tangent - handle_right = midpoint + s_binormal + c_tangent - handle_lefts.extend(handle_left) - handle_rights.extend(handle_right) - - points = current_spline.bezier_points - points.foreach_set("co", cos) - if handle_type != "AUTO": - points.foreach_set("handle_left", handle_lefts) - points.foreach_set("handle_right", handle_rights) - - # Attempt to start a loop at each untouched loop in the entire mesh - for face in bm.faces: - for loop in face.loops: - if ignorable_loop(loop): - continue - if not loops_exited[loop]: - make_loop(loop, True) - if not loops_entered[loop]: - make_loop(loop, False) - - # Create an object from the curve - from bpy_extras import object_utils - object_utils.object_data_add(context, curve, operator=None) - # Set the handle type (this is faster than setting it pointwise) - bpy.ops.object.editmode_toggle() - bpy.ops.curve.select_all(action="SELECT") - bpy.ops.curve.handle_type_set(type=self.handle_type_map[handle_type]) - # Some blender versions lack the default - bpy.ops.curve.radius_set(radius=1.0) - bpy.ops.object.editmode_toggle() - # Restore active selection - curve_obj = context.active_object - - # apply the bevel setting since it was unused - try: - curve_obj.data.bevel_depth = self.geo_bDepth - except: - pass - context.scene.objects.active = orig_obj - - return {'FINISHED'} - - -def register(): - bpy.utils.register_class(CelticKnotOperator) - - -def unregister(): - bpy.utils.unregister_class(CelticKnotOperator) - - -if __name__ == "__main__": - register() diff --git a/tests/test_helpers/addons/add_curve_extra_objects/add_curve_curly.py b/tests/test_helpers/addons/add_curve_extra_objects/add_curve_curly.py deleted file mode 100644 index 779689a..0000000 --- a/tests/test_helpers/addons/add_curve_extra_objects/add_curve_curly.py +++ /dev/null @@ -1,491 +0,0 @@ -# gpl: author Cmomoney - -# DevBo Task https://developer.blender.org/T37299 - -bl_info = { - "name": "Curly Curves", - "author": "Cmomoney", - "version": (1, 1, 8), - "blender": (2, 69, 0), - "location": "View3D > Add > Curve > Curly Curve", - "description": "Adds a new Curly Curve", - "warning": "", - "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/" - "Py/Scripts/Curve/Curly_Curves", - "category": "Add Curve"} - -import bpy -from bpy.types import Operator -from bpy.props import ( - FloatProperty, - IntProperty, - ) -from bpy_extras.object_utils import ( - AddObjectHelper, - object_data_add, - ) - - -def add_type6(self, context): - - scale_x = self.scale_x - scale_y = self.scale_y - verts = [ - [0.047131 * scale_x, 0.065832 * scale_y, - 0.0, 0.010396 * scale_x, -0.186771 * scale_y, - 0.0, 0.076107 * scale_x, 0.19414 * scale_y, - 0.0, 0.0 * scale_x, -1.0 * scale_y, 0.0], - [0.451396 * scale_x, -0.48376 * scale_y, - 0.0, 0.433623 * scale_x, -0.587557 * scale_y, - 0.0, 0.525837 * scale_x, -0.423363 * scale_y, - 0.0, 0.15115 * scale_x, -0.704345 * scale_y, 0.0] - ] - lhandles = [ - [(-0.067558 * scale_x, 0.078418 * scale_y, 0.0), - (0.168759 * scale_x, -0.154334 * scale_y, 0.0), - (-0.236823 * scale_x, 0.262436 * scale_y, 0.0), - (0.233116 * scale_x, -0.596115 * scale_y, 0.0)], - [(0.498001 * scale_x, -0.493434 * scale_y, 0.0), - (0.375618 * scale_x, -0.55465 * scale_y, 0.0), - (0.634373 * scale_x, -0.49873 * scale_y, 0.0), - (0.225277 * scale_x, -0.526814 * scale_y, 0.0)] - ] - rhandles = [ - [(0.161825 * scale_x, 0.053245 * scale_y, 0.0), - (-0.262003 * scale_x, -0.242566 * scale_y, 0.0), - (0.519691 * scale_x, 0.097329 * scale_y, 0.0), - (-0.233116 * scale_x, -1.403885 * scale_y, 0.0)], - [(0.404788 * scale_x, -0.474085 * scale_y, 0.0), - (0.533397 * scale_x, -0.644158 * scale_y, 0.0), - (0.371983 * scale_x, -0.316529 * scale_y, 0.0), - (0.077022 * scale_x, -0.881876 * scale_y, 0.0)] - ] - make_curve(self, context, verts, lhandles, rhandles) - - -def add_type5(self, context): - - scale_x = self.scale_x - scale_y = self.scale_y - verts = [ - [0.047131 * scale_x, 0.065832 * scale_y, 0.0, - 0.010396 * scale_x, -0.186771 * scale_y, 0.0, - 0.076107 * scale_x, 0.19414 * scale_y, 0.0, - 0.0 * scale_x, -1.0 * scale_y, 0.0], - [0.086336 * scale_x, -0.377611 * scale_y, 0.0, - 0.022417 * scale_x, -0.461301 * scale_y, 0.0, - 0.079885 * scale_x, -0.281968 * scale_y, 0.0, - 0.129212 * scale_x, -0.747702 * scale_y, 0.0] - ] - lhandles = [ - [(-0.067558 * scale_x, 0.078419 * scale_y, 0.0), - (0.168759 * scale_x, -0.154335 * scale_y, 0.0), - (-0.236823 * scale_x, 0.262436 * scale_y, 0.0), - (0.233116 * scale_x, -0.596115 * scale_y, 0.0)], - [(0.047518 * scale_x, -0.350065 * scale_y, 0.0), - (0.086012 * scale_x, -0.481379 * scale_y, 0.0), - (-0.049213 * scale_x, -0.253793 * scale_y, 0.0), - (0.208763 * scale_x, -0.572534 * scale_y, 0.0)] - ] - rhandles = [ - [(0.161825 * scale_x, 0.053245 * scale_y, 0.0), - (-0.262003 * scale_x, -0.242566 * scale_y, 0.0), - (0.519691 * scale_x, 0.097329 * scale_y, 0.0), - (-0.233116 * scale_x, -1.403885 * scale_y, 0.0)], - [(0.125156 * scale_x, -0.405159 * scale_y, 0.0), - (-0.086972 * scale_x, -0.426766 * scale_y, 0.0), - (0.262886 * scale_x, -0.321908 * scale_y, 0.0), - (0.049661 * scale_x, -0.92287 * scale_y, 0.0)] - ] - make_curve(self, context, verts, lhandles, rhandles) - - -def add_type8(self, context): - - scale_x = self.scale_x - scale_y = self.scale_y - verts = [ - [-0.850431 * scale_x, -0.009091 * scale_y, - 0.0, -0.818807 * scale_x, -0.130518 * scale_y, - 0.0, -0.944931 * scale_x, 0.055065 * scale_y, - 0.0, -0.393355 * scale_x, -0.035521 * scale_y, - 0.0, 0.0 * scale_x, 0.348298 * scale_y, - 0.0, 0.393355 * scale_x, -0.035521 * scale_y, - 0.0, 0.978373 * scale_x, 0.185638 * scale_y, - 0.0, 0.771617 * scale_x, 0.272819 * scale_y, - 0.0, 0.864179 * scale_x, 0.188103 * scale_y, 0.0] - ] - lhandles = [ - [(-0.90478 * scale_x, -0.025302 * scale_y, 0.0), - (-0.753279 * scale_x, -0.085571 * scale_y, 0.0), - (-1.06406 * scale_x, -0.047879 * scale_y, 0.0), - (-0.622217 * scale_x, -0.022501 * scale_y, 0.0), - (0.181 * scale_x, 0.34879 * scale_y, 0.0), - (-0.101464 * scale_x, -0.063669 * scale_y, 0.0), - (0.933064 * scale_x, 0.03001 * scale_y, 0.0), - (0.82418 * scale_x, 0.39899 * scale_y, 0.0), - (0.827377 * scale_x, 0.144945 * scale_y, 0.0)] - ] - rhandles = [ - [(-0.796079 * scale_x, 0.007121 * scale_y, 0.0), - (-0.931521 * scale_x, -0.207832 * scale_y, 0.0), - (-0.822288 * scale_x, 0.161045 * scale_y, 0.0), - (0.101464 * scale_x, -0.063671 * scale_y, 0.0), - (-0.181193 * scale_x, 0.347805 * scale_y, 0.0), - (0.622217 * scale_x, -0.022502 * scale_y, 0.0), - (1.022383 * scale_x, 0.336808 * scale_y, 0.0), - (0.741059 * scale_x, 0.199468 * scale_y, 0.0), - (0.900979 * scale_x, 0.231258 * scale_y, 0.0)] - ] - make_curve(self, context, verts, lhandles, rhandles) - - -def add_type3(self, context): - - scale_x = self.scale_x - scale_y = self.scale_y - verts = [ - [-0.78652 * scale_x, -0.070157 * scale_y, - 0.0, -0.697972 * scale_x, -0.247246 * scale_y, - 0.0, -0.953385 * scale_x, -0.002048 * scale_y, - 0.0, 0.0 * scale_x, 0.0 * scale_y, - 0.0, 0.917448 * scale_x, 0.065788 * scale_y, - 0.0, 0.448535 * scale_x, 0.515947 * scale_y, - 0.0, 0.6111 * scale_x, 0.190831 * scale_y, 0.0] - ] - lhandles = [ - [(-0.86511 * scale_x, -0.112965 * scale_y, 0.0), - (-0.61153 * scale_x, -0.156423 * scale_y, 0.0), - (-1.103589 * scale_x, -0.199934 * scale_y, 0.0), - (-0.446315 * scale_x, 0.135163 * scale_y, 0.0), - (0.669383 * scale_x, -0.254463 * scale_y, 0.0), - (0.721512 * scale_x, 0.802759 * scale_y, 0.0), - (0.466815 * scale_x, 0.112232 * scale_y, 0.0)] - ] - rhandles = [ - [(-0.707927 * scale_x, -0.027348 * scale_y, 0.0), - (-0.846662 * scale_x, -0.40347 * scale_y, 0.0), - (-0.79875 * scale_x, 0.201677 * scale_y, 0.0), - (0.446315 * scale_x, -0.135163 * scale_y, 0.0), - (1.196752 * scale_x, 0.42637 * scale_y, 0.0), - (0.289834 * scale_x, 0.349204 * scale_y, 0.0), - (0.755381 * scale_x, 0.269428 * scale_y, 0.0)] - ] - make_curve(self, context, verts, lhandles, rhandles) - - -def add_type2(self, context): - - scale_x = self.scale_x - scale_y = self.scale_y - verts = [ - [-0.719632 * scale_x, -0.08781 * scale_y, - 0.0, -0.605138 * scale_x, -0.31612 * scale_y, - 0.0, -0.935392 * scale_x, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.935392 * scale_x, 0.0, 0.0, 0.605138 * scale_x, - -0.316119 * scale_y, 0.0, 0.719632 * scale_x, -0.08781 * scale_y, 0.0] - ] - lhandles = [ - [(-0.82125 * scale_x, -0.142999 * scale_y, 0.0), - (-0.493366 * scale_x, -0.199027 * scale_y, 0.0), - (-1.129601 * scale_x, -0.25513 * scale_y, 0.0), - (-0.467584 * scale_x, 0.00044 * scale_y, 0.0), - (0.735439 * scale_x, 0.262646 * scale_y, 0.0), - (0.797395 * scale_x, -0.517531 * scale_y, 0.0), - (0.618012 * scale_x, -0.032614 * scale_y, 0.0)] - ] - rhandles = [ - [(-0.618009 * scale_x, -0.032618 * scale_y, 0.0), - (-0.797396 * scale_x, -0.517532 * scale_y, 0.0), - (-0.735445 * scale_x, 0.262669 * scale_y, 0.0), - (0.468041 * scale_x, -0.00044 * scale_y, 0.0), - (1.129616 * scale_x, -0.255119 * scale_y, 0.0), - (0.493365 * scale_x, -0.199025 * scale_y, 0.0), - (0.821249 * scale_x, -0.143004 * scale_y, 0.0)] - ] - make_curve(self, context, verts, lhandles, rhandles) - - -def add_type10(self, context): - - scale_x = self.scale_x - scale_y = self.scale_y - verts = [ - [-0.999637 * scale_x, 0.000348 * scale_y, - 0.0, 0.259532 * scale_x, -0.017841 * scale_y, - 0.0, 0.482303 * scale_x, 0.780429 * scale_y, - 0.0, 0.573183 * scale_x, 0.506898 * scale_y, 0.0], - [0.259532 * scale_x, -0.017841 * scale_y, - 0.0, 0.554919 * scale_x, -0.140918 * scale_y, - 0.0, 0.752264 * scale_x, -0.819275 * scale_y, - 0.0, 0.824152 * scale_x, -0.514881 * scale_y, 0.0] - ] - lhandles = [ - [(-1.258333 * scale_x, -0.258348 * scale_y, 0.0), - (-0.240006 * scale_x, -0.15259 * scale_y, 0.0), - (0.79037 * scale_x, 0.857575 * scale_y, 0.0), - (0.376782 * scale_x, 0.430157 * scale_y, 0.0)], - [(0.224917 * scale_x, -0.010936 * scale_y, 0.0), - (0.514858 * scale_x, -0.122809 * scale_y, 0.0), - (1.057957 * scale_x, -0.886925 * scale_y, 0.0), - (0.61945 * scale_x, -0.464285 * scale_y, 0.0)] - ] - rhandles = [ - [(-0.74094 * scale_x, 0.259045 * scale_y, 0.0), - (0.768844 * scale_x, 0.119545 * scale_y, 0.0), - (0.279083 * scale_x, 0.729538 * scale_y, 0.0), - (0.643716 * scale_x, 0.534458 * scale_y, 0.0)], - [(0.294147 * scale_x, -0.024746 * scale_y, 0.0), - (1.03646 * scale_x, -0.358598 * scale_y, 0.0), - (0.547718 * scale_x, -0.774008 * scale_y, 0.0), - (0.897665 * scale_x, -0.533051 * scale_y, 0.0)] - ] - make_curve(self, context, verts, lhandles, rhandles) - - -def add_type9(self, context): - - scale_x = self.scale_x - scale_y = self.scale_y - verts = [ - [0.260968 * scale_x, -0.668118 * scale_y, - 0.0, 0.108848 * scale_x, -0.381587 * scale_y, - 0.0, 0.537002 * scale_x, -0.77303 * scale_y, - 0.0, -0.600421 * scale_x, -0.583106 * scale_y, - 0.0, -0.600412 * scale_x, 0.583103 * scale_y, - 0.0, 0.537002 * scale_x, 0.773025 * scale_y, - 0.0, 0.108854 * scale_x, 0.381603 * scale_y, - 0.0, 0.260966 * scale_x, 0.668129 * scale_y, 0.0] - ] - lhandles = [ - [(0.387973 * scale_x, -0.594856 * scale_y, 0.0), - (-0.027835 * scale_x, -0.532386 * scale_y, 0.0), - (0.775133 * scale_x, -0.442883 * scale_y, 0.0), - (-0.291333 * scale_x, -1.064385 * scale_y, 0.0), - (-0.833382 * scale_x, 0.220321 * scale_y, 0.0), - (0.291856 * scale_x, 1.112891 * scale_y, 0.0), - (0.346161 * scale_x, 0.119777 * scale_y, 0.0), - (0.133943 * scale_x, 0.741389 * scale_y, 0.0)] - ] - rhandles = [ - [(0.133951 * scale_x, -0.741386 * scale_y, 0.0), - (0.346154 * scale_x, -0.119772 * scale_y, 0.0), - (0.291863 * scale_x, -1.112896 * scale_y, 0.0), - (-0.833407 * scale_x, -0.220324 * scale_y, 0.0), - (-0.29134 * scale_x, 1.064389 * scale_y, 0.0), - (0.775125 * scale_x, 0.442895 * scale_y, 0.0), - (-0.029107 * scale_x, 0.533819 * scale_y, 0.0), - (0.387981 * scale_x, 0.594873 * scale_y, 0.0)] - ] - make_curve(self, context, verts, lhandles, rhandles) - - -def add_type7(self, context): - - scale_x = self.scale_x - scale_y = self.scale_y - verts = [ - [-0.850431 * scale_x, -0.009091 * scale_y, - 0.0, -0.818807 * scale_x, -0.130518 * scale_y, - 0.0, -0.944931 * scale_x, 0.055065 * scale_y, 0.0, - -0.393355 * scale_x, -0.035521 * scale_y, - 0.0, 0.0 * scale_x, 0.348298 * scale_y, - 0.0, 0.393355 * scale_x, -0.035521 * scale_y, - 0.0, 0.944931 * scale_x, 0.055065 * scale_y, - 0.0, 0.818807 * scale_x, -0.130518 * scale_y, - 0.0, 0.850431 * scale_x, -0.009091 * scale_y, 0.0] - ] - lhandles = [ - [(-0.90478 * scale_x, -0.025302 * scale_y, 0.0), - (-0.753279 * scale_x, -0.085571 * scale_y, 0.0), - (-1.06406 * scale_x, -0.047879 * scale_y, 0.0), - (-0.622217 * scale_x, -0.022502 * scale_y, 0.0), - (0.181 * scale_x, 0.348791 * scale_y, 0.0), - (-0.101464 * scale_x, -0.063671 * scale_y, 0.0), - (0.822288 * scale_x, 0.161045 * scale_y, 0.0), - (0.931521 * scale_x, -0.207832 * scale_y, 0.0), - (0.796079 * scale_x, 0.007121 * scale_y, 0.0)] - ] - rhandles = [ - [(-0.796079 * scale_x, 0.007121 * scale_y, 0.0), - (-0.931521 * scale_x, -0.207832 * scale_y, 0.0), - (-0.822288 * scale_x, 0.161045 * scale_y, 0.0), - (0.101464 * scale_x, -0.063671 * scale_y, 0.0), - (-0.181193 * scale_x, 0.347805 * scale_y, 0.0), - (0.622217 * scale_x, -0.022502 * scale_y, 0.0), - (1.06406 * scale_x, -0.047879 * scale_y, 0.0), - (0.753279 * scale_x, -0.085571 * scale_y, 0.0), - (0.90478 * scale_x, -0.025302 * scale_y, 0.0)] - ] - make_curve(self, context, verts, lhandles, rhandles) - - -def add_type4(self, context): - - scale_x = self.scale_x - scale_y = self.scale_y - verts = [ - [0.072838 * scale_x, -0.071461 * scale_y, - 0.0, -0.175451 * scale_x, -0.130711 * scale_y, - 0.0, 0.207269 * scale_x, 0.118064 * scale_y, - 0.0, 0 * scale_x, -1.0 * scale_y, 0.0] - ] - lhandles = [ - [(0.042135 * scale_x, 0.039756 * scale_y, 0), - (-0.086769 * scale_x, -0.265864 * scale_y, 0), - (0.002865 * scale_x, 0.364657 * scale_y, 0), - (0.233116 * scale_x, -0.596115 * scale_y, 0)] - ] - rhandles = [ - [(0.103542 * scale_x, -0.182683 * scale_y, 0), - (-0.327993 * scale_x, 0.101765 * scale_y, 0), - (0.417702 * scale_x, -0.135803 * scale_y, 0), - (-0.233116 * scale_x, -1.403885 * scale_y, 0)] - ] - make_curve(self, context, verts, lhandles, rhandles) - - -def add_type1(self, context): - - scale_x = self.scale_x - scale_y = self.scale_y - verts = [ - [-0.71753 * scale_x, -0.08781 * scale_y, - 0, -0.60337 * scale_x, -0.31612 * scale_y, 0, - -0.93266 * scale_x, 0, 0, 0, 0, 0, 0.93266 * scale_x, - 0, 0, 0.60337 * scale_x, 0.31612 * scale_y, - 0, 0.71753 * scale_x, 0.08781 * scale_y, 0] - ] - lhandles = [ - [(-0.81885 * scale_x, -0.143002 * scale_y, 0), - (-0.491926 * scale_x, -0.199026 * scale_y, 0), - (-1.126316 * scale_x, -0.255119 * scale_y, 0), - (-0.446315 * scale_x, 0.135164 * scale_y, 0), - (0.733297 * scale_x, -0.26265 * scale_y, 0), - (0.795065 * scale_x, 0.517532 * scale_y, 0), - (0.616204 * scale_x, 0.03262 * scale_y, 0)] - ] - rhandles = [ - [(-0.616204 * scale_x, -0.032618 * scale_y, 0), - (-0.795067 * scale_x, -0.517532 * scale_y, 0), - (-0.733297 * scale_x, 0.262651 * scale_y, 0), - (0.446315 * scale_x, -0.135163 * scale_y, 0), - (1.126316 * scale_x, 0.255119 * scale_y, 0), - (0.491924 * scale_x, 0.199026 * scale_y, 0), - (0.81885 * scale_x, 0.143004 * scale_y, 0)] - ] - make_curve(self, context, verts, lhandles, rhandles) - - -def make_curve(self, context, verts, lh, rh): - - types = self.types - curve_data = bpy.data.curves.new(name='CurlyCurve', type='CURVE') - curve_data.dimensions = '3D' - - for p in range(len(verts)): - c = 0 - spline = curve_data.splines.new(type='BEZIER') - spline.bezier_points.add(len(verts[p]) / 3 - 1) - spline.bezier_points.foreach_set('co', verts[p]) - - for bp in spline.bezier_points: - bp.handle_left_type = 'ALIGNED' - bp.handle_right_type = 'ALIGNED' - bp.handle_left.xyz = lh[p][c] - bp.handle_right.xyz = rh[p][c] - c += 1 - # something weird with this one - if types == 1 or types == 2 or types == 3: - spline.bezier_points[3].handle_left.xyz = lh[p][3] - object_data_add(context, curve_data, operator=self) - - -class add_curlycurve(Operator, AddObjectHelper): - bl_idname = "curve.curlycurve" - bl_label = "Add Curly Curve" - bl_description = "Create a Curly Curve" - bl_options = {'REGISTER', 'UNDO'} - - types = IntProperty( - name="Type", - description="Type of curly curve", - default=1, - min=1, max=10 - ) - scale_x = FloatProperty( - name="Scale X", - description="Scale on X axis", - default=1.0 - ) - scale_y = FloatProperty( - name="Scale Y", - description="Scale on Y axis", - default=1.0 - ) - - def draw(self, context): - layout = self.layout - - col = layout.column(align=True) - # AddObjectHelper props - col.prop(self, "view_align") - col.prop(self, "location") - col.prop(self, "rotation") - - col = layout.column() - col.label("Curve:") - col.prop(self, "types") - - col = layout.column(align=True) - col.label("Resize:") - col.prop(self, "scale_x") - col.prop(self, "scale_y") - - def execute(self, context): - if self.types == 1: - add_type1(self, context) - if self.types == 2: - add_type2(self, context) - if self.types == 3: - add_type3(self, context) - if self.types == 4: - add_type4(self, context) - if self.types == 5: - add_type5(self, context) - if self.types == 6: - add_type6(self, context) - if self.types == 7: - add_type7(self, context) - if self.types == 8: - add_type8(self, context) - if self.types == 9: - add_type9(self, context) - if self.types == 10: - add_type10(self, context) - - return {'FINISHED'} - - -# Registration - -def add_curlycurve_button(self, context): - self.layout.operator( - add_curlycurve.bl_idname, - text="Add Curly Curve", - icon='PLUGIN' - ) - - -def register(): - bpy.utils.register_class(add_curlycurve) - bpy.types.INFO_MT_curve_add.append(add_curlycurve_button) - - -def unregister(): - bpy.utils.unregister_class(add_curlycurve) - bpy.types.INFO_MT_curve_add.remove(add_curlycurve_button) - - -if __name__ == "__main__": - register() diff --git a/tests/test_helpers/addons/add_curve_extra_objects/add_curve_simple.py b/tests/test_helpers/addons/add_curve_extra_objects/add_curve_simple.py deleted file mode 100644 index 3e85fa1..0000000 --- a/tests/test_helpers/addons/add_curve_extra_objects/add_curve_simple.py +++ /dev/null @@ -1,1747 +0,0 @@ -# ##### 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 ##### - -bl_info = { - "name": "Simple Curve", - "author": "Spivak Vladimir (http://cwolf3d.korostyshev.net)", - "version": (1, 5, 3), - "blender": (2, 6, 9), - "location": "View3D > Add > Curve", - "description": "Adds Simple Curve", - "warning": "", - "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/" - "Py/Scripts/Curve/Simple_curves", - "category": "Add Curve"} - - -# ------------------------------------------------------------ - -import bpy -from bpy.types import ( - Operator, - Menu, - Panel, - PropertyGroup, - ) -from bpy.props import ( - BoolProperty, - EnumProperty, - FloatProperty, - FloatVectorProperty, - IntProperty, - StringProperty, - PointerProperty, - ) -from mathutils import ( - Vector, - Matrix, - ) -from math import ( - sin, asin, sqrt, - acos, cos, pi, - radians, tan, - hypot, - ) -# from bpy_extras.object_utils import * - - -# ------------------------------------------------------------ -# Point: - -def SimplePoint(): - newpoints = [] - - newpoints.append([0.0, 0.0, 0.0]) - - return newpoints - - -# ------------------------------------------------------------ -# Line: - -def SimpleLine(c1=[0.0, 0.0, 0.0], c2=[2.0, 2.0, 2.0]): - newpoints = [] - - c3 = Vector(c2) - Vector(c1) - newpoints.append([0.0, 0.0, 0.0]) - newpoints.append([c3[0], c3[1], c3[2]]) - - return newpoints - - -# ------------------------------------------------------------ -# Angle: - -def SimpleAngle(length=1.0, angle=45.0): - newpoints = [] - - angle = radians(angle) - newpoints.append([length, 0.0, 0.0]) - newpoints.append([0.0, 0.0, 0.0]) - newpoints.append([length * cos(angle), length * sin(angle), 0.0]) - - return newpoints - - -# ------------------------------------------------------------ -# Distance: - -def SimpleDistance(length=1.0, center=True): - newpoints = [] - - if center: - newpoints.append([-length / 2, 0.0, 0.0]) - newpoints.append([length / 2, 0.0, 0.0]) - else: - newpoints.append([0.0, 0.0, 0.0]) - newpoints.append([length, 0.0, 0.0]) - - return newpoints - - -# ------------------------------------------------------------ -# Circle: - -def SimpleCircle(sides=4, radius=1.0): - newpoints = [] - - angle = radians(360) / sides - newpoints.append([radius, 0, 0]) - j = 1 - while j < sides: - t = angle * j - x = cos(t) * radius - y = sin(t) * radius - newpoints.append([x, y, 0]) - j += 1 - - return newpoints - - -# ------------------------------------------------------------ -# Ellipse: - -def SimpleEllipse(a=2.0, b=1.0): - newpoints = [] - - newpoints.append([a, 0.0, 0.0]) - newpoints.append([0.0, b, 0.0]) - newpoints.append([-a, 0.0, 0.0]) - newpoints.append([0.0, -b, 0.0]) - - return newpoints - - -# ------------------------------------------------------------ -# Arc: - -def SimpleArc(sides=0, radius=1.0, startangle=0.0, endangle=45.0): - newpoints = [] - - startangle = radians(startangle) - endangle = radians(endangle) - sides += 1 - - angle = (endangle - startangle) / sides - x = cos(startangle) * radius - y = sin(startangle) * radius - newpoints.append([x, y, 0]) - j = 1 - while j < sides: - t = angle * j - x = cos(t + startangle) * radius - y = sin(t + startangle) * radius - newpoints.append([x, y, 0]) - j += 1 - x = cos(endangle) * radius - y = sin(endangle) * radius - newpoints.append([x, y, 0]) - - return newpoints - - -# ------------------------------------------------------------ -# Sector: - -def SimpleSector(sides=0, radius=1.0, startangle=0.0, endangle=45.0): - newpoints = [] - - startangle = radians(startangle) - endangle = radians(endangle) - sides += 1 - - newpoints.append([0, 0, 0]) - angle = (endangle - startangle) / sides - x = cos(startangle) * radius - y = sin(startangle) * radius - newpoints.append([x, y, 0]) - j = 1 - while j < sides: - t = angle * j - x = cos(t + startangle) * radius - y = sin(t + startangle) * radius - newpoints.append([x, y, 0]) - j += 1 - x = cos(endangle) * radius - y = sin(endangle) * radius - newpoints.append([x, y, 0]) - - return newpoints - - -# ------------------------------------------------------------ -# Segment: - -def SimpleSegment(sides=0, a=2.0, b=1.0, startangle=0.0, endangle=45.0): - newpoints = [] - - startangle = radians(startangle) - endangle = radians(endangle) - sides += 1 - - angle = (endangle - startangle) / sides - x = cos(startangle) * a - y = sin(startangle) * a - newpoints.append([x, y, 0]) - j = 1 - while j < sides: - t = angle * j - x = cos(t + startangle) * a - y = sin(t + startangle) * a - newpoints.append([x, y, 0]) - j += 1 - x = cos(endangle) * a - y = sin(endangle) * a - newpoints.append([x, y, 0]) - - x = cos(endangle) * b - y = sin(endangle) * b - newpoints.append([x, y, 0]) - j = sides - while j > 0: - t = angle * j - x = cos(t + startangle) * b - y = sin(t + startangle) * b - newpoints.append([x, y, 0]) - j -= 1 - x = cos(startangle) * b - y = sin(startangle) * b - newpoints.append([x, y, 0]) - - return newpoints - - -# ------------------------------------------------------------ -# Rectangle: - -def SimpleRectangle(width=2.0, length=2.0, rounded=0.0, center=True): - newpoints = [] - - r = rounded / 2 - - if center: - x = width / 2 - y = length / 2 - if rounded != 0.0: - newpoints.append([-x + r, y, 0.0]) - newpoints.append([x - r, y, 0.0]) - newpoints.append([x, y - r, 0.0]) - newpoints.append([x, -y + r, 0.0]) - newpoints.append([x - r, -y, 0.0]) - newpoints.append([-x + r, -y, 0.0]) - newpoints.append([-x, -y + r, 0.0]) - newpoints.append([-x, y - r, 0.0]) - else: - newpoints.append([-x, y, 0.0]) - newpoints.append([x, y, 0.0]) - newpoints.append([x, -y, 0.0]) - newpoints.append([-x, -y, 0.0]) - - else: - x = width - y = length - if rounded != 0.0: - newpoints.append([r, y, 0.0]) - newpoints.append([x - r, y, 0.0]) - newpoints.append([x, y - r, 0.0]) - newpoints.append([x, r, 0.0]) - newpoints.append([x - r, 0.0, 0.0]) - newpoints.append([r, 0.0, 0.0]) - newpoints.append([0.0, r, 0.0]) - newpoints.append([0.0, y - r, 0.0]) - else: - newpoints.append([0.0, 0.0, 0.0]) - newpoints.append([0.0, y, 0.0]) - newpoints.append([x, y, 0.0]) - newpoints.append([x, 0.0, 0.0]) - - return newpoints - - -# ------------------------------------------------------------ -# Rhomb: - -def SimpleRhomb(width=2.0, length=2.0, center=True): - newpoints = [] - x = width / 2 - y = length / 2 - - if center: - newpoints.append([-x, 0.0, 0.0]) - newpoints.append([0.0, y, 0.0]) - newpoints.append([x, 0.0, 0.0]) - newpoints.append([0.0, -y, 0.0]) - else: - newpoints.append([x, 0.0, 0.0]) - newpoints.append([0.0, y, 0.0]) - newpoints.append([x, length, 0.0]) - newpoints.append([width, y, 0.0]) - - return newpoints - - -# ------------------------------------------------------------ -# Polygon: - -def SimplePolygon(sides=3, radius=1.0): - newpoints = [] - angle = radians(360.0) / sides - j = 0 - - while j < sides: - t = angle * j - x = sin(t) * radius - y = cos(t) * radius - newpoints.append([x, y, 0.0]) - j += 1 - - return newpoints - - -# ------------------------------------------------------------ -# Polygon_ab: - -def SimplePolygon_ab(sides=3, a=2.0, b=1.0): - newpoints = [] - angle = radians(360.0) / sides - j = 0 - - while j < sides: - t = angle * j - x = sin(t) * a - y = cos(t) * b - newpoints.append([x, y, 0.0]) - j += 1 - - return newpoints - - -# ------------------------------------------------------------ -# Trapezoid: - -def SimpleTrapezoid(a=2.0, b=1.0, h=1.0, center=True): - newpoints = [] - x = a / 2 - y = b / 2 - r = h / 2 - - if center: - newpoints.append([-x, -r, 0.0]) - newpoints.append([-y, r, 0.0]) - newpoints.append([y, r, 0.0]) - newpoints.append([x, -r, 0.0]) - - else: - newpoints.append([0.0, 0.0, 0.0]) - newpoints.append([x - y, h, 0.0]) - newpoints.append([x + y, h, 0.0]) - newpoints.append([a, 0.0, 0.0]) - - return newpoints - - -# ------------------------------------------------------------ -# calculates the matrix for the new object -# depending on user pref - -def align_matrix(context, location): - loc = Matrix.Translation(location) - obj_align = context.user_preferences.edit.object_align - if (context.space_data.type == 'VIEW_3D' and - obj_align == 'VIEW'): - rot = context.space_data.region_3d.view_matrix.to_3x3().inverted().to_4x4() - else: - rot = Matrix() - align_matrix = loc * rot - - return align_matrix - - -# ------------------------------------------------------------ -# Main Function - -def main(context, self, align_matrix): - # deselect all objects - bpy.ops.object.select_all(action='DESELECT') - - # create object - name = self.Simple_Type # Type as name - - # create curve - scene = bpy.context.scene - newCurve = bpy.data.curves.new(name, type='CURVE') # curvedatablock - newSpline = newCurve.splines.new('BEZIER') # spline - - # set curveOptions - newCurve.dimensions = self.shape - newSpline.use_endpoint_u = True - - sides = abs(int((self.Simple_endangle - self.Simple_startangle) / 90)) - - # get verts - if self.Simple_Type == 'Point': - verts = SimplePoint() - newSpline.use_cyclic_u = False - - if self.Simple_Type == 'Line': - verts = SimpleLine(self.Simple_startlocation, self.Simple_endlocation) - newSpline.use_cyclic_u = False - newCurve.dimensions = '3D' - - if self.Simple_Type == 'Distance': - verts = SimpleDistance(self.Simple_length, self.Simple_center) - newSpline.use_cyclic_u = False - - if self.Simple_Type == 'Angle': - verts = SimpleAngle(self.Simple_length, self.Simple_angle) - newSpline.use_cyclic_u = False - - if self.Simple_Type == 'Circle': - if self.Simple_sides < 4: - self.Simple_sides = 4 - verts = SimpleCircle(self.Simple_sides, self.Simple_radius) - newSpline.use_cyclic_u = True - - if self.Simple_Type == 'Ellipse': - verts = SimpleEllipse(self.Simple_a, self.Simple_b) - newSpline.use_cyclic_u = True - - if self.Simple_Type == 'Arc': - if self.Simple_sides < sides: - self.Simple_sides = sides - if self.Simple_radius == 0: - return {'FINISHED'} - verts = SimpleArc( - self.Simple_sides, self.Simple_radius, - self.Simple_startangle, self.Simple_endangle - ) - newSpline.use_cyclic_u = False - - if self.Simple_Type == 'Sector': - if self.Simple_sides < sides: - self.Simple_sides = sides - - if self.Simple_radius == 0: - return {'FINISHED'} - - verts = SimpleSector( - self.Simple_sides, self.Simple_radius, - self.Simple_startangle, self.Simple_endangle - ) - newSpline.use_cyclic_u = True - - if self.Simple_Type == 'Segment': - if self.Simple_sides < sides: - self.Simple_sides = sides - if self.Simple_a == 0 or self.Simple_b == 0: - return {'FINISHED'} - verts = SimpleSegment( - self.Simple_sides, self.Simple_a, self.Simple_b, - self.Simple_startangle, self.Simple_endangle - ) - newSpline.use_cyclic_u = True - - if self.Simple_Type == 'Rectangle': - verts = SimpleRectangle( - self.Simple_width, self.Simple_length, - self.Simple_rounded, self.Simple_center - ) - newSpline.use_cyclic_u = True - - if self.Simple_Type == 'Rhomb': - verts = SimpleRhomb( - self.Simple_width, self.Simple_length, self.Simple_center - ) - newSpline.use_cyclic_u = True - - if self.Simple_Type == 'Polygon': - if self.Simple_sides < 3: - self.Simple_sides = 3 - verts = SimplePolygon( - self.Simple_sides, self.Simple_radius - ) - newSpline.use_cyclic_u = True - - if self.Simple_Type == 'Polygon_ab': - if self.Simple_sides < 3: - self.Simple_sides = 3 - verts = SimplePolygon_ab( - self.Simple_sides, self.Simple_a, self.Simple_b - ) - newSpline.use_cyclic_u = True - - if self.Simple_Type == 'Trapezoid': - verts = SimpleTrapezoid( - self.Simple_a, self.Simple_b, self.Simple_h, self.Simple_center - ) - newSpline.use_cyclic_u = True - - vertArray = [] - for v in verts: - vertArray += v - - newSpline.bezier_points.add(int(len(vertArray) * 0.333333333)) - newSpline.bezier_points.foreach_set('co', vertArray) - - # create object with newCurve - SimpleCurve = bpy.data.objects.new(name, newCurve) # object - scene.objects.link(SimpleCurve) # place in active scene - SimpleCurve.select = True # set as selected - scene.objects.active = SimpleCurve # set as active - SimpleCurve.matrix_world = align_matrix # apply matrix - SimpleCurve.rotation_euler = self.Simple_rotation_euler - - all_points = [p for p in newSpline.bezier_points] - d = 2 * 0.27606262 - n = 0 - for p in all_points: - p.handle_right_type = 'VECTOR' - p.handle_left_type = 'VECTOR' - n += 1 - - if self.Simple_Type == 'Circle' or self.Simple_Type == 'Arc' or \ - self.Simple_Type == 'Sector' or self.Simple_Type == 'Segment' or \ - self.Simple_Type == 'Ellipse': - - for p in all_points: - p.handle_right_type = 'FREE' - p.handle_left_type = 'FREE' - - if self.Simple_Type == 'Circle': - i = 0 - for p1 in all_points: - if i != n - 1: - p2 = all_points[i + 1] - u1 = asin(p1.co.y / self.Simple_radius) - u2 = asin(p2.co.y / self.Simple_radius) - if p1.co.x > 0 and p2.co.x < 0: - u1 = acos(p1.co.x / self.Simple_radius) - u2 = acos(p2.co.x / self.Simple_radius) - elif p1.co.x < 0 and p2.co.x > 0: - u1 = acos(p1.co.x / self.Simple_radius) - u2 = acos(p2.co.x / self.Simple_radius) - u = u2 - u1 - if u < 0: - u = -u - l = 4 / 3 * tan(1 / 4 * u) * self.Simple_radius - v1 = Vector((-p1.co.y, p1.co.x, 0)) - v1.normalize() - v2 = Vector((-p2.co.y, p2.co.x, 0)) - v2.normalize() - vh1 = v1 * l - vh2 = v2 * l - v1 = Vector((p1.co.x, p1.co.y, 0)) + vh1 - v2 = Vector((p2.co.x, p2.co.y, 0)) - vh2 - p1.handle_right = v1 - p2.handle_left = v2 - if i == n - 1: - p2 = all_points[0] - u1 = asin(p1.co.y / self.Simple_radius) - u2 = asin(p2.co.y / self.Simple_radius) - if p1.co.x > 0 and p2.co.x < 0: - u1 = acos(p1.co.x / self.Simple_radius) - u2 = acos(p2.co.x / self.Simple_radius) - elif p1.co.x < 0 and p2.co.x > 0: - u1 = acos(p1.co.x / self.Simple_radius) - u2 = acos(p2.co.x / self.Simple_radius) - u = u2 - u1 - if u < 0: - u = -u - l = 4 / 3 * tan(1 / 4 * u) * self.Simple_radius - v1 = Vector((-p1.co.y, p1.co.x, 0)) - v1.normalize() - v2 = Vector((-p2.co.y, p2.co.x, 0)) - v2.normalize() - vh1 = v1 * l - vh2 = v2 * l - v1 = Vector((p1.co.x, p1.co.y, 0)) + vh1 - v2 = Vector((p2.co.x, p2.co.y, 0)) - vh2 - p1.handle_right = v1 - p2.handle_left = v2 - i += 1 - - if self.Simple_Type == 'Ellipse': - all_points[0].handle_right = Vector((self.Simple_a, self.Simple_b * d, 0)) - all_points[0].handle_left = Vector((self.Simple_a, -self.Simple_b * d, 0)) - all_points[1].handle_right = Vector((-self.Simple_a * d, self.Simple_b, 0)) - all_points[1].handle_left = Vector((self.Simple_a * d, self.Simple_b, 0)) - all_points[2].handle_right = Vector((-self.Simple_a, -self.Simple_b * d, 0)) - all_points[2].handle_left = Vector((-self.Simple_a, self.Simple_b * d, 0)) - all_points[3].handle_right = Vector((self.Simple_a * d, -self.Simple_b, 0)) - all_points[3].handle_left = Vector((-self.Simple_a * d, -self.Simple_b, 0)) - - if self.Simple_Type == 'Arc': - i = 0 - for p1 in all_points: - if i != n - 1: - p2 = all_points[i + 1] - u1 = asin(p1.co.y / self.Simple_radius) - u2 = asin(p2.co.y / self.Simple_radius) - if p1.co.x > 0 and p2.co.x < 0: - u1 = acos(p1.co.x / self.Simple_radius) - u2 = acos(p2.co.x / self.Simple_radius) - elif p1.co.x < 0 and p2.co.x > 0: - u1 = acos(p1.co.x / self.Simple_radius) - u2 = acos(p2.co.x / self.Simple_radius) - u = u2 - u1 - if u < 0: - u = -u - l = 4 / 3 * tan(1 / 4 * u) * self.Simple_radius - v1 = Vector((-p1.co.y, p1.co.x, 0)) - v1.normalize() - v2 = Vector((-p2.co.y, p2.co.x, 0)) - v2.normalize() - vh1 = v1 * l - vh2 = v2 * l - if self.Simple_startangle < self.Simple_endangle: - v1 = Vector((p1.co.x, p1.co.y, 0)) + vh1 - v2 = Vector((p2.co.x, p2.co.y, 0)) - vh2 - p1.handle_right = v1 - p2.handle_left = v2 - else: - v1 = Vector((p1.co.x, p1.co.y, 0)) - vh1 - v2 = Vector((p2.co.x, p2.co.y, 0)) + vh2 - p1.handle_right = v1 - p2.handle_left = v2 - i += 1 - - if self.Simple_Type == 'Sector': - i = 0 - for p1 in all_points: - if i == 0: - p1.handle_right_type = 'VECTOR' - p1.handle_left_type = 'VECTOR' - elif i != n - 1: - p2 = all_points[i + 1] - u1 = asin(p1.co.y / self.Simple_radius) - u2 = asin(p2.co.y / self.Simple_radius) - if p1.co.x > 0 and p2.co.x < 0: - u1 = acos(p1.co.x / self.Simple_radius) - u2 = acos(p2.co.x / self.Simple_radius) - elif p1.co.x < 0 and p2.co.x > 0: - u1 = acos(p1.co.x / self.Simple_radius) - u2 = acos(p2.co.x / self.Simple_radius) - u = u2 - u1 - if u < 0: - u = -u - l = 4 / 3 * tan(1 / 4 * u) * self.Simple_radius - v1 = Vector((-p1.co.y, p1.co.x, 0)) - v1.normalize() - v2 = Vector((-p2.co.y, p2.co.x, 0)) - v2.normalize() - vh1 = v1 * l - vh2 = v2 * l - if self.Simple_startangle < self.Simple_endangle: - v1 = Vector((p1.co.x, p1.co.y, 0)) + vh1 - v2 = Vector((p2.co.x, p2.co.y, 0)) - vh2 - p1.handle_right = v1 - p2.handle_left = v2 - else: - v1 = Vector((p1.co.x, p1.co.y, 0)) - vh1 - v2 = Vector((p2.co.x, p2.co.y, 0)) + vh2 - p1.handle_right = v1 - p2.handle_left = v2 - i += 1 - - if self.Simple_Type == 'Segment': - i = 0 - for p1 in all_points: - if i < n / 2 - 1: - p2 = all_points[i + 1] - u1 = asin(p1.co.y / self.Simple_a) - u2 = asin(p2.co.y / self.Simple_a) - if p1.co.x > 0 and p2.co.x < 0: - u1 = acos(p1.co.x / self.Simple_a) - u2 = acos(p2.co.x / self.Simple_a) - elif p1.co.x < 0 and p2.co.x > 0: - u1 = acos(p1.co.x / self.Simple_a) - u2 = acos(p2.co.x / self.Simple_a) - u = u2 - u1 - if u < 0: - u = -u - l = 4 / 3 * tan(1 / 4 * u) * self.Simple_a - v1 = Vector((-p1.co.y, p1.co.x, 0)) - v1.normalize() - v2 = Vector((-p2.co.y, p2.co.x, 0)) - v2.normalize() - vh1 = v1 * l - vh2 = v2 * l - if self.Simple_startangle < self.Simple_endangle: - v1 = Vector((p1.co.x, p1.co.y, 0)) + vh1 - v2 = Vector((p2.co.x, p2.co.y, 0)) - vh2 - p1.handle_right = v1 - p2.handle_left = v2 - else: - v1 = Vector((p1.co.x, p1.co.y, 0)) - vh1 - v2 = Vector((p2.co.x, p2.co.y, 0)) + vh2 - p1.handle_right = v1 - p2.handle_left = v2 - elif i != n / 2 - 1 and i != n - 1: - p2 = all_points[i + 1] - u1 = asin(p1.co.y / self.Simple_b) - u2 = asin(p2.co.y / self.Simple_b) - if p1.co.x > 0 and p2.co.x < 0: - u1 = acos(p1.co.x / self.Simple_b) - u2 = acos(p2.co.x / self.Simple_b) - elif p1.co.x < 0 and p2.co.x > 0: - u1 = acos(p1.co.x / self.Simple_b) - u2 = acos(p2.co.x / self.Simple_b) - u = u2 - u1 - if u < 0: - u = -u - l = 4 / 3 * tan(1 / 4 * u) * self.Simple_b - v1 = Vector((-p1.co.y, p1.co.x, 0)) - v1.normalize() - v2 = Vector((-p2.co.y, p2.co.x, 0)) - v2.normalize() - vh1 = v1 * l - vh2 = v2 * l - if self.Simple_startangle < self.Simple_endangle: - v1 = Vector((p1.co.x, p1.co.y, 0)) - vh1 - v2 = Vector((p2.co.x, p2.co.y, 0)) + vh2 - p1.handle_right = v1 - p2.handle_left = v2 - else: - v1 = Vector((p1.co.x, p1.co.y, 0)) + vh1 - v2 = Vector((p2.co.x, p2.co.y, 0)) - vh2 - p1.handle_right = v1 - p2.handle_left = v2 - - i += 1 - all_points[0].handle_left_type = 'VECTOR' - all_points[n - 1].handle_right_type = 'VECTOR' - all_points[int(n / 2) - 1].handle_right_type = 'VECTOR' - all_points[int(n / 2)].handle_left_type = 'VECTOR' - - SimpleCurve.s_curve.Simple = True - SimpleCurve.s_curve.Simple_Change = False - SimpleCurve.s_curve.Simple_Type = self.Simple_Type - SimpleCurve.s_curve.Simple_startlocation = self.Simple_startlocation - SimpleCurve.s_curve.Simple_endlocation = self.Simple_endlocation - SimpleCurve.s_curve.Simple_a = self.Simple_a - SimpleCurve.s_curve.Simple_b = self.Simple_b - SimpleCurve.s_curve.Simple_h = self.Simple_h - SimpleCurve.s_curve.Simple_angle = self.Simple_angle - SimpleCurve.s_curve.Simple_startangle = self.Simple_startangle - SimpleCurve.s_curve.Simple_endangle = self.Simple_endangle - SimpleCurve.s_curve.Simple_rotation_euler = self.Simple_rotation_euler - SimpleCurve.s_curve.Simple_sides = self.Simple_sides - SimpleCurve.s_curve.Simple_radius = self.Simple_radius - SimpleCurve.s_curve.Simple_center = self.Simple_center - SimpleCurve.s_curve.Simple_width = self.Simple_width - SimpleCurve.s_curve.Simple_length = self.Simple_length - SimpleCurve.s_curve.Simple_rounded = self.Simple_rounded - - bpy.ops.object.mode_set(mode='EDIT', toggle=True) - bpy.ops.curve.select_all(action='SELECT') - bpy.ops.object.mode_set(mode='OBJECT', toggle=True) - - return - - -# ------------------------------------------------------------ -# Delete simple curve - -def SimpleDelete(name): - if bpy.ops.object.mode_set.poll(): - bpy.ops.object.mode_set(mode='OBJECT') - - bpy.context.scene.objects.active = bpy.data.objects[name] - bpy.ops.object.delete() - - return - - -# ------------------------------------------------------------ -# Simple operator - -class Simple(Operator): - bl_idname = "curve.simple" - bl_label = "Simple Curve" - bl_description = "Construct a Simple Curve" - bl_options = {'REGISTER', 'UNDO'} - - # align_matrix for the invoke - align_matrix = Matrix() - - # change properties - Simple = BoolProperty( - name="Simple", - default=True, - description="Simple Curve" - ) - Simple_Change = BoolProperty( - name="Change", - default=False, - description="Change Simple Curve" - ) - Simple_Delete = StringProperty( - name="Delete", - description="Delete Simple Curve" - ) - # general properties - Types = [('Point', "Point", "Construct a Point"), - ('Line', "Line", "Construct a Line"), - ('Distance', "Distance", "Contruct a two point Distance"), - ('Angle', "Angle", "Construct an Angle"), - ('Circle', "Circle", "Construct a Circle"), - ('Ellipse', "Ellipse", "Construct an Ellipse"), - ('Arc', "Arc", "Construct an Arc"), - ('Sector', "Sector", "Construct a Sector"), - ('Segment', "Segment", "Construct a Segment"), - ('Rectangle', "Rectangle", "Construct a Rectangle"), - ('Rhomb', "Rhomb", "Construct a Rhomb"), - ('Polygon', "Polygon", "Construct a Polygon"), - ('Polygon_ab', "Polygon ab", "Construct a Polygon ab"), - ('Trapezoid', "Trapezoid", "Construct a Trapezoid") - ] - Simple_Type = EnumProperty( - name="Type", - description="Form of Curve to create", - items=Types - ) - # Line properties - Simple_startlocation = FloatVectorProperty( - name="", - description="Start location", - default=(0.0, 0.0, 0.0), - subtype='TRANSLATION' - ) - Simple_endlocation = FloatVectorProperty( - name="", - description="End location", - default=(2.0, 2.0, 2.0), - subtype='TRANSLATION' - ) - Simple_rotation_euler = FloatVectorProperty( - name="", - description="Rotation", - default=(0.0, 0.0, 0.0), - subtype='EULER' - ) - # Trapezoid properties - Simple_a = FloatProperty( - name="Side a", - default=2.0, - min=0.0, soft_min=0.0, - unit='LENGTH', - description="a side Value" - ) - Simple_b = FloatProperty( - name="Side b", - default=1.0, - min=0.0, soft_min=0.0, - unit='LENGTH', - description="b side Value" - ) - Simple_h = FloatProperty( - name="Height", - default=1.0, - unit='LENGTH', - description="Height of the Trapezoid - distance between a and b" - ) - Simple_angle = FloatProperty( - name="Angle", - default=45.0, - description="Angle" - ) - Simple_startangle = FloatProperty( - name="Start angle", - default=0.0, - min=-360.0, soft_min=-360.0, - max=360.0, soft_max=360.0, - description="Start angle" - ) - Simple_endangle = FloatProperty( - name="End angle", - default=45.0, - min=-360.0, soft_min=-360.0, - max=360.0, soft_max=360.0, - description="End angle" - ) - Simple_sides = IntProperty( - name="Sides", - default=3, - min=0, soft_min=0, - description="Sides" - ) - Simple_radius = FloatProperty( - name="Radius", - default=1.0, - min=0.0, soft_min=0.0, - unit='LENGTH', - description="Radius" - ) - Simple_center = BoolProperty( - name="Length center", - default=True, - description="Length center" - ) - - Angle_types = [('Degrees', "Degrees", "Use Degrees"), - ('Radians', "Radians", "Use Radians")] - Simple_degrees_or_radians = EnumProperty( - name="Degrees or radians", - description="Degrees or radians", - items=Angle_types - ) - # Rectangle properties - Simple_width = FloatProperty( - name="Width", - default=2.0, - min=0.0, soft_min=0, - unit='LENGTH', - description="Width" - ) - Simple_length = FloatProperty( - name="Length", - default=2.0, - min=0.0, soft_min=0.0, - unit='LENGTH', - description="Length" - ) - Simple_rounded = FloatProperty( - name="Rounded", - default=0.0, - min=0.0, soft_min=0.0, - unit='LENGTH', - description="Rounded corners" - ) - # Curve Options - shapeItems = [ - ('2D', "2D", "2D shape Curve"), - ('3D', "3D", "3D shape Curve")] - shape = EnumProperty( - name="2D / 3D", - items=shapeItems, - description="2D or 3D Curve" - ) - - def draw(self, context): - layout = self.layout - - # general options - col = layout.column() - col.prop(self, "Simple_Type") - - l = 0 - s = 0 - - if self.Simple_Type == 'Line': - box = layout.box() - col = box.column(align=True) - col.label(text=self.Simple_Type + " Options:") - col.prop(self, "Simple_endlocation") - v = Vector(self.Simple_endlocation) - Vector(self.Simple_startlocation) - l = v.length - - if self.Simple_Type == 'Distance': - box = layout.box() - col = box.column(align=True) - col.label(text=self.Simple_Type + " Options:") - col.prop(self, "Simple_length") - col.prop(self, "Simple_center") - l = self.Simple_length - - if self.Simple_Type == 'Angle': - box = layout.box() - col = box.column(align=True) - col.label(text=self.Simple_Type + " Options:") - col.prop(self, "Simple_length") - col.prop(self, "Simple_angle") - - row = layout.row() - row.prop(self, "Simple_degrees_or_radians", expand=True) - - if self.Simple_Type == 'Circle': - box = layout.box() - col = box.column(align=True) - col.label(text=self.Simple_Type + " Options:") - col.prop(self, "Simple_sides") - col.prop(self, "Simple_radius") - - l = 2 * pi * abs(self.Simple_radius) - s = pi * self.Simple_radius * self.Simple_radius - - if self.Simple_Type == 'Ellipse': - box = layout.box() - col = box.column(align=True) - col.label(text=self.Simple_Type + " Options:") - col.prop(self, "Simple_a", text="Radius a") - col.prop(self, "Simple_b", text="Radius b") - - l = pi * (3 * (self.Simple_a + self.Simple_b) - - sqrt((3 * self.Simple_a + self.Simple_b) * - (self.Simple_a + 3 * self.Simple_b))) - - s = pi * abs(self.Simple_b) * abs(self.Simple_a) - - if self.Simple_Type == 'Arc': - box = layout.box() - col = box.column(align=True) - col.label(text=self.Simple_Type + " Options:") - col.prop(self, "Simple_sides") - col.prop(self, "Simple_radius") - - col = box.column(align=True) - col.prop(self, "Simple_startangle") - col.prop(self, "Simple_endangle") - row = layout.row() - row.prop(self, "Simple_degrees_or_radians", expand=True) - - l = abs(pi * self.Simple_radius * (self.Simple_endangle - self.Simple_startangle) / 180) - - if self.Simple_Type == 'Sector': - box = layout.box() - col = box.column(align=True) - col.label(text=self.Simple_Type + " Options:") - col.prop(self, "Simple_sides") - col.prop(self, "Simple_radius") - - col = box.column(align=True) - col.prop(self, "Simple_startangle") - col.prop(self, "Simple_endangle") - row = layout.row() - row.prop(self, "Simple_degrees_or_radians", expand=True) - - l = abs(pi * self.Simple_radius * - (self.Simple_endangle - self.Simple_startangle) / 180) + self.Simple_radius * 2 - - s = pi * self.Simple_radius * self.Simple_radius * \ - abs(self.Simple_endangle - self.Simple_startangle) / 360 - - if self.Simple_Type == 'Segment': - box = layout.box() - col = box.column(align=True) - col.label(text=self.Simple_Type + " Options:") - col.prop(self, "Simple_sides") - col.prop(self, "Simple_a", text="Radius a") - col.prop(self, "Simple_b", text="Radius b") - - col = box.column(align=True) - col.prop(self, "Simple_startangle") - col.prop(self, "Simple_endangle") - - row = layout.row() - row.prop(self, "Simple_degrees_or_radians", expand=True) - - la = abs(pi * self.Simple_a * (self.Simple_endangle - self.Simple_startangle) / 180) - lb = abs(pi * self.Simple_b * (self.Simple_endangle - self.Simple_startangle) / 180) - l = abs(self.Simple_a - self.Simple_b) * 2 + la + lb - - sa = pi * self.Simple_a * self.Simple_a * \ - abs(self.Simple_endangle - self.Simple_startangle) / 360 - - sb = pi * self.Simple_b * self.Simple_b * \ - abs(self.Simple_endangle - self.Simple_startangle) / 360 - - s = abs(sa - sb) - - if self.Simple_Type == 'Rectangle': - box = layout.box() - col = box.column(align=True) - col.label(text=self.Simple_Type + " Options:") - col.prop(self, "Simple_width") - col.prop(self, "Simple_length") - col.prop(self, "Simple_rounded") - - box.prop(self, "Simple_center") - l = 2 * abs(self.Simple_width) + 2 * abs(self.Simple_length) - s = abs(self.Simple_width) * abs(self.Simple_length) - - if self.Simple_Type == 'Rhomb': - box = layout.box() - col = box.column(align=True) - col.label(text=self.Simple_Type + " Options:") - col.prop(self, "Simple_width") - col.prop(self, "Simple_length") - col.prop(self, "Simple_center") - - g = hypot(self.Simple_width / 2, self.Simple_length / 2) - l = 4 * g - s = self.Simple_width * self.Simple_length / 2 - - if self.Simple_Type == 'Polygon': - box = layout.box() - col = box.column(align=True) - col.label(text=self.Simple_Type + " Options:") - col.prop(self, "Simple_sides") - col.prop(self, "Simple_radius") - - if self.Simple_Type == 'Polygon_ab': - box = layout.box() - col = box.column(align=True) - col.label(text="Polygon ab Options:") - col.prop(self, "Simple_sides") - col.prop(self, "Simple_a") - col.prop(self, "Simple_b") - - if self.Simple_Type == 'Trapezoid': - box = layout.box() - col = box.column(align=True) - col.label(text=self.Simple_Type + " Options:") - col.prop(self, "Simple_a") - col.prop(self, "Simple_b") - col.prop(self, "Simple_h") - - box.prop(self, "Simple_center") - g = hypot(self.Simple_h, (self.Simple_a - self.Simple_b) / 2) - l = self.Simple_a + self.Simple_b + g * 2 - s = (abs(self.Simple_a) + abs(self.Simple_b)) / 2 * self.Simple_h - - row = layout.row() - row.prop(self, "shape", expand=True) - box = layout.box() - box.label("Location:") - box.prop(self, "Simple_startlocation") - box = layout.box() - box.label("Rotation:") - box.prop(self, "Simple_rotation_euler") - - if l != 0 or s != 0: - box = layout.box() - box.label(text="Statistics:", icon="INFO") - if l != 0: - l_str = str(round(l, 4)) - box.label("Length: " + l_str) - if s != 0: - s_str = str(round(s, 4)) - box.label("Area: " + s_str) - - @classmethod - def poll(cls, context): - return context.scene is not None - - def execute(self, context): - if self.Simple_Change: - SimpleDelete(self.Simple_Delete) - - # go to object mode - if bpy.ops.object.mode_set.poll(): - bpy.ops.object.mode_set(mode='OBJECT') - - # turn off undo - undo = bpy.context.user_preferences.edit.use_global_undo - bpy.context.user_preferences.edit.use_global_undo = False - - # main function - self.align_matrix = align_matrix(context, self.Simple_startlocation) - main(context, self, self.align_matrix) - - # restore pre operator undo state - bpy.context.user_preferences.edit.use_global_undo = undo - - return {'FINISHED'} - - def invoke(self, context, event): - # store creation_matrix - if self.Simple_Change: - bpy.context.scene.cursor_location = self.Simple_startlocation - else: - self.Simple_startlocation = bpy.context.scene.cursor_location - - self.align_matrix = align_matrix(context, self.Simple_startlocation) - self.execute(context) - - return {'FINISHED'} - - -# ------------------------------------------------------------ -# Fillet - -class BezierPointsFillet(Operator): - bl_idname = "curve.bezier_points_fillet" - bl_label = "Bezier points Fillet" - bl_description = "Bezier points Fillet" - bl_options = {'REGISTER', 'UNDO'} - - Fillet_radius = FloatProperty( - name="Radius", - default=0.25, - unit='LENGTH', - description="Radius" - ) - Types = [('Round', "Round", "Round"), - ('Chamfer', "Chamfer", "Chamfer")] - Fillet_Type = EnumProperty( - name="Type", - description="Fillet type", - items=Types - ) - - def draw(self, context): - layout = self.layout - - # general options - col = layout.column() - col.prop(self, "Fillet_radius") - col.prop(self, "Fillet_Type", expand=True) - - @classmethod - def poll(cls, context): - return context.scene is not None - - def execute(self, context): - # go to object mode - if bpy.ops.object.mode_set.poll(): - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.mode_set(mode='EDIT') - - # turn off undo - undo = bpy.context.user_preferences.edit.use_global_undo - bpy.context.user_preferences.edit.use_global_undo = False - - # main function - spline = bpy.context.object.data.splines.active - selected = [p for p in spline.bezier_points if p.select_control_point] - - bpy.ops.curve.handle_type_set(type='VECTOR') - n = 0 - ii = [] - for p in spline.bezier_points: - if p.select_control_point: - ii.append(n) - n += 1 - else: - n += 1 - - if n > 2: - jn = 0 - for j in ii: - - j += jn - - selected_all = [p for p in spline.bezier_points] - - bpy.ops.curve.select_all(action='DESELECT') - - if j != 0 and j != n - 1: - selected_all[j].select_control_point = True - selected_all[j + 1].select_control_point = True - bpy.ops.curve.subdivide() - selected_all = [p for p in spline.bezier_points] - selected4 = [selected_all[j - 1], selected_all[j], - selected_all[j + 1], selected_all[j + 2]] - jn += 1 - n += 1 - - elif j == 0: - selected_all[j].select_control_point = True - selected_all[j + 1].select_control_point = True - bpy.ops.curve.subdivide() - selected_all = [p for p in spline.bezier_points] - selected4 = [selected_all[n], selected_all[0], - selected_all[1], selected_all[2]] - jn += 1 - n += 1 - - elif j == n - 1: - selected_all[j].select_control_point = True - selected_all[j - 1].select_control_point = True - bpy.ops.curve.subdivide() - selected_all = [p for p in spline.bezier_points] - selected4 = [selected_all[0], selected_all[n], - selected_all[n - 1], selected_all[n - 2]] - - selected4[2].co = selected4[1].co - s1 = Vector(selected4[0].co) - Vector(selected4[1].co) - s2 = Vector(selected4[3].co) - Vector(selected4[2].co) - s1.normalize() - s11 = Vector(selected4[1].co) + s1 * self.Fillet_radius - selected4[1].co = s11 - s2.normalize() - s22 = Vector(selected4[2].co) + s2 * self.Fillet_radius - selected4[2].co = s22 - - if self.Fillet_Type == 'Round': - if j != n - 1: - selected4[2].handle_right_type = 'VECTOR' - selected4[1].handle_left_type = 'VECTOR' - selected4[1].handle_right_type = 'ALIGNED' - selected4[2].handle_left_type = 'ALIGNED' - else: - selected4[1].handle_right_type = 'VECTOR' - selected4[2].handle_left_type = 'VECTOR' - selected4[2].handle_right_type = 'ALIGNED' - selected4[1].handle_left_type = 'ALIGNED' - if self.Fillet_Type == 'Chamfer': - selected4[2].handle_right_type = 'VECTOR' - selected4[1].handle_left_type = 'VECTOR' - selected4[1].handle_right_type = 'VECTOR' - selected4[2].handle_left_type = 'VECTOR' - - bpy.ops.curve.select_all(action='SELECT') - bpy.ops.curve.spline_type_set(type='BEZIER') - - # restore pre operator undo state - bpy.context.user_preferences.edit.use_global_undo = undo - - return {'FINISHED'} - - def invoke(self, context, event): - self.execute(context) - - return {'FINISHED'} - - -def subdivide_cubic_bezier(p1, p2, p3, p4, t): - p12 = (p2 - p1) * t + p1 - p23 = (p3 - p2) * t + p2 - p34 = (p4 - p3) * t + p3 - p123 = (p23 - p12) * t + p12 - p234 = (p34 - p23) * t + p23 - p1234 = (p234 - p123) * t + p123 - return [p12, p123, p1234, p234, p34] - - -# ------------------------------------------------------------ -# BezierDivide Operator - -class BezierDivide(Operator): - bl_idname = "curve.bezier_spline_divide" - bl_label = "Bezier Spline Divide" - bl_description = "Bezier Divide (enters edit mode) for Fillet Curves" - bl_options = {'REGISTER', 'UNDO'} - - # align_matrix for the invoke - align_matrix = Matrix() - - Bezier_t = FloatProperty( - name="t (0% - 100%)", - default=50.0, - min=0.0, soft_min=0.0, - max=100.0, soft_max=100.0, - description="t (0% - 100%)" - ) - - @classmethod - def poll(cls, context): - return context.scene is not None - - def execute(self, context): - # go to object mode - if bpy.ops.object.mode_set.poll(): - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.mode_set(mode='EDIT') - - # turn off undo - undo = bpy.context.user_preferences.edit.use_global_undo - bpy.context.user_preferences.edit.use_global_undo = False - - # main function - spline = bpy.context.object.data.splines.active - selected_all = [p for p in spline.bezier_points if p.select_control_point] - h = subdivide_cubic_bezier( - selected_all[0].co, selected_all[0].handle_right, - selected_all[1].handle_left, selected_all[1].co, self.Bezier_t / 100 - ) - - selected_all[0].handle_right_type = 'FREE' - selected_all[0].handle_left_type = 'FREE' - selected_all[1].handle_right_type = 'FREE' - selected_all[1].handle_left_type = 'FREE' - bpy.ops.curve.subdivide(1) - selected_all = [p for p in spline.bezier_points if p.select_control_point] - - selected_all[0].handle_right = h[0] - selected_all[1].co = h[2] - selected_all[1].handle_left = h[1] - selected_all[1].handle_right = h[3] - selected_all[2].handle_left = h[4] - - # restore pre operator undo state - bpy.context.user_preferences.edit.use_global_undo = undo - - return {'FINISHED'} - - def invoke(self, context, event): - self.execute(context) - - return {'FINISHED'} - - -# ------------------------------------------------------------ -# Simple change panel - -class SimplePanel(Panel): - bl_label = "Simple Curve" - bl_space_type = "VIEW_3D" - bl_region_type = "TOOLS" - bl_options = {'DEFAULT_CLOSED'} - bl_category = "Tools" - - @classmethod - def poll(cls, context): - if not context.active_object: - pass - elif context.object.s_curve.Simple is True: - return (context.object) - - def draw(self, context): - if context.object.s_curve.Simple is True: - layout = self.layout - obj = context.object - row = layout.row() - - simple_change = row.operator("curve.simple", text="Change", - icon="OUTLINER_DATA_CURVE") - simple_change.Simple_Change = True - simple_change.Simple_Delete = obj.name - simple_change.Simple_Type = obj.s_curve.Simple_Type - simple_change.Simple_startlocation = obj.location - simple_change.Simple_endlocation = obj.s_curve.Simple_endlocation - - simple_change.Simple_a = obj.s_curve.Simple_a - simple_change.Simple_b = obj.s_curve.Simple_b - simple_change.Simple_h = obj.s_curve.Simple_h - - simple_change.Simple_angle = obj.s_curve.Simple_angle - simple_change.Simple_startangle = obj.s_curve.Simple_startangle - simple_change.Simple_endangle = obj.s_curve.Simple_endangle - simple_change.Simple_rotation_euler = obj.rotation_euler - - simple_change.Simple_sides = obj.s_curve.Simple_sides - simple_change.Simple_radius = obj.s_curve.Simple_radius - simple_change.Simple_center = obj.s_curve.Simple_center - simple_change.Simple_width = obj.s_curve.Simple_width - simple_change.Simple_length = obj.s_curve.Simple_length - simple_change.Simple_rounded = obj.s_curve.Simple_rounded - - -# ------------------------------------------------------------ -# Fillet tools panel - -class SimpleEdit(Operator): - bl_idname = "object._simple_edit" - bl_label = "Create Curves" - bl_description = "Subdivide and Fillet Curves" - bl_options = {'REGISTER', 'UNDO'} - - @classmethod - def poll(cls, context): - vertex = [] - nselected = [] - n = 0 - obj = context.active_object - if obj is not None: - if obj.type == 'CURVE': - for i in obj.data.splines: - for j in i.bezier_points: - n += 1 - if j.select_control_point: - nselected.append(n) - vertex.append(obj.matrix_world * j.co) - - if len(vertex) > 0 and n > 2: - return (context.active_object) - if len(vertex) == 2 and abs(nselected[0] - nselected[1]) == 1: - return (context.active_object) - - selected = 0 - for obj in context.selected_objects: - if obj.type == 'CURVE': - selected += 1 - - if selected >= 2: - return (context.selected_objects) - - def draw(self, context): - vertex = [] - selected = [] - n = 0 - obj = context.active_object - if obj is not None: - if obj.type == 'CURVE': - for i in obj.data.splines: - for j in i.bezier_points: - n += 1 - if j.select_control_point: - selected.append(n) - vertex.append(obj.matrix_world * j.co) - - if len(vertex) > 0 and n > 2: - layout = self.layout - row = layout.row() - row.operator("curve.bezier_points_fillet", text="Fillet") - - if len(vertex) == 2 and abs(selected[0] - selected[1]) == 1: - layout = self.layout - row = layout.row() - row.operator("curve.bezier_spline_divide", text="Divide") - - -# ------------------------------------------------------------ -# location update - -def StartLocationUpdate(self, context): - - bpy.context.scene.cursor_location = self.Simple_startlocation - return - - -# ------------------------------------------------------------ -# Add properties to objects - -class SimpleVariables(PropertyGroup): - - Simple = BoolProperty() - Simple_Change = BoolProperty() - - # general properties - Types = [('Point', "Point", "Construct a Point"), - ('Line', "Line", "Construct a Line"), - ('Distance', "Distance", "Contruct a two point Distance"), - ('Angle', "Angle", "Construct an Angle"), - ('Circle', "Circle", "Construct a Circle"), - ('Ellipse', "Ellipse", "Construct an Ellipse"), - ('Arc', "Arc", "Construct an Arc"), - ('Sector', "Sector", "Construct a Sector"), - ('Segment', "Segment", "Construct a Segment"), - ('Rectangle', "Rectangle", "Construct a Rectangle"), - ('Rhomb', "Rhomb", "Construct a Rhomb"), - ('Polygon', "Polygon", "Construct a Polygon"), - ('Polygon_ab', "Polygon ab", "Construct a Polygon ab"), - ('Trapezoid', "Trapezoid", "Construct a Trapezoid") - ] - Simple_Type = EnumProperty( - name="Type", - description="Form of Curve to create", - items=Types - ) - # Line properties - Simple_startlocation = FloatVectorProperty( - name="Start location", - description="Start location", - default=(0.0, 0.0, 0.0), - subtype='TRANSLATION', - update=StartLocationUpdate - ) - Simple_endlocation = FloatVectorProperty( - name="End location", - description="End location", - default=(2.0, 2.0, 2.0), - subtype='TRANSLATION' - ) - Simple_rotation_euler = FloatVectorProperty( - name="Rotation", - description="Rotation", - default=(0.0, 0.0, 0.0), - subtype='EULER' - ) - # Trapezoid properties - Simple_a = FloatProperty( - name="Side a", - default=2.0, - min=0.0, soft_min=0.0, - unit='LENGTH', - description="a side Value" - ) - Simple_b = FloatProperty( - name="Side b", - default=1.0, - min=0.0, soft_min=0.0, - unit='LENGTH', - description="b side Value" - ) - Simple_h = FloatProperty( - name="Height", - default=1.0, - unit='LENGTH', - description="Height of the Trapezoid - distance between a and b" - ) - Simple_angle = FloatProperty( - name="Angle", - default=45.0, - description="Angle" - ) - Simple_startangle = FloatProperty( - name="Start angle", - default=0.0, - min=-360.0, soft_min=-360.0, - max=360.0, soft_max=360.0, - description="Start angle" - ) - Simple_endangle = FloatProperty( - name="End angle", - default=45.0, - min=-360.0, soft_min=-360.0, - max=360.0, soft_max=360.0, - description="End angle" - ) - Simple_sides = IntProperty( - name="Sides", - default=3, - min=3, soft_min=3, - description="Number of Sides" - ) - Simple_radius = FloatProperty( - name="Radius", - default=1.0, - min=0.0, soft_min=0.0, - unit='LENGTH', - description="Radius" - ) - Simple_center = BoolProperty( - name="Length center", - default=True, - description="Length center" - ) - # Rectangle properties - Simple_width = FloatProperty( - name="Width", - default=2.0, - min=0.0, soft_min=0.0, - unit='LENGTH', - description="Width" - ) - Simple_length = FloatProperty( - name="Length", - default=2.0, - min=0.0, soft_min=0.0, - unit='LENGTH', - description="Length" - ) - Simple_rounded = FloatProperty( - name="Rounded", - default=0.0, - unit='LENGTH', - description="Rounded corners" - ) - - -class INFO_MT_simple_menu(Menu): - bl_idname = "INFO_MT_simple_menu" - bl_label = "2D Objects" - - def draw(self, context): - self.layout.operator_context = 'INVOKE_REGION_WIN' - - oper1 = self.layout.operator(Simple.bl_idname, text="Angle", icon="MOD_CURVE") - oper1.Simple_Change = False - oper1.Simple_Type = "Angle" - - oper2 = self.layout.operator(Simple.bl_idname, text="Arc", icon="MOD_CURVE") - oper2.Simple_Change = False - oper2.Simple_Type = "Arc" - - oper3 = self.layout.operator(Simple.bl_idname, text="Circle", icon="MOD_CURVE") - oper3.Simple_Change = False - oper3.Simple_Type = "Circle" - - oper4 = self.layout.operator(Simple.bl_idname, text="Distance", icon="MOD_CURVE") - oper4.Simple_Change = False - oper4.Simple_Type = "Distance" - - oper5 = self.layout.operator(Simple.bl_idname, text="Ellipse", icon="MOD_CURVE") - oper5.Simple_Change = False - oper5.Simple_Type = "Ellipse" - - oper6 = self.layout.operator(Simple.bl_idname, text="Line", icon="MOD_CURVE") - oper6.Simple_Change = False - oper6.Simple_Type = "Line" - - oper7 = self.layout.operator(Simple.bl_idname, text="Point", icon="MOD_CURVE") - oper7.Simple_Change = False - oper7.Simple_Type = "Point" - - oper8 = self.layout.operator(Simple.bl_idname, text="Polygon", icon="MOD_CURVE") - oper8.Simple_Change = False - oper8.Simple_Type = "Polygon" - - oper9 = self.layout.operator(Simple.bl_idname, text="Polygon ab", icon="MOD_CURVE") - oper9.Simple_Change = False - oper9.Simple_Type = "Polygon_ab" - - oper10 = self.layout.operator(Simple.bl_idname, text="Rectangle", icon="MOD_CURVE") - oper10.Simple_Change = False - oper10.Simple_Type = "Rectangle" - - oper11 = self.layout.operator(Simple.bl_idname, text="Rhomb", icon="MOD_CURVE") - oper11.Simple_Change = False - oper11.Simple_Type = "Rhomb" - - oper12 = self.layout.operator(Simple.bl_idname, text="Sector", icon="MOD_CURVE") - oper12.Simple_Change = False - oper12.Simple_Type = "Sector" - - oper13 = self.layout.operator(Simple.bl_idname, text="Segment", icon="MOD_CURVE") - oper13.Simple_Change = False - oper13.Simple_Type = "Segment" - - oper14 = self.layout.operator(Simple.bl_idname, text="Trapezoid", icon="MOD_CURVE") - oper14.Simple_Change = False - oper14.Simple_Type = "Trapezoid" - - -# Register - -def Simple_button(self, context): - layout = self.layout - layout.separator() - self.layout.menu("INFO_MT_simple_menu", icon="MOD_CURVE") - - -def register(): - bpy.utils.register_class(Simple) - bpy.utils.register_class(BezierPointsFillet) - bpy.utils.register_class(BezierDivide) - bpy.utils.register_class(SimplePanel) - bpy.utils.register_class(SimpleEdit) - bpy.utils.register_class(INFO_MT_simple_menu) - bpy.utils.register_class(SimpleVariables) - - bpy.types.INFO_MT_curve_add.append(Simple_button) - - bpy.types.Object.s_curve = PointerProperty(type=SimpleVariables) - - -def unregister(): - bpy.utils.unregister_class(Simple) - bpy.utils.unregister_class(BezierPointsFillet) - bpy.utils.unregister_class(BezierDivide) - bpy.utils.unregister_class(SimplePanel) - bpy.utils.unregister_class(SimpleEdit) - bpy.utils.unregister_class(INFO_MT_simple_menu) - bpy.utils.unregister_class(SimpleVariables) - - bpy.types.INFO_MT_curve_add.remove(Simple_button) - del bpy.types.Object.s_curve - - -if __name__ == "__main__": - register() diff --git a/tests/test_helpers/addons/add_curve_extra_objects/add_curve_spirals.py b/tests/test_helpers/addons/add_curve_extra_objects/add_curve_spirals.py deleted file mode 100644 index 522f637..0000000 --- a/tests/test_helpers/addons/add_curve_extra_objects/add_curve_spirals.py +++ /dev/null @@ -1,424 +0,0 @@ -# gpl: author Alejandro Omar Chocano Vasquez - -""" -bl_info = { - "name": "Spirals", - "description": "Make spirals", - "author": "Alejandro Omar Chocano Vasquez", - "version": (1, 2, 1), - "blender": (2, 62, 0), - "location": "View3D > Add > Curve", - "warning": "", - "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.4/Py/" - "Scripts/Object/Spirals", - "tracker_url": "http://alexvaqp.googlepages.com?" - "func=detail&aid=", - "category": "Add Curve", -} -""" - -import bpy -import time -from bpy.props import ( - EnumProperty, - BoolProperty, - FloatProperty, - IntProperty, - ) -from math import ( - sin, cos, pi - ) -from bpy_extras.object_utils import object_data_add -from bpy.types import ( - Operator, - Menu, - ) -from bl_operators.presets import AddPresetBase - - -# make normal spiral -# ---------------------------------------------------------------------------- - -def make_spiral(props, context): - # archemedian and logarithmic can be plotted in cylindrical coordinates - - # INPUT: turns->degree->max_phi, steps, direction - # Initialise Polar Coordinate Enviroment - props.degree = 360 * props.turns # If you want to make the slider for degree - steps = props.steps * props.turns # props.steps[per turn] -> steps[for the whole spiral] - props.z_scale = props.dif_z * props.turns - - max_phi = pi * props.degree / 180 # max angle in radian - step_phi = max_phi / steps # angle in radians between two vertices - - if props.spiral_direction == 'CLOCKWISE': - step_phi *= -1 # flip direction - max_phi *= -1 - - step_z = props.z_scale / (steps - 1) # z increase in one step - - verts = [] - verts.extend([props.radius, 0, 0, 1]) - - cur_phi = 0 - cur_z = 0 - - # Archemedean: dif_radius, radius - cur_rad = props.radius - step_rad = props.dif_radius / (steps * 360 / props.degree) - # radius increase per angle for archemedean spiral| - # (steps * 360/props.degree)...Steps needed for 360 deg - # Logarithmic: radius, B_force, ang_div, dif_z - - while abs(cur_phi) <= abs(max_phi): - cur_phi += step_phi - cur_z += step_z - - if props.spiral_type == 'ARCH': - cur_rad += step_rad - if props.spiral_type == 'LOG': - # r = a*e^{|theta| * b} - cur_rad = props.radius * pow(props.B_force, abs(cur_phi)) - - px = cur_rad * cos(cur_phi) - py = cur_rad * sin(cur_phi) - verts.extend([px, py, cur_z, 1]) - - return verts - - -# make Spheric spiral -# ---------------------------------------------------------------------------- - -def make_spiral_spheric(props, context): - # INPUT: turns, steps[per turn], radius - # use spherical Coordinates - step_phi = (2 * pi) / props.steps # Step of angle in radians for one turn - steps = props.steps * props.turns # props.steps[per turn] -> steps[for the whole spiral] - - max_phi = 2 * pi * props.turns # max angle in radian - step_phi = max_phi / steps # angle in radians between two vertices - if props.spiral_direction == 'CLOCKWISE': # flip direction - step_phi *= -1 - max_phi *= -1 - step_theta = pi / (steps - 1) # theta increase in one step (pi == 180 deg) - - verts = [] - verts.extend([0, 0, -props.radius, 1]) # First vertex at south pole - - cur_phi = 0 - cur_theta = -pi / 2 # Beginning at south pole - - while abs(cur_phi) <= abs(max_phi): - # Coordinate Transformation sphere->rect - px = props.radius * cos(cur_theta) * cos(cur_phi) - py = props.radius * cos(cur_theta) * sin(cur_phi) - pz = props.radius * sin(cur_theta) - - verts.extend([px, py, pz, 1]) - cur_theta += step_theta - cur_phi += step_phi - - return verts - - -# make torus spiral -# ---------------------------------------------------------------------------- - -def make_spiral_torus(props, context): - # INPUT: turns, steps, inner_radius, curves_number, - # mul_height, dif_inner_radius, cycles - max_phi = 2 * pi * props.turns * props.cycles # max angle in radian - step_phi = 2 * pi / props.steps # Step of angle in radians between two vertices - - if props.spiral_direction == 'CLOCKWISE': # flip direction - step_phi *= -1 - max_phi *= -1 - - step_theta = (2 * pi / props.turns) / props.steps - step_rad = props.dif_radius / (props.steps * props.turns) - step_inner_rad = props.dif_inner_radius / props.steps - step_z = props.dif_z / (props.steps * props.turns) - - verts = [] - - cur_phi = 0 # Inner Ring Radius Angle - cur_theta = 0 # Ring Radius Angle - cur_rad = props.radius - cur_inner_rad = props.inner_radius - cur_z = 0 - n_cycle = 0 - - while abs(cur_phi) <= abs(max_phi): - # Torus Coordinates -> Rect - px = (cur_rad + cur_inner_rad * cos(cur_phi)) * \ - cos(props.curves_number * cur_theta) - py = (cur_rad + cur_inner_rad * cos(cur_phi)) * \ - sin(props.curves_number * cur_theta) - pz = cur_inner_rad * sin(cur_phi) + cur_z - - verts.extend([px, py, pz, 1]) - - if props.touch and cur_phi >= n_cycle * 2 * pi: - step_z = ((n_cycle + 1) * props.dif_inner_radius + - props.inner_radius) * 2 / (props.steps * props.turns) - n_cycle += 1 - - cur_theta += step_theta - cur_phi += step_phi - cur_rad += step_rad - cur_inner_rad += step_inner_rad - cur_z += step_z - - return verts - - -def draw_curve(props, context): - if props.spiral_type == 'ARCH': - verts = make_spiral(props, context) - if props.spiral_type == 'LOG': - verts = make_spiral(props, context) - if props.spiral_type == 'SPHERE': - verts = make_spiral_spheric(props, context) - if props.spiral_type == 'TORUS': - verts = make_spiral_torus(props, context) - - curve_data = bpy.data.curves.new(name='Spiral', type='CURVE') - curve_data.dimensions = '3D' - - spline = curve_data.splines.new(type=props.curve_type) - """ - if props.curve_type == 0: - spline = curve_data.splines.new(type='POLY') - elif props.curve_type == 1: - spline = curve_data.splines.new(type='NURBS') - """ - spline.points.add(len(verts) * 0.25 - 1) - # Add only one quarter of points as elements in verts, - # because verts looks like: "x,y,z,?,x,y,z,?,x,..." - spline.points.foreach_set('co', verts) - new_obj = object_data_add(context, curve_data) - - -class CURVE_OT_spirals(Operator): - bl_idname = "curve.spirals" - bl_label = "Curve Spirals" - bl_description = "Create different types of spirals" - bl_options = {'REGISTER', 'UNDO'} - - spiral_type = EnumProperty( - items=[('ARCH', "Archemedian", "Archemedian"), - ("LOG", "Logarithmic", "Logarithmic"), - ("SPHERE", "Spheric", "Spheric"), - ("TORUS", "Torus", "Torus")], - default='ARCH', - name="Spiral Type", - description="Type of spiral to add" - ) - curve_type = EnumProperty( - items=[('POLY', "Poly", "PolyLine"), - ("NURBS", "NURBS", "NURBS")], - default='POLY', - name="Curve Type", - description="Type of spline to use" - ) - spiral_direction = EnumProperty( - items=[('COUNTER_CLOCKWISE', "Counter Clockwise", - "Wind in a counter clockwise direction"), - ("CLOCKWISE", "Clockwise", - "Wind in a clockwise direction")], - default='COUNTER_CLOCKWISE', - name="Spiral Direction", - description="Direction of winding" - ) - turns = IntProperty( - default=1, - min=1, max=1000, - description="Length of Spiral in 360 deg" - ) - steps = IntProperty( - default=24, - min=2, max=1000, - description="Number of Vertices per turn" - ) - radius = FloatProperty( - default=1.00, - min=0.00, max=100.00, - description="Radius for first turn" - ) - dif_z = FloatProperty( - default=0, - min=-10.00, max=100.00, - description="Increase in Z axis per turn" - ) - # needed for 1 and 2 spiral_type - # Archemedian variables - dif_radius = FloatProperty( - default=0.00, - min=-50.00, max=50.00, - description="Radius increment in each turn" - ) - # step between turns(one turn equals 360 deg) - # Log variables - B_force = FloatProperty( - default=1.00, - min=0.00, max=30.00, - description="Factor of exponent" - ) - # Torus variables - inner_radius = FloatProperty( - default=0.20, - min=0.00, max=100, - description="Inner Radius of Torus" - ) - dif_inner_radius = FloatProperty( - default=0, - min=-10, max=100, - description="Increase of inner Radius per Cycle" - ) - dif_radius = FloatProperty( - default=0, - min=-10, max=100, - description="Increase of Torus Radius per Cycle" - ) - cycles = FloatProperty( - default=1, - min=0.00, max=1000, - description="Number of Cycles" - ) - curves_number = IntProperty( - default=1, - min=1, max=400, - description="Number of curves of spiral" - ) - touch = BoolProperty( - default=False, - description="No empty spaces between cycles" - ) - - def draw(self, context): - layout = self.layout - col = layout.column_flow(align=True) - - col.label("Presets:") - - row = col.row(align=True) - row.menu("OBJECT_MT_spiral_curve_presets", - text=bpy.types.OBJECT_MT_spiral_curve_presets.bl_label) - row.operator("curve_extras.spiral_presets", text="", icon='ZOOMIN') - op = row.operator("curve_extras.spiral_presets", text="", icon='ZOOMOUT') - op.remove_active = True - - layout.prop(self, "spiral_type") - layout.prop(self, "curve_type") - layout.prop(self, "spiral_direction") - - col = layout.column(align=True) - col.label(text="Spiral Parameters:") - col.prop(self, "turns", text="Turns") - col.prop(self, "steps", text="Steps") - - box = layout.box() - if self.spiral_type == 'ARCH': - box.label("Archemedian Settings:") - col = box.column(align=True) - col.prop(self, "dif_radius", text="Radius Growth") - col.prop(self, "radius", text="Radius") - col.prop(self, "dif_z", text="Height") - - if self.spiral_type == 'LOG': - box.label("Logarithmic Settings:") - col = box.column(align=True) - col.prop(self, "radius", text="Radius") - col.prop(self, "B_force", text="Expansion Force") - col.prop(self, "dif_z", text="Height") - - if self.spiral_type == 'SPHERE': - box.label("Spheric Settings:") - box.prop(self, "radius", text="Radius") - - if self.spiral_type == 'TORUS': - box.label("Torus Settings:") - col = box.column(align=True) - col.prop(self, "cycles", text="Number of Cycles") - - if self.dif_inner_radius == 0 and self.dif_z == 0: - self.cycles = 1 - col.prop(self, "radius", text="Radius") - - if self.dif_z == 0: - col.prop(self, "dif_z", text="Height per Cycle") - else: - box2 = box.box() - col2 = box2.column(align=True) - col2.prop(self, "dif_z", text="Height per Cycle") - col2.prop(self, "touch", text="Make Snail") - - col = box.column(align=True) - col.prop(self, "curves_number", text="Curves Number") - col.prop(self, "inner_radius", text="Inner Radius") - col.prop(self, "dif_radius", text="Increase of Torus Radius") - col.prop(self, "dif_inner_radius", text="Increase of Inner Radius") - - @classmethod - def poll(cls, context): - return context.scene is not None - - def execute(self, context): - time_start = time.time() - draw_curve(self, context) - - self.report({'INFO'}, - "Drawing Spiral Finished: %.4f sec" % (time.time() - time_start)) - - return {'FINISHED'} - - -class CURVE_EXTRAS_OT_spirals_presets(AddPresetBase, Operator): - bl_idname = "curve_extras.spiral_presets" - bl_label = "Spirals" - bl_description = "Spirals Presets" - preset_menu = "OBJECT_MT_spiral_curve_presets" - preset_subdir = "curve_extras/curve.spirals" - - preset_defines = [ - "op = bpy.context.active_operator", - ] - preset_values = [ - "op.spiral_type", - "op.curve_type", - "op.spiral_direction", - "op.turns", - "op.steps", - "op.radius", - "op.dif_z", - "op.dif_radius", - "op.B_force", - "op.inner_radius", - "op.dif_inner_radius", - "op.cycles", - "op.curves_number", - "op.touch", - ] - - -class OBJECT_MT_spiral_curve_presets(Menu): - '''Presets for curve.spiral''' - bl_label = "Spiral Curve Presets" - bl_idname = "OBJECT_MT_spiral_curve_presets" - preset_subdir = "curve_extras/curve.spirals" - preset_operator = "script.execute_preset" - - draw = bpy.types.Menu.draw_preset - - -def register(): - bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - - -if __name__ == "__main__": - register() diff --git a/tests/test_helpers/addons/add_curve_extra_objects/add_curve_spirofit_bouncespline.py b/tests/test_helpers/addons/add_curve_extra_objects/add_curve_spirofit_bouncespline.py deleted file mode 100644 index 33caf12..0000000 --- a/tests/test_helpers/addons/add_curve_extra_objects/add_curve_spirofit_bouncespline.py +++ /dev/null @@ -1,1014 +0,0 @@ -# ##### 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 ##### - - -bl_info = { - "name": "SpiroFit, BounceSpline and Catenary", - "author": "Antonio Osprite, Liero, Atom, Jimmy Hazevoet", - "version": (0, 2, 1), - "blender": (2, 78, 0), - "location": "Toolshelf > Create Tab", - "description": "SpiroFit, BounceSpline and Catenary adds " - "splines to selected mesh or objects", - "warning": "", - "wiki_url": "", - "category": "Object", - } - -import bpy -from bpy.types import ( - Operator, - Panel, - ) -from bpy.props import ( - BoolProperty, - EnumProperty, - FloatProperty, - IntProperty, - StringProperty, - ) -from mathutils import ( - Matrix, - Vector, - ) -from math import ( - sin, cos, - pi, sqrt, - pow, radians - ) -import random as r - - -# ------------------------------------------------------------ -# "Build a spiral that fit the active object" -# Spirofit, original blender 2.45 script by: Antonio Osprite -# http://www.kino3d.com/forum/viewtopic.php?t=5374 -# ------------------------------------------------------------ -def distance(v1, v2): - d = (Vector(v1) - Vector(v2)).length - return d - - -def spiral_point(step, radius, z_coord, spires, waves, wave_iscale, rndm): - x = radius * cos(spires * step) + (r.random() - 0.5) * rndm - y = radius * sin(spires * step) + (r.random() - 0.5) * rndm - z = z_coord + (cos(waves * step * pi) * wave_iscale) + (r.random() - 0.5) * rndm - return [x, y, z] - - -def spirofit_spline(obj, - spire_resolution=4, - spires=4, - offset=0.0, - waves=0, - wave_iscale=0.0, - rndm_spire=0.0, - direction=False, - map_method='RAYCAST' - ): - - points = [] - bb = obj.bound_box - bb_xmin = min([v[0] for v in bb]) - bb_ymin = min([v[1] for v in bb]) - bb_zmin = min([v[2] for v in bb]) - bb_xmax = max([v[0] for v in bb]) - bb_ymax = max([v[1] for v in bb]) - bb_zmax = max([v[2] for v in bb]) - - radius = distance([bb_xmax, bb_ymax, bb_zmin], [bb_xmin, bb_ymin, bb_zmin]) / 2.0 - height = bb_zmax - bb_zmin - cx = (bb_xmax + bb_xmin) / 2.0 - cy = (bb_ymax + bb_ymin) / 2.0 - steps = spires * spire_resolution - - for i in range(steps + 1): - t = bb_zmin + (2 * pi / steps) * i - z = bb_zmin + (float(height) / steps) * i - if direction: - t = -t - cp = spiral_point(t, radius, z, spires, waves, wave_iscale, rndm_spire) - - if map_method == 'RAYCAST': - success, hit, nor, index = obj.ray_cast(Vector(cp), (Vector([cx, cy, z]) - Vector(cp))) - if success: - points.append((hit + offset * nor)) - - elif map_method == 'CLOSESTPOINT': - success, hit, nor, index = obj.closest_point_on_mesh(cp) - if success: - points.append((hit + offset * nor)) - - return points - - -class SpiroFitSpline(Operator): - bl_idname = "object.add_spirofit_spline" - bl_label = "SpiroFit" - bl_description = "Wrap selected mesh in a spiral" - bl_options = {'REGISTER', 'UNDO', 'PRESET'} - - map_method = EnumProperty( - name="Mapping", - default='RAYCAST', - description="Mapping method", - items=[('RAYCAST', 'Ray cast', 'Ray casting'), - ('CLOSESTPOINT', 'Closest point', 'Closest point on mesh')] - ) - direction = BoolProperty( - name="Direction", - description="Spire direction", - default=False - ) - spire_resolution = IntProperty( - name="Spire Resolution", - default=8, - min=3, - max=1024, - soft_max=128, - description="Number of steps for one turn" - ) - spires = IntProperty( - name="Spires", - default=4, - min=1, - max=1024, - soft_max=128, - description="Number of turns" - ) - offset = FloatProperty( - name="Offset", - default=0.0, - precision=3, - description="Use normal direction to offset spline" - ) - waves = IntProperty( - name="Wave", - default=0, - min=0, - description="Wave amount" - ) - wave_iscale = FloatProperty( - name="Wave Intensity", - default=0.0, - min=0.0, - precision=3, - description="Wave intensity scale" - ) - rndm_spire = FloatProperty( - name="Randomise", - default=0.0, - min=0.0, - precision=3, - description="Randomise spire" - ) - spline_name = StringProperty( - name="Name", - default="SpiroFit" - ) - spline_type = EnumProperty( - name="Spline", - default='BEZIER', - description="Spline type", - items=[('POLY', 'Poly', 'Poly spline'), - ('BEZIER', 'Bezier', 'Bezier spline')] - ) - resolution_u = IntProperty( - name="Resolution U", - default=12, - min=0, - max=64, - description="Curve resolution u" - ) - bevel = FloatProperty( - name="Bevel Radius", - default=0.0, - min=0.0, - precision=3, - description="Bevel depth" - ) - bevel_res = IntProperty( - name="Bevel Resolution", - default=0, - min=0, - max=32, - description="Bevel resolution" - ) - extrude = FloatProperty( - name="Extrude", - default=0.0, - min=0.0, - precision=3, - description="Extrude amount" - ) - twist_mode = EnumProperty( - name="Twisting", - default='MINIMUM', - description="Twist method, type of tilt calculation", - items=[('Z_UP', "Z-Up", 'Z Up'), - ('MINIMUM', "Minimum", 'Minimum'), - ('TANGENT', "Tangent", 'Tangent')] - ) - twist_smooth = FloatProperty( - name="Smooth", - default=0.0, - min=0.0, - precision=3, - description="Twist smoothing amount for tangents" - ) - tilt = FloatProperty( - name="Tilt", - default=0.0, - precision=3, - description="Spline handle tilt" - ) - random_radius = FloatProperty( - name="Randomise", - default=0.0, - min=0.0, - precision=3, - description="Randomise radius of spline controlpoints" - ) - x_ray = BoolProperty( - name="X-Ray", - default=False, - description="X-Ray - make the object draw in front of others" - ) - random_seed = IntProperty( - name="Random Seed", - default=1, - min=0, - description="Random seed number" - ) - origin_to_start = BoolProperty( - name="Origin at Start", - description="Set origin at first point of spline", - default=False - ) - refresh = BoolProperty( - name="Refresh", - description="Refresh spline", - default=False - ) - auto_refresh = BoolProperty( - name="Auto", - description="Auto refresh spline", - default=True - ) - - def draw(self, context): - layout = self.layout - col = layout.column(align=True) - row = col.row(align=True) - - if self.auto_refresh is False: - self.refresh = False - elif self.auto_refresh is True: - self.refresh = True - - row.prop(self, "auto_refresh", toggle=True, icon="AUTO", icon_only=True) - row.prop(self, "refresh", toggle=True, icon="FILE_REFRESH", icon_only=True) - row.operator("object.add_spirofit_spline", text="Add") - row.prop(self, "x_ray", toggle=True, icon_only=True, icon="RESTRICT_VIEW_OFF") - row.prop(self, "origin_to_start", toggle=True, icon="CURVE_DATA", icon_only=True) - - col = layout.column(align=True) - col.prop(self, "spline_name") - col.separator() - col.prop(self, "map_method") - col.separator() - col.prop(self, "spire_resolution") - row = col.row(align=True).split(0.9, align=True) - row.prop(self, "spires") - row.prop(self, "direction", toggle=True, text="", icon="ARROW_LEFTRIGHT") - col.prop(self, "offset") - col.prop(self, "waves") - col.prop(self, "wave_iscale") - col.prop(self, "rndm_spire") - col.prop(self, "random_seed") - draw_spline_settings(self) - - @classmethod - def poll(self, context): - ob = context.active_object - return ((ob is not None) and - (context.mode == 'OBJECT')) - - def invoke(self, context, event): - self.refresh = True - return self.execute(context) - - def execute(self, context): - if not self.refresh: - return {'PASS_THROUGH'} - - obj = context.active_object - if obj.type != 'MESH': - self.report({'WARNING'}, - "Active Object is not a Mesh. Operation Cancelled") - return {'CANCELLED'} - - undo = context.user_preferences.edit.use_global_undo - context.user_preferences.edit.use_global_undo = False - - bpy.ops.object.select_all(action='DESELECT') - - r.seed(self.random_seed) - - points = spirofit_spline( - obj, - self.spire_resolution, - self.spires, - self.offset, - self.waves, - self.wave_iscale, - self.rndm_spire, - self.direction, - self.map_method - ) - - add_curve_object( - points, - obj.matrix_world, - self.spline_name, - self.spline_type, - self.resolution_u, - self.bevel, - self.bevel_res, - self.extrude, - self.random_radius, - self.twist_mode, - self.twist_smooth, - self.tilt, - self.x_ray - ) - - if self.origin_to_start is True: - move_origin_to_start() - - if self.auto_refresh is False: - self.refresh = False - - context.user_preferences.edit.use_global_undo = undo - return {'FINISHED'} - - -# ------------------------------------------------------------ -# Bounce spline / Fiber mesh -# Original script by Liero and Atom -# https://blenderartists.org/forum/showthread.php?331750-Fiber-Mesh-Emulation -# ------------------------------------------------------------ -def noise(var=1): - rand = Vector((r.gauss(0, 1), r.gauss(0, 1), r.gauss(0, 1))) - vec = rand.normalized() * var - return vec - - -def bounce_spline(obj, - number=1000, - ang_noise=0.25, - offset=0.0, - extra=50, - active_face=False - ): - - dist, points = 1000, [] - poly = obj.data.polygons - - if active_face: - try: - n = poly.active - except: - print("No active face selected") - pass - else: - n = r.randint(0, len(poly) - 1) - - end = poly[n].normal.copy() * -1 - start = poly[n].center - points.append(start + offset * end) - - for i in range(number): - for ray in range(extra + 1): - end += noise(ang_noise) - try: - hit, nor, index = obj.ray_cast(start, end * dist)[-3:] - except: - index = -1 - if index != -1: - start = hit - nor / 10000 - end = end.reflect(nor).normalized() - points.append(hit + offset * nor) - break - if index == -1: - return points - return points - - -class BounceSpline(Operator): - bl_idname = "object.add_bounce_spline" - bl_label = "Bounce Spline" - bl_description = "Fill selected mesh with a spline" - bl_options = {'REGISTER', 'UNDO', 'PRESET'} - - bounce_number = IntProperty( - name="Bounces", - default=1000, - min=1, - max=100000, - soft_max=10000, - description="Number of bounces" - ) - ang_noise = FloatProperty( - name="Angular Noise", - default=0.25, - min=0.0, - precision=3, - description="Add some noise to ray direction" - ) - offset = FloatProperty( - name="Offset", - default=0.0, - precision=3, - description="Use normal direction to offset spline" - ) - extra = IntProperty( - name="Extra", - default=50, - min=0, - max=1000, - description="Number of extra tries if it fails to hit mesh" - ) - active_face = BoolProperty( - name="Active Face", - default=False, - description="Starts from active face or a random one" - ) - spline_name = StringProperty( - name="Name", - default="BounceSpline" - ) - spline_type = EnumProperty( - name="Spline", - default='BEZIER', - description="Spline type", - items=[('POLY', "Poly", "Poly spline"), - ('BEZIER', "Bezier", "Bezier spline")] - ) - resolution_u = IntProperty( - name="Resolution U", - default=12, - min=0, - max=64, - description="Curve resolution u" - ) - bevel = FloatProperty( - name="Bevel Radius", - default=0.0, - min=0.0, - precision=3, - description="Bevel depth" - ) - bevel_res = IntProperty( - name="Bevel Resolution", - default=0, - min=0, - max=32, - description="Bevel resolution" - ) - extrude = FloatProperty( - name="Extrude", - default=0.0, - min=0.0, - precision=3, - description="Extrude amount" - ) - twist_mode = EnumProperty( - name="Twisting", - default='MINIMUM', - description="Twist method, type of tilt calculation", - items=[('Z_UP', "Z-Up", 'Z Up'), - ('MINIMUM', "Minimum", 'Minimum'), - ('TANGENT', "Tangent", 'Tangent')] - ) - twist_smooth = FloatProperty( - name="Smooth", - default=0.0, - min=0.0, - precision=3, - description="Twist smoothing amount for tangents" - ) - tilt = FloatProperty( - name="Tilt", - default=0.0, - precision=3, - description="Spline handle tilt" - ) - random_radius = FloatProperty( - name="Randomise", - default=0.0, - min=0.0, - precision=3, - description="Randomise radius of spline controlpoints" - ) - x_ray = BoolProperty( - name="X-Ray", - default=False, - description="X-Ray - make the object draw in front of others" - ) - random_seed = IntProperty( - name="Random Seed", - default=1, - min=0, - description="Random seed number" - ) - origin_to_start = BoolProperty( - name="Origin at Start", - description="Set origin at first point of spline", - default=False - ) - refresh = BoolProperty( - name="Refresh", - description="Refresh spline", - default=False - ) - auto_refresh = BoolProperty( - name="Auto", - description="Auto refresh spline", - default=True - ) - - def draw(self, context): - layout = self.layout - col = layout.column(align=True) - row = col.row(align=True) - if self.auto_refresh is False: - self.refresh = False - elif self.auto_refresh is True: - self.refresh = True - - row.prop(self, "auto_refresh", toggle=True, icon="AUTO", icon_only=True) - row.prop(self, "refresh", toggle=True, icon="FILE_REFRESH", icon_only=True) - row.operator("object.add_bounce_spline", text="Add") - row.prop(self, "x_ray", toggle=True, icon_only=True, icon="RESTRICT_VIEW_OFF") - row.prop(self, "origin_to_start", toggle=True, icon="CURVE_DATA", icon_only=True) - - col = layout.column(align=True) - col.prop(self, "spline_name") - col.separator() - col.prop(self, "bounce_number") - row = col.row(align=True).split(0.9, align=True) - row.prop(self, "ang_noise") - row.prop(self, "active_face", toggle=True, text="", icon="SNAP_FACE") - col.prop(self, "offset") - col.prop(self, "extra") - col.prop(self, "random_seed") - draw_spline_settings(self) - - @classmethod - def poll(self, context): - ob = context.active_object - return ((ob is not None) and - (context.mode == 'OBJECT')) - - def invoke(self, context, event): - self.refresh = True - return self.execute(context) - - def execute(self, context): - if not self.refresh: - return {'PASS_THROUGH'} - - obj = context.active_object - if obj.type != 'MESH': - return {'CANCELLED'} - - undo = context.user_preferences.edit.use_global_undo - context.user_preferences.edit.use_global_undo = False - - bpy.ops.object.select_all(action='DESELECT') - - r.seed(self.random_seed) - - points = bounce_spline( - obj, - self.bounce_number, - self.ang_noise, - self.offset, - self.extra, - self.active_face - ) - - add_curve_object( - points, - obj.matrix_world, - self.spline_name, - self.spline_type, - self.resolution_u, - self.bevel, - self.bevel_res, - self.extrude, - self.random_radius, - self.twist_mode, - self.twist_smooth, - self.tilt, - self.x_ray - ) - - if self.origin_to_start is True: - move_origin_to_start() - - if self.auto_refresh is False: - self.refresh = False - - context.user_preferences.edit.use_global_undo = undo - return {'FINISHED'} - - -# ------------------------------------------------------------ -# Hang Catenary curve between two selected objects -# ------------------------------------------------------------ -def catenary_curve( - start=[-2, 0, 2], - end=[2, 0, 2], - steps=24, - a=2.0 - ): - - points = [] - lx = end[0] - start[0] - ly = end[1] - start[1] - lr = sqrt(pow(lx, 2) + pow(ly, 2)) - lv = lr / 2 - (end[2] - start[2]) * a / lr - zv = start[2] - pow(lv, 2) / (2 * a) - slx = lx / steps - sly = ly / steps - slr = lr / steps - i = 0 - while i <= steps: - x = start[0] + i * slx - y = start[1] + i * sly - z = zv + pow((i * slr) - lv, 2) / (2 * a) - points.append([x, y, z]) - i += 1 - return points - - -class CatenaryCurve(Operator): - bl_idname = "object.add_catenary_curve" - bl_label = "Catenary" - bl_description = "Hang a curve between two selected objects" - bl_options = {'REGISTER', 'UNDO', 'PRESET'} - - steps = IntProperty( - name="Steps", - description="Resolution of the curve", - default=24, - min=2, - max=1024, - ) - var_a = FloatProperty( - name="a", - description="Catenary variable a", - precision=3, - default=2.0, - min=0.01, - max=100.0 - ) - spline_name = StringProperty( - name="Name", - default="Catenary" - ) - spline_type = EnumProperty( - name="Spline", - default='BEZIER', - description="Spline type", - items=[('POLY', "Poly", "Poly spline"), - ('BEZIER', "Bezier", "Bezier spline")] - ) - resolution_u = IntProperty( - name="Resolution U", - default=12, - min=0, - max=64, - description="Curve resolution u" - ) - bevel = FloatProperty( - name="Bevel Radius", - default=0.0, - min=0.0, - precision=3, - description="Bevel depth" - ) - bevel_res = IntProperty( - name="Bevel Resolution", - default=0, - min=0, - max=32, - description="Bevel resolution" - ) - extrude = FloatProperty( - name="Extrude", - default=0.0, - min=0.0, - precision=3, - description="Extrude amount" - ) - twist_mode = EnumProperty( - name="Twisting", - default='MINIMUM', - description="Twist method, type of tilt calculation", - items=[('Z_UP', "Z-Up", 'Z Up'), - ('MINIMUM', "Minimum", "Minimum"), - ('TANGENT', "Tangent", "Tangent")] - ) - twist_smooth = FloatProperty( - name="Smooth", - default=0.0, - min=0.0, - precision=3, - description="Twist smoothing amount for tangents" - ) - tilt = FloatProperty( - name="Tilt", - default=0.0, - precision=3, - description="Spline handle tilt" - ) - random_radius = FloatProperty( - name="Randomise", - default=0.0, - min=0.0, - precision=3, - description="Randomise radius of spline controlpoints" - ) - x_ray = BoolProperty( - name="X-Ray", - default=False, - description="X-Ray - make the object draw in front of others" - ) - random_seed = IntProperty( - name="Random Seed", - default=1, - min=0, - description="Random seed number" - ) - origin_to_start = BoolProperty( - name="Origin at Start", - description="Set origin at first point of spline", - default=False - ) - refresh = BoolProperty( - name="Refresh", - description="Refresh spline", - default=False - ) - auto_refresh = BoolProperty( - name="Auto", - description="Auto refresh spline", - default=True - ) - - def draw(self, context): - layout = self.layout - col = layout.column(align=True) - row = col.row(align=True) - - if self.auto_refresh is False: - self.refresh = False - elif self.auto_refresh is True: - self.refresh = True - - row.prop(self, "auto_refresh", toggle=True, icon="AUTO", icon_only=True) - row.prop(self, "refresh", toggle=True, icon="FILE_REFRESH", icon_only=True) - row.operator("object.add_catenary_curve", text="Add") - row.prop(self, "x_ray", toggle=True, icon_only=True, icon="RESTRICT_VIEW_OFF") - row.prop(self, "origin_to_start", toggle=True, icon="CURVE_DATA", icon_only=True) - - col = layout.column(align=True) - col.prop(self, "spline_name") - col.separator() - col.prop(self, "steps") - col.prop(self, "var_a") - - draw_spline_settings(self) - col = layout.column(align=True) - col.prop(self, "random_seed") - - @classmethod - def poll(self, context): - ob = context.active_object - return ob is not None - - def invoke(self, context, event): - self.refresh = True - return self.execute(context) - - def execute(self, context): - if not self.refresh: - return {'PASS_THROUGH'} - - try: - ob1 = bpy.context.active_object - ob1.select = False - ob2 = bpy.context.selected_objects[0] - start = ob1.location - end = ob2.location - if (start[0] == end[0]) and (start[1] == end[1]): - self.report({"WARNING"}, - "Objects have the same X, Y location. Operation Cancelled") - - return {'CANCELLED'} - except: - self.report({"WARNING"}, - "Catenary could not be completed. Operation Cancelled") - return {'CANCELLED'} - - bpy.ops.object.select_all(action='DESELECT') - - undo = context.user_preferences.edit.use_global_undo - context.user_preferences.edit.use_global_undo = False - - r.seed(self.random_seed) - - points = catenary_curve( - start, - end, - self.steps, - self.var_a - ) - add_curve_object( - points, - Matrix(), - self.spline_name, - self.spline_type, - self.resolution_u, - self.bevel, - self.bevel_res, - self.extrude, - self.random_radius, - self.twist_mode, - self.twist_smooth, - self.tilt, - self.x_ray - ) - - if self.origin_to_start is True: - move_origin_to_start() - else: - bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY') - - if self.auto_refresh is False: - self.refresh = False - - context.user_preferences.edit.use_global_undo = undo - return {'FINISHED'} - - -# ------------------------------------------------------------ -# Generate curve object from given points -# ------------------------------------------------------------ -def add_curve_object( - verts, - matrix, - spline_name="Spline", - spline_type='BEZIER', - resolution_u=12, - bevel=0.0, - bevel_resolution=0, - extrude=0.0, - spline_radius=0.0, - twist_mode='MINIMUM', - twist_smooth=0.0, - tilt=0.0, - x_ray=False - ): - - curve = bpy.data.curves.new(spline_name, 'CURVE') - curve.dimensions = '3D' - spline = curve.splines.new(spline_type) - cur = bpy.data.objects.new(spline_name, curve) - - spline.radius_interpolation = 'BSPLINE' - spline.tilt_interpolation = 'BSPLINE' - - if spline_type == 'BEZIER': - spline.bezier_points.add(int(len(verts) - 1)) - for i in range(len(verts)): - spline.bezier_points[i].co = verts[i] - spline.bezier_points[i].handle_right_type = 'AUTO' - spline.bezier_points[i].handle_left_type = 'AUTO' - spline.bezier_points[i].radius += spline_radius * r.random() - spline.bezier_points[i].tilt = radians(tilt) - else: - spline.points.add(int(len(verts) - 1)) - for i in range(len(verts)): - spline.points[i].co = verts[i][0], verts[i][1], verts[i][2], 1 - - bpy.context.scene.objects.link(cur) - cur.data.use_uv_as_generated = True - cur.data.resolution_u = resolution_u - cur.data.fill_mode = 'FULL' - cur.data.bevel_depth = bevel - cur.data.bevel_resolution = bevel_resolution - cur.data.extrude = extrude - cur.data.twist_mode = twist_mode - cur.data.twist_smooth = twist_smooth - cur.matrix_world = matrix - bpy.context.scene.objects.active = cur - cur.select = True - if x_ray is True: - cur.show_x_ray = x_ray - return - - -def move_origin_to_start(): - active = bpy.context.active_object - spline = active.data.splines[0] - if spline.type == 'BEZIER': - start = active.matrix_world * spline.bezier_points[0].co - else: - start = active.matrix_world * spline.points[0].co - start = start[:-1] - cursor = bpy.context.scene.cursor_location.copy() - bpy.context.scene.cursor_location = start - bpy.ops.object.origin_set(type='ORIGIN_CURSOR') - bpy.context.scene.cursor_location = cursor - - -def draw_spline_settings(self): - layout = self.layout - col = layout.column(align=True) - - col.prop(self, "spline_type") - col.separator() - col.prop(self, "resolution_u") - col.prop(self, "bevel") - col.prop(self, "bevel_res") - col.prop(self, "extrude") - - if self.spline_type == 'BEZIER': - col.prop(self, "random_radius") - col.separator() - col.prop(self, "twist_mode") - col.separator() - - if self.twist_mode == 'TANGENT': - col.prop(self, "twist_smooth") - - if self.spline_type == 'BEZIER': - col.prop(self, "tilt") - - -# ------------------------------------------------------------ -# Tools Panel > Create -# ------------------------------------------------------------ -class SplinePanel(Panel): - bl_space_type = "VIEW_3D" - bl_context = "objectmode" - bl_region_type = "TOOLS" - bl_label = "Spline" - bl_category = "Create" - bl_options = {'DEFAULT_CLOSED'} - - def draw(self, context): - col = self.layout.column(align=True) - col.operator(SpiroFitSpline.bl_idname, icon="FORCE_MAGNETIC") - col.operator(BounceSpline.bl_idname, icon="FORCE_HARMONIC") - col.operator(CatenaryCurve.bl_idname, icon="FORCE_CURVE") - - -# ------------------------------------------------------------ -# Register -# ------------------------------------------------------------ -def register(): - bpy.utils.register_class(SplinePanel) - bpy.utils.register_class(SpiroFitSpline) - bpy.utils.register_class(BounceSpline) - bpy.utils.register_class(CatenaryCurve) - - -def unregister(): - bpy.utils.unregister_class(SplinePanel) - bpy.utils.unregister_class(SpiroFitSpline) - bpy.utils.unregister_class(BounceSpline) - bpy.utils.unregister_class(CatenaryCurve) - - -if __name__ == "__main__": - register() diff --git a/tests/test_helpers/addons/add_curve_extra_objects/add_curve_torus_knots.py b/tests/test_helpers/addons/add_curve_extra_objects/add_curve_torus_knots.py deleted file mode 100644 index e093257..0000000 --- a/tests/test_helpers/addons/add_curve_extra_objects/add_curve_torus_knots.py +++ /dev/null @@ -1,726 +0,0 @@ -# ##### 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 ##### - -""" -bl_info = { - "name": "Torus Knots", - "author": "Marius Giurgi (DolphinDream), testscreenings", - "version": (0, 2), - "blender": (2, 76, 0), - "location": "View3D > Add > Curve", - "description": "Adds many types of (torus) knots", - "warning": "", - "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/" - "Scripts/Curve/Torus_Knot", - "category": "Add Curve"} -""" - -import bpy -from bpy.props import ( - BoolProperty, - EnumProperty, - FloatProperty, - IntProperty - ) -from math import ( - sin, cos, - pi, sqrt - ) -from mathutils import ( - Vector, - Matrix, - ) -from bpy_extras.object_utils import AddObjectHelper -from random import random -from bpy.types import Operator - -# Globals: -DEBUG = False - - -# greatest common denominator -def gcd(a, b): - if b == 0: - return a - else: - return gcd(b, a % b) - - -# ####################################################################### -# ###################### Knot Definitions ############################### -# ####################################################################### -def Torus_Knot(self, linkIndex=0): - p = self.torus_p # revolution count (around the torus center) - q = self.torus_q # spin count (around the torus tube) - - N = self.torus_res # curve resolution (number of control points) - -# use plus options only when they are enabled - if self.options_plus: - u = self.torus_u # p multiplier - v = self.torus_v # q multiplier - h = self.torus_h # height (scale along Z) - s = self.torus_s # torus scale (radii scale factor) - else: # don't use plus settings - u = 1 - v = 1 - h = 1 - s = 1 - - R = self.torus_R * s # major radius (scaled) - r = self.torus_r * s # minor radius (scaled) - - # number of decoupled links when (p,q) are NOT co-primes - links = gcd(p, q) # = 1 when (p,q) are co-primes - - # parametrized angle increment (cached outside of the loop for performance) - # NOTE: the total angle is divided by number of decoupled links to ensure - # the curve does not overlap with itself when (p,q) are not co-primes - da = 2 * pi / links / (N - 1) - - # link phase : each decoupled link is phased equally around the torus center - # NOTE: linkIndex value is in [0, links-1] - linkPhase = 2 * pi / q * linkIndex # = 0 when there is just ONE link - - # user defined phasing - if self.options_plus: - rPhase = self.torus_rP # user defined revolution phase - sPhase = self.torus_sP # user defined spin phase - else: # don't use plus settings - rPhase = 0 - sPhase = 0 - - rPhase += linkPhase # total revolution phase of the current link - - if DEBUG: - print("") - print("Link: %i of %i" % (linkIndex, links)) - print("gcd = %i" % links) - print("p = %i" % p) - print("q = %i" % q) - print("link phase = %.2f deg" % (linkPhase * 180 / pi)) - print("link phase = %.2f rad" % linkPhase) - - # flip directions ? NOTE: flipping both is equivalent to no flip - if self.flip_p: - p *= -1 - if self.flip_q: - q *= -1 - - # create the 3D point array for the current link - newPoints = [] - for n in range(N - 1): - # t = 2 * pi / links * n/(N-1) with: da = 2*pi/links/(N-1) => t = n * da - t = n * da - theta = p * t * u + rPhase # revolution angle - phi = q * t * v + sPhase # spin angle - - x = (R + r * cos(phi)) * cos(theta) - y = (R + r * cos(phi)) * sin(theta) - z = r * sin(phi) * h - - # append 3D point - # NOTE : the array is adjusted later as needed to 4D for POLY and NURBS - newPoints.append([x, y, z]) - - return newPoints - - -# ------------------------------------------------------------------------------ -# Calculate the align matrix for the new object (based on user preferences) - -def align_matrix(self, context): - if self.absolute_location: - loc = Matrix.Translation(Vector((0, 0, 0))) - else: - loc = Matrix.Translation(context.scene.cursor_location) - -# user defined location & translation - userLoc = Matrix.Translation(self.location) - userRot = self.rotation.to_matrix().to_4x4() - - obj_align = context.user_preferences.edit.object_align - if (context.space_data.type == 'VIEW_3D' and obj_align == 'VIEW'): - rot = context.space_data.region_3d.view_matrix.to_3x3().inverted().to_4x4() - else: - rot = Matrix() - - align_matrix = userLoc * loc * rot * userRot - return align_matrix - - -# ------------------------------------------------------------------------------ -# Set curve BEZIER handles to auto - -def setBezierHandles(obj, mode='AUTOMATIC'): - scene = bpy.context.scene - if obj.type != 'CURVE': - return - scene.objects.active = obj - bpy.ops.object.mode_set(mode='EDIT', toggle=True) - bpy.ops.curve.select_all(action='SELECT') - bpy.ops.curve.handle_type_set(type=mode) - bpy.ops.object.mode_set(mode='OBJECT', toggle=True) - - -# ------------------------------------------------------------------------------ -# Convert array of vert coordinates to points according to spline type - -def vertsToPoints(Verts, splineType): - # main vars - vertArray = [] - - # array for BEZIER spline output (V3) - if splineType == 'BEZIER': - for v in Verts: - vertArray += v - - # array for non-BEZIER output (V4) - else: - for v in Verts: - vertArray += v - if splineType == 'NURBS': - vertArray.append(1) # for NURBS w=1 - else: # for POLY w=0 - vertArray.append(0) - - return vertArray - - -# ------------------------------------------------------------------------------ -# Create the Torus Knot curve and object and add it to the scene - -def create_torus_knot(self, context): - # pick a name based on (p,q) parameters - aName = "Torus Knot %i x %i" % (self.torus_p, self.torus_q) - - # create curve - curve_data = bpy.data.curves.new(name=aName, type='CURVE') - - # setup materials to be used for the TK links - if self.use_colors: - addLinkColors(self, curve_data) - - # create torus knot link(s) - if self.multiple_links: - links = gcd(self.torus_p, self.torus_q) - else: - links = 1 - - for l in range(links): - # get vertices for the current link - verts = Torus_Knot(self, l) - - # output splineType 'POLY' 'NURBS' or 'BEZIER' - splineType = self.outputType - - # turn verts into proper array (based on spline type) - vertArray = vertsToPoints(verts, splineType) - - # create spline from vertArray (based on spline type) - spline = curve_data.splines.new(type=splineType) - if splineType == 'BEZIER': - spline.bezier_points.add(int(len(vertArray) * 1.0 / 3 - 1)) - spline.bezier_points.foreach_set('co', vertArray) - else: - spline.points.add(int(len(vertArray) * 1.0 / 4 - 1)) - spline.points.foreach_set('co', vertArray) - spline.use_endpoint_u = True - - # set curve options - spline.use_cyclic_u = True - spline.order_u = 4 - - # set a color per link - if self.use_colors: - spline.material_index = l - - curve_data.dimensions = '3D' - curve_data.resolution_u = self.segment_res - - # create surface ? - if self.geo_surface: - curve_data.fill_mode = 'FULL' - curve_data.bevel_depth = self.geo_bDepth - curve_data.bevel_resolution = self.geo_bRes - curve_data.extrude = self.geo_extrude - curve_data.offset = self.geo_offset - - new_obj = bpy.data.objects.new(aName, curve_data) - - # set object in the scene - scene = bpy.context.scene - scene.objects.link(new_obj) # place in active scene - new_obj.select = True # set as selected - scene.objects.active = new_obj # set as active - new_obj.matrix_world = self.align_matrix # apply matrix - - # set BEZIER handles - if splineType == 'BEZIER': - setBezierHandles(new_obj, self.handleType) - - return - - -# ------------------------------------------------------------------------------ -# Create materials to be assigned to each TK link - -def addLinkColors(self, curveData): - # some predefined colors for the torus knot links - colors = [] - if self.colorSet == "1": # RGBish - colors += [[0.0, 0.0, 1.0]] - colors += [[0.0, 1.0, 0.0]] - colors += [[1.0, 0.0, 0.0]] - colors += [[1.0, 1.0, 0.0]] - colors += [[0.0, 1.0, 1.0]] - colors += [[1.0, 0.0, 1.0]] - colors += [[1.0, 0.5, 0.0]] - colors += [[0.0, 1.0, 0.5]] - colors += [[0.5, 0.0, 1.0]] - else: # RainBow - colors += [[0.0, 0.0, 1.0]] - colors += [[0.0, 0.5, 1.0]] - colors += [[0.0, 1.0, 1.0]] - colors += [[0.0, 1.0, 0.5]] - colors += [[0.0, 1.0, 0.0]] - colors += [[0.5, 1.0, 0.0]] - colors += [[1.0, 1.0, 0.0]] - colors += [[1.0, 0.5, 0.0]] - colors += [[1.0, 0.0, 0.0]] - - me = curveData - links = gcd(self.torus_p, self.torus_q) - - for i in range(links): - matName = "TorusKnot-Link-%i" % i - matListNames = bpy.data.materials.keys() - # create the material - if matName not in matListNames: - if DEBUG: - print("Creating new material : %s" % matName) - mat = bpy.data.materials.new(matName) - else: - if DEBUG: - print("Material %s already exists" % matName) - mat = bpy.data.materials[matName] - - # set material color - if self.options_plus and self.random_colors: - mat.diffuse_color = random(), random(), random() - else: - cID = i % (len(colors)) # cycle through predefined colors - mat.diffuse_color = colors[cID] - - if self.options_plus: - mat.diffuse_color.s = self.saturation - else: - mat.diffuse_color.s = 0.75 - - me.materials.append(mat) - - -# ------------------------------------------------------------------------------ -# Main Torus Knot class - -class torus_knot_plus(Operator, AddObjectHelper): - bl_idname = "curve.torus_knot_plus" - bl_label = "Torus Knot +" - bl_options = {'REGISTER', 'UNDO', 'PRESET'} - bl_description = "Adds many types of tours knots" - bl_context = "object" - - def mode_update_callback(self, context): - # keep the equivalent radii sets (R,r)/(eR,iR) in sync - if self.mode == 'EXT_INT': - self.torus_eR = self.torus_R + self.torus_r - self.torus_iR = self.torus_R - self.torus_r - - # align_matrix for the invoke - align_matrix = None - - # GENERAL options - options_plus = BoolProperty( - name="Extra Options", - default=False, - description="Show more options (the plus part)", - ) - absolute_location = BoolProperty( - name="Absolute Location", - default=False, - description="Set absolute location instead of relative to 3D cursor", - ) - # COLOR options - use_colors = BoolProperty( - name="Use Colors", - default=False, - description="Show torus links in colors", - ) - colorSet = EnumProperty( - name="Color Set", - items=(('1', "RGBish", "RGBsish ordered colors"), - ('2', "Rainbow", "Rainbow ordered colors")), - ) - random_colors = BoolProperty( - name="Randomize Colors", - default=False, - description="Randomize link colors", - ) - saturation = FloatProperty( - name="Saturation", - default=0.75, - min=0.0, max=1.0, - description="Color saturation", - ) - # SURFACE Options - geo_surface = BoolProperty( - name="Surface", - default=True, - description="Create surface", - ) - geo_bDepth = FloatProperty( - name="Bevel Depth", - default=0.04, - min=0, soft_min=0, - description="Bevel Depth", - ) - geo_bRes = IntProperty( - name="Bevel Resolution", - default=2, - min=0, soft_min=0, - max=5, soft_max=5, - description="Bevel Resolution" - ) - geo_extrude = FloatProperty( - name="Extrude", - default=0.0, - min=0, soft_min=0, - description="Amount of curve extrusion" - ) - geo_offset = FloatProperty( - name="Offset", - default=0.0, - min=0, soft_min=0, - description="Offset the surface relative to the curve" - ) - # TORUS KNOT Options - torus_p = IntProperty( - name="p", - default=2, - min=1, soft_min=1, - description="Number of Revolutions around the torus hole before closing the knot" - ) - torus_q = IntProperty( - name="q", - default=3, - min=1, soft_min=1, - description="Number of Spins through the torus hole before closing the knot" - ) - flip_p = BoolProperty( - name="Flip p", - default=False, - description="Flip Revolution direction" - ) - flip_q = BoolProperty( - name="Flip q", - default=False, - description="Flip Spin direction" - ) - multiple_links = BoolProperty( - name="Multiple Links", - default=True, - description="Generate all links or just one link when q and q are not co-primes" - ) - torus_u = IntProperty( - name="Rev. Multiplier", - default=1, - min=1, soft_min=1, - description="Revolutions Multiplier" - ) - torus_v = IntProperty( - name="Spin Multiplier", - default=1, - min=1, soft_min=1, - description="Spin multiplier" - ) - torus_rP = FloatProperty( - name="Revolution Phase", - default=0.0, - min=0.0, soft_min=0.0, - description="Phase revolutions by this radian amount" - ) - torus_sP = FloatProperty( - name="Spin Phase", - default=0.0, - min=0.0, soft_min=0.0, - description="Phase spins by this radian amount" - ) - # TORUS DIMENSIONS options - mode = EnumProperty( - name="Torus Dimensions", - items=(("MAJOR_MINOR", "Major/Minor", - "Use the Major/Minor radii for torus dimensions."), - ("EXT_INT", "Exterior/Interior", - "Use the Exterior/Interior radii for torus dimensions.")), - update=mode_update_callback, - ) - torus_R = FloatProperty( - name="Major Radius", - min=0.00, max=100.0, - default=1.0, - subtype='DISTANCE', - unit='LENGTH', - description="Radius from the torus origin to the center of the cross section" - ) - torus_r = FloatProperty( - name="Minor Radius", - min=0.00, max=100.0, - default=.25, - subtype='DISTANCE', - unit='LENGTH', - description="Radius of the torus' cross section" - ) - torus_iR = FloatProperty( - name="Interior Radius", - min=0.00, max=100.0, - default=.75, - subtype='DISTANCE', - unit='LENGTH', - description="Interior radius of the torus (closest to the torus center)" - ) - torus_eR = FloatProperty( - name="Exterior Radius", - min=0.00, max=100.0, - default=1.25, - subtype='DISTANCE', - unit='LENGTH', - description="Exterior radius of the torus (farthest from the torus center)" - ) - torus_s = FloatProperty( - name="Scale", - min=0.01, max=100.0, - default=1.00, - description="Scale factor to multiply the radii" - ) - torus_h = FloatProperty( - name="Height", - default=1.0, - min=0.0, max=100.0, - description="Scale along the local Z axis" - ) - # CURVE options - torus_res = IntProperty( - name="Curve Resolution", - default=100, - min=3, soft_min=3, - description="Number of control vertices in the curve" - ) - segment_res = IntProperty( - name="Segment Resolution", - default=12, - min=1, soft_min=1, - description="Curve subdivisions per segment" - ) - SplineTypes = [ - ('POLY', "Poly", "Poly type"), - ('NURBS', "Nurbs", "Nurbs type"), - ('BEZIER', "Bezier", "Bezier type")] - outputType = EnumProperty( - name="Output splines", - default='BEZIER', - description="Type of splines to output", - items=SplineTypes, - ) - bezierHandles = [ - ('VECTOR', "Vector", "Bezier Hanles type - Vector"), - ('AUTOMATIC', "Auto", "Bezier Hanles type - Automatic"), - ] - handleType = EnumProperty( - name="Handle type", - default='AUTOMATIC', - items=bezierHandles, - description="Bezier handle type", - ) - adaptive_resolution = BoolProperty( - name="Adaptive Resolution", - default=False, - description="Auto adjust curve resolution based on TK length", - ) - - def draw(self, context): - layout = self.layout - - # extra parameters toggle - layout.prop(self, "options_plus") - - # TORUS KNOT Parameters - col = layout.column() - col.label(text="Torus Knot Parameters:") - - box = layout.box() - split = box.split(percentage=0.85, align=True) - split.prop(self, "torus_p", text="Revolutions") - split.prop(self, "flip_p", toggle=True, text="", - icon="ARROW_LEFTRIGHT") - - split = box.split(percentage=0.85, align=True) - split.prop(self, "torus_q", text="Spins") - split.prop(self, "flip_q", toggle=True, text="", - icon="ARROW_LEFTRIGHT") - - links = gcd(self.torus_p, self.torus_q) - info = "Multiple Links" - - if links > 1: - info += " ( " + str(links) + " )" - box.prop(self, 'multiple_links', text=info) - - if self.options_plus: - box = box.box() - col = box.column(align=True) - col.prop(self, "torus_u") - col.prop(self, "torus_v") - - col = box.column(align=True) - col.prop(self, "torus_rP") - col.prop(self, "torus_sP") - - # TORUS DIMENSIONS options - col = layout.column(align=True) - col.label(text="Torus Dimensions:") - box = layout.box() - col = box.column(align=True) - col.row().prop(self, "mode", expand=True) - - if self.mode == "MAJOR_MINOR": - col = box.column(align=True) - col.prop(self, "torus_R") - col.prop(self, "torus_r") - else: # EXTERIOR-INTERIOR - col = box.column(align=True) - col.prop(self, "torus_eR") - col.prop(self, "torus_iR") - - if self.options_plus: - box = box.box() - col = box.column(align=True) - col.prop(self, "torus_s") - col.prop(self, "torus_h") - - # CURVE options - col = layout.column(align=True) - col.label(text="Curve Options:") - box = layout.box() - - col = box.column() - col.label(text="Output Curve Type:") - col.row().prop(self, "outputType", expand=True) - - depends = box.column() - depends.prop(self, "torus_res") - # deactivate the "curve resolution" if "adaptive resolution" is enabled - depends.enabled = not self.adaptive_resolution - - box.prop(self, "adaptive_resolution") - box.prop(self, "segment_res") - - # SURFACE options - col = layout.column() - col.label(text="Geometry Options:") - box = layout.box() - box.prop(self, "geo_surface") - if self.geo_surface: - col = box.column(align=True) - col.prop(self, "geo_bDepth") - col.prop(self, "geo_bRes") - - col = box.column(align=True) - col.prop(self, "geo_extrude") - col.prop(self, "geo_offset") - - # COLOR options - col = layout.column() - col.label(text="Color Options:") - box = layout.box() - box.prop(self, "use_colors") - if self.use_colors and self.options_plus: - box = box.box() - box.prop(self, "colorSet") - box.prop(self, "random_colors") - box.prop(self, "saturation") - - # TRANSFORM options - col = layout.column() - col.label(text="Transform Options:") - box = col.box() - box.prop(self, "location") - box.prop(self, "absolute_location") - box.prop(self, "rotation") - - @classmethod - def poll(cls, context): - if context.mode != "OBJECT": - return False - return context.scene is not None - - def execute(self, context): - if self.mode == 'EXT_INT': - # adjust the equivalent radii pair : (R,r) <=> (eR,iR) - self.torus_R = (self.torus_eR + self.torus_iR) * 0.5 - self.torus_r = (self.torus_eR - self.torus_iR) * 0.5 - - if self.adaptive_resolution: - # adjust curve resolution automatically based on (p,q,R,r) values - p = self.torus_p - q = self.torus_q - R = self.torus_R - r = self.torus_r - links = gcd(p, q) - - # get an approximate length of the whole TK curve - # upper bound approximation - maxTKLen = 2 * pi * sqrt(p * p * (R + r) * (R + r) + q * q * r * r) - # lower bound approximation - minTKLen = 2 * pi * sqrt(p * p * (R - r) * (R - r) + q * q * r * r) - avgTKLen = (minTKLen + maxTKLen) / 2 # average approximation - - if DEBUG: - print("Approximate average TK length = %.2f" % avgTKLen) - - # x N factor = control points per unit length - self.torus_res = max(3, avgTKLen / links * 8) - - # update align matrix - self.align_matrix = align_matrix(self, context) - - # turn off undo - undo = bpy.context.user_preferences.edit.use_global_undo - bpy.context.user_preferences.edit.use_global_undo = False - - # create the curve - create_torus_knot(self, context) - - # restore pre operator undo state - bpy.context.user_preferences.edit.use_global_undo = undo - - return {'FINISHED'} - - def invoke(self, context, event): - self.execute(context) - - return {'FINISHED'} diff --git a/tests/test_helpers/addons/add_curve_extra_objects/add_surface_plane_cone.py b/tests/test_helpers/addons/add_curve_extra_objects/add_surface_plane_cone.py deleted file mode 100644 index dcbb5b5..0000000 --- a/tests/test_helpers/addons/add_curve_extra_objects/add_surface_plane_cone.py +++ /dev/null @@ -1,398 +0,0 @@ -# gpl: author Folkert de Vries - -bl_info = { - "name": "Surface: Plane / Cone/ Star / Wedge", - "description": "Create a NURBS surface plane", - "author": "Folkert de Vries", - "version": (1, 0, 1), - "blender": (2, 5, 9), - "location": "View3D > Add > Surface", - "warning": "", - "wiki_url": "", - "category": "Add Mesh" -} - -""" -Info: -to add a surface star, plane or cone, go to add Menu > Surface > Star, Plane or Cone -next parameters like scale and u and v resolution can be adjusted in the toolshelf - -have fun using this add-on -""" - -import bpy -from bpy.props import ( - FloatProperty, - IntProperty, - ) -from bpy.types import Operator - - -# generic class for inheritance -class MakeSurfaceHelpers: - # get input for size and resolution - size = FloatProperty( - name="Size", - description="Size of the object", - default=1.0, - min=0.01, - max=100.0, - unit="LENGTH", - ) - res_u = IntProperty( - name="Resolution U", - description="Surface resolution in u direction", - default=1, - min=1, - max=500, - ) - res_v = IntProperty( - name="Resolution V", - description="Surface resolution in v direction", - default=1, - min=1, - max=500, - ) - - @classmethod - def poll(cls, context): - return context.mode == 'OBJECT' - - def draw(self, context): - layout = self.layout - layout.prop(self, "size") - - col = layout.column(align=True) - col.prop(self, "res_u") - col.prop(self, "res_v") - - -class MakeSurfaceWedge(Operator, MakeSurfaceHelpers): - bl_idname = "object.add_surface_wedge" - bl_label = "Add Surface Wedge" - bl_description = "Construct a Surface Wedge" - bl_options = {'REGISTER', 'UNDO'} - - def execute(self, context): - # variables - size = self.size - res_u = self.res_u - res_v = self.res_v - - # add a surface Plane - bpy.ops.object.add_surface_plane() - # save some time, by getting instant acces to those values - ao = context.active_object - point = ao.data.splines[0].points - - # rotate 90 degrees on the z axis - ao.rotation_euler[0] = 0.0 - ao.rotation_euler[1] = 0.0 - ao.rotation_euler[2] = 1.570796 - - # go into edit mode and deselect - bpy.ops.object.mode_set(mode='EDIT') - bpy.ops.curve.select_all(action='DESELECT') - - # select points 0 and 1, and extrudde them - # declaring ao and point again seems necesary... - ao = context.active_object - point = ao.data.splines[0].points - point[0].select = True - ao = context.active_object - point = ao.data.splines[0].points - point[1].select = True - bpy.ops.curve.extrude() - # bring extruded points up 1 bu on the z axis, and toggle - # cyclic in V direction - bpy.ops.transform.translate(value=(0, 0, 1), constraint_axis=(False, False, True)) - bpy.ops.curve.cyclic_toggle(direction='CYCLIC_V') - - # get points to the right coords. - point[0].co = (1.0, 0.0, 1.0, 1.0) - point[1].co = (-1.0, 0.0, 1.0, 1.0) - point[2].co = (1.0, -0.5, 0.0, 1.0) - point[3].co = (-1.0, -0.5, 0.0, 1.0) - point[4].co = (1.0, 0.5, 0.0, 1.0) - point[5].co = (-1.0, 0.5, 0.0, 1.0) - - # go back to object mode - bpy.ops.object.mode_set(mode='OBJECT') - # get origin to geometry. - bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='MEDIAN') - # change name - context.active_object.name = 'SurfaceWedge' - # get the wedge to the 3d cursor. - context.active_object.location = context.scene.cursor_location - bpy.ops.transform.resize(value=(size, size, size)) - - # adjust resolution in u and v direction - context.active_object.data.resolution_u = res_u - context.active_object.data.resolution_v = res_v - - return{'FINISHED'} - - -class MakeSurfaceCone(Operator, MakeSurfaceHelpers): - bl_idname = "object.add_surface_cone" - bl_label = "Add Surface Cone" - bl_description = "Construct a Surface Cone" - bl_options = {'REGISTER', 'UNDO'} - - def execute(self, context): - size = self.size - res_u = self.res_u - res_v = self.res_v - - # add basemesh, a nurbs torus - bpy.ops.surface.primitive_nurbs_surface_torus_add(location=(0, 0, 0)) - # get active object and active object name - - ao = context.active_object - - # go to edit mode - bpy.ops.object.mode_set(mode='EDIT') - # deselect all - bpy.ops.curve.select_all(action='DESELECT') - # too shorten alot of lines - point = ao.data.splines[0].points - # get middle points - - for i in range(0, 63): - if point[i].co.z == 0.0: - point[i].select = True - - # select non-middle points and delete them - bpy.ops.curve.select_all(action='INVERT') - bpy.ops.curve.delete(type='VERT') - # declaring this again seems necesary... - point = ao.data.splines[0].points - # list of points to be in center, and 2 bu'' s higher - - ToKeep = [1, 3, 5, 7, 9, 11, 13, 15, 17] - for i in range(0, len(ToKeep) - 1): - point[ToKeep[i]].select = True - - bpy.ops.transform.resize(value=(0, 0, 0)) - bpy.ops.curve.cyclic_toggle(direction='CYCLIC_U') - bpy.ops.transform.translate(value=(0, 0, 2)) - - # to make cone visible - bpy.ops.object.editmode_toggle() - bpy.ops.object.editmode_toggle() - # change name - context.active_object.name = 'SurfaceCone' - # go back to object mode - bpy.ops.object.editmode_toggle() - # bring object to cursor - bpy.ops.object.mode_set(mode='OBJECT') - context.active_object.location = context.scene.cursor_location - # adjust size - bpy.ops.transform.resize(value=(size, size, size)) - - # adjust resolution in u and v direction - context.active_object.data.resolution_u = res_u - context.active_object.data.resolution_v = res_v - - return{'FINISHED'} - - -class MakeSurfaceStar(Operator, MakeSurfaceHelpers): - bl_idname = "object.add_surface_star" - bl_label = "Add Surface Star" - bl_description = "Contruct a Surface Star" - bl_options = {'REGISTER', 'UNDO'} - - def execute(self, context): - size = self.size - res_u = self.res_u - res_v = self.res_v - - # add surface circle: - bpy.ops.surface.primitive_nurbs_surface_circle_add(location=(0, 0, 0)) - # we got 8 points, we need 40 points. - # get active object - ao = context.active_object - # enter edtimode - bpy.ops.object.mode_set(mode='EDIT') - # deselect all - bpy.ops.curve.select_all(action='DESELECT') - # select point 0 and 1, and subdivide - point = ao.data.splines[0].points - - point[0].select = True - point[1].select = True - bpy.ops.curve.subdivide() - bpy.ops.curve.select_all(action='DESELECT') - - # select point 2 and 3, and subdivide - point[2].select = True - point[3].select = True - bpy.ops.curve.subdivide() - bpy.ops.curve.select_all(action='DESELECT') - - ListOfCoords = [ - (0.5, 0.0, 0.25, 1.0), - (0.80901700258255, 0.5877853035926819, 0.25, 1.0), - (0.1545085906982422, 0.4755282402038574, 0.25, 1.0), - (-0.30901703238487244, 0.9510565400123596, 0.25, 1.0), - (-0.4045085608959198, 0.293892502784729, 0.2499999850988388, 1.0), - (-1.0, 0.0, 0.25, 1.0), - (-0.4045085608959198, -0.293892502784729, 0.2499999850988388, 1.0), - (-0.30901703238487244, -0.9510565400123596, 0.25, 1.0), - (0.1545085906982422, -0.4755282402038574, 0.25, 1.0), - (0.8090166449546814, -0.5877856612205505, 0.2499999850988388, 1.0) - ] - for i in range(0, 10): - context.active_object.data.splines[0].points[i].co = ListOfCoords[i] - - # now select all, and subdivide till 40 points is reached: - bpy.ops.curve.select_all(action='SELECT') - bpy.ops.curve.subdivide() - bpy.ops.curve.subdivide() - bpy.ops.curve.subdivide() - - # extrude the star - bpy.ops.curve.extrude(mode='TRANSLATION') - # bring extruded part up - bpy.ops.transform.translate( - value=(0, 0, 0.5), - constraint_axis=(False, False, True) - ) - # flip normals - bpy.ops.curve.switch_direction() - # go back to object mode - bpy.ops.object.mode_set(mode='OBJECT') - # origin to geometry - bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='MEDIAN') - # get object to 3d cursor - context.active_object.location = context.scene.cursor_location - # change name - ao.name = 'SurfaceStar' - # adjust size - bpy.ops.transform.resize(value=(size, size, size)) - - # adjust resolution in u and v direction - context.active_object.data.resolution_u = res_u - context.active_object.data.resolution_v = res_v - - return{'FINISHED'} - - -class MakeSurfacePlane(Operator, MakeSurfaceHelpers): - bl_idname = "object.add_surface_plane" - bl_label = "Add Surface Plane" - bl_description = "Contruct a Surface Plane" - bl_options = {'REGISTER', 'UNDO'} - - def execute(self, context): - size = self.size - res_u = self.res_u - res_v = self.res_v - - bpy.ops.surface.primitive_nurbs_surface_surface_add() # add the base mesh, a NURBS Surface - - bpy.ops.transform.resize( - value=(1, 1, 0.0001), - constraint_axis=(False, False, True) - ) # make it flat - - # added surface has 16 points - - # deleting points to get plane shape - bpy.ops.object.mode_set(mode='EDIT') - bpy.ops.curve.select_all(action='DESELECT') - - context.active_object.data.splines[0].points[0].select = True - context.active_object.data.splines[0].points[1].select = True - context.active_object.data.splines[0].points[2].select = True - context.active_object.data.splines[0].points[3].select = True - bpy.ops.curve.delete(type='VERT') - - context.active_object.data.splines[0].points[8].select = True - context.active_object.data.splines[0].points[9].select = True - context.active_object.data.splines[0].points[10].select = True - context.active_object.data.splines[0].points[11].select = True - bpy.ops.curve.delete(type='VERT') - - context.active_object.data.splines[0].points[0].select = True - context.active_object.data.splines[0].points[4].select = True - bpy.ops.curve.delete(type='VERT') - context.active_object.data.splines[0].points[2].select = True - context.active_object.data.splines[0].points[5].select = True - bpy.ops.curve.delete(type='VERT') - - # assigning name - context.active_object.name = "SurfacePlane" - # select all - bpy.ops.curve.select_all(action='SELECT') - # bringing origin to center: - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='MEDIAN') - # transform scale - bpy.ops.object.transform_apply(scale=True) - - # bring object to 3d cursor - bpy.ops.object.mode_set(mode='OBJECT') - context.active_object.location = context.scene.cursor_location - bpy.ops.transform.resize(value=(size, size, size)) - - # adjust resolution in u and v direction - context.active_object.data.resolution_u = res_u - context.active_object.data.resolution_v = res_v - - return{'FINISHED'} - - -class SmoothXtimes(Operator): - bl_idname = "curve.smooth_x_times" - bl_label = "Smooth X Times" - bl_space_type = "VIEW_3D" - bl_options = {'REGISTER', 'UNDO'} - - # use of this class: - # lets you smooth till a thousand times. this is normally difficult, because - # you have to press w, click, press w, click etc. - - # get values - times = IntProperty( - name="Smooth x Times", - min=1, - max=1000, - default=1, - description="Smooth amount" - ) - - @classmethod - def poll(cls, context): - return context.mode == 'EDIT_SURFACE' - - def execute(self, context): - # smooth times - times = self.times - for i in range(1, times): - bpy.ops.curve.smooth() - - return{'FINISHED'} - - -def register(): - bpy.utils.register_class(MakeSurfaceHelpers) - bpy.utils.register_class(MakeSurfacePlane) - bpy.utils.register_class(MakeSurfaceCone) - bpy.utils.register_class(MakeSurfaceStar) - bpy.utils.register_class(MakeSurfaceWedge) - bpy.utils.register_class(SmoothXtimes) - - -def unregister(): - bpy.utils.unregister_class(MakeSurfaceHelpers) - bpy.utils.unregister_class(MakeSurfacePlane) - bpy.utils.unregister_class(MakeSurfaceCone) - bpy.utils.unregister_class(MakeSurfaceStar) - bpy.utils.unregister_class(MakeSurfaceWedge) - bpy.utils.unregister_class(SmoothXtimes) - - -if __name__ == "__main__": - register() diff --git a/tests/test_helpers/addons/add_curve_extra_objects/beveltaper_curve.py b/tests/test_helpers/addons/add_curve_extra_objects/beveltaper_curve.py deleted file mode 100644 index f91eb8b..0000000 --- a/tests/test_helpers/addons/add_curve_extra_objects/beveltaper_curve.py +++ /dev/null @@ -1,422 +0,0 @@ -# ##### 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 3 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, see . -# -# ##### END GPL LICENSE BLOCK ##### - -# DevBo Task: https://developer.blender.org/T37377 - -bl_info = { - "name": "Bevel/Taper Curve", - "author": "Cmomoney", - "version": (1, 1), - "blender": (2, 69, 0), - "location": "View3D > Object > Bevel/Taper", - "description": "Adds bevel and/or taper curve to active curve", - "warning": "", - "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/" - "Py/Scripts/Curve/Bevel_-Taper_Curve", - "category": "Curve"} - - -import bpy -from bpy.types import ( - Operator, - Menu, - ) -from bpy.props import ( - BoolProperty, - FloatProperty, - IntProperty, - ) -from bpy_extras.object_utils import ( - AddObjectHelper, - object_data_add, - ) - - -def add_taper(self, context): - scale_ends1 = self.scale_ends1 - scale_ends2 = self.scale_ends2 - scale_mid = self.scale_mid - verts = [ - (-2.0, 1.0 * scale_ends1, 0.0, 1.0), - (-1.0, 0.75 * scale_mid, 0.0, 1.0), - (0.0, 1.5 * scale_mid, 0.0, 1.0), - (1.0, 0.75 * scale_mid, 0.0, 1.0), - (2.0, 1.0 * scale_ends2, 0.0, 1.0) - ] - make_path(self, context, verts) - - -def add_type5(self, context): - scale_x = self.scale_x - scale_y = self.scale_y - verts = [ - [0.0 * scale_x, 0.049549 * scale_y, - 0.0, 0.031603 * scale_x, 0.047013 * scale_y, - 0.0, 0.05 * scale_x, 0.0 * scale_y, 0.0, - 0.031603 * scale_x, -0.047013 * scale_y, - 0.0, 0.0 * scale_x, -0.049549 * scale_y, - 0.0, -0.031603 * scale_x, -0.047013 * scale_y, - 0.0, -0.05 * scale_x, -0.0 * scale_y, 0.0, - -0.031603 * scale_x, 0.047013 * scale_y, 0.0] - ] - lhandles = [ - [(-0.008804 * scale_x, 0.049549 * scale_y, 0.0), - (0.021304 * scale_x, 0.02119 * scale_y, 0.0), - (0.05 * scale_x, 0.051228 * scale_y, 0.0), - (0.036552 * scale_x, -0.059423 * scale_y, 0.0), - (0.008804 * scale_x, -0.049549 * scale_y, 0.0), - (-0.021304 * scale_x, -0.02119 * scale_y, 0.0), - (-0.05 * scale_x, -0.051228 * scale_y, 0.0), - (-0.036552 * scale_x, 0.059423 * scale_y, 0.0)] - ] - rhandles = [ - [(0.008803 * scale_x, 0.049549 * scale_y, 0.0), - (0.036552 * scale_x, 0.059423 * scale_y, 0.0), - (0.05 * scale_x, -0.051228 * scale_y, 0.0), - (0.021304 * scale_x, -0.02119 * scale_y, 0.0), - (-0.008803 * scale_x, -0.049549 * scale_y, 0.0), - (-0.036552 * scale_x, -0.059423 * scale_y, 0.0), - (-0.05 * scale_x, 0.051228 * scale_y, 0.0), - (-0.021304 * scale_x, 0.02119 * scale_y, 0.0)] - ] - make_curve(self, context, verts, lhandles, rhandles) - - -def add_type4(self, context): - scale_x = self.scale_x - scale_y = self.scale_y - verts = [ - [-0.0 * scale_x, 0.017183 * scale_y, - 0.0, 0.05 * scale_x, 0.0 * scale_y, - 0.0, 0.0 * scale_x, -0.017183 * scale_y, - 0.0, -0.05 * scale_x, -0.0 * scale_y, 0.0] - ] - lhandles = [ - [(-0.017607 * scale_x, 0.017183 * scale_y, 0.0), - (0.05 * scale_x, 0.102456 * scale_y, 0.0), - (0.017607 * scale_x, -0.017183 * scale_y, 0.0), - (-0.05 * scale_x, -0.102456 * scale_y, 0.0)] - ] - rhandles = [ - [(0.017607 * scale_x, 0.017183 * scale_y, 0.0), - (0.05 * scale_x, -0.102456 * scale_y, 0.0), - (-0.017607 * scale_x, -0.017183 * scale_y, 0.0), - (-0.05 * scale_x, 0.102456 * scale_y, 0.0)] - ] - make_curve(self, context, verts, lhandles, rhandles) - - -def add_type3(self, context): - scale_x = self.scale_x - scale_y = self.scale_y - verts = [ - [-0.017183 * scale_x, 0.0 * scale_y, - 0.0, 0.0 * scale_x, 0.05 * scale_y, - 0.0, 0.017183 * scale_x, 0.0 * scale_y, - 0.0, 0.0 * scale_x, -0.05 * scale_y, 0.0] - ] - lhandles = [ - [(-0.017183 * scale_x, -0.017607 * scale_y, 0.0), - (-0.102456 * scale_x, 0.05 * scale_y, 0.0), - (0.017183 * scale_x, 0.017607 * scale_y, 0.0), - (0.102456 * scale_x, -0.05 * scale_y, 0.0)] - ] - rhandles = [ - [(-0.017183 * scale_x, 0.017607 * scale_y, 0.0), - (0.102456 * scale_x, 0.05 * scale_y, 0.0), - (0.017183 * scale_x, -0.017607 * scale_y, 0.0), - (-0.102456 * scale_x, -0.05 * scale_y, 0.0)] - ] - make_curve(self, context, verts, lhandles, rhandles) - - -def add_type2(self, context): - scale_x = self.scale_x - scale_y = self.scale_y - verts = [ - [-0.05 * scale_x, 0.0 * scale_y, - 0.0, 0.0 * scale_x, 0.05 * scale_y, - 0.0, 0.05 * scale_x, 0.0 * scale_y, - 0.0, 0.0 * scale_x, -0.05 * scale_y, 0.0] - ] - lhandles = [ - [(-0.05 * scale_x, -0.047606 * scale_y, 0.0), - (-0.047606 * scale_x, 0.05 * scale_y, 0.0), - (0.05 * scale_x, 0.047607 * scale_y, 0.0), - (0.047606 * scale_x, -0.05 * scale_y, 0.0)] - ] - rhandles = [ - [(-0.05 * scale_x, 0.047607 * scale_y, 0.0), - (0.047607 * scale_x, 0.05 * scale_y, 0.0), - (0.05 * scale_x, -0.047607 * scale_y, 0.0), - (-0.047607 * scale_x, -0.05 * scale_y, 0.0)] - ] - make_curve(self, context, verts, lhandles, rhandles) - - -def add_type1(self, context): - scale_x = self.scale_x - scale_y = self.scale_y - verts = [ - [-0.05 * scale_x, 0.0 * scale_y, - 0.0, 0.0 * scale_x, 0.05 * scale_y, - 0.0, 0.05 * scale_x, 0.0 * scale_y, - 0.0, 0.0 * scale_x, -0.05 * scale_y, 0.0] - ] - lhandles = [ - [(-0.05 * scale_x, -0.027606 * scale_y, 0.0), - (-0.027606 * scale_x, 0.05 * scale_y, 0.0), - (0.05 * scale_x, 0.027606 * scale_y, 0.0), - (0.027606 * scale_x, -0.05 * scale_y, 0.0)] - ] - rhandles = [ - [(-0.05 * scale_x, 0.027607 * scale_y, 0.0), - (0.027607 * scale_x, 0.05 * scale_y, 0.0), - (0.05 * scale_x, -0.027607 * scale_y, 0.0), - (-0.027607 * scale_x, -0.05 * scale_y, 0.0)] - ] - make_curve(self, context, verts, lhandles, rhandles) - - -def make_path(self, context, verts): - target = bpy.context.scene.objects.active - bpy.ops.curve.primitive_nurbs_path_add( - view_align=False, enter_editmode=False, location=(0, 0, 0) - ) - target.data.taper_object = bpy.context.scene.objects.active - taper = bpy.context.scene.objects.active - taper.name = target.name + '_Taper' - bpy.context.scene.objects.active = target - points = taper.data.splines[0].points - - for i in range(len(verts)): - points[i].co = verts[i] - - -def make_curve(self, context, verts, lh, rh): - target = bpy.context.scene.objects.active - curve_data = bpy.data.curves.new( - name=target.name + '_Bevel', type='CURVE' - ) - curve_data.dimensions = '3D' - - for p in range(len(verts)): - c = 0 - spline = curve_data.splines.new(type='BEZIER') - spline.use_cyclic_u = True - spline.bezier_points.add(len(verts[p]) / 3 - 1) - spline.bezier_points.foreach_set('co', verts[p]) - - for bp in spline.bezier_points: - bp.handle_left_type = 'ALIGNED' - bp.handle_right_type = 'ALIGNED' - bp.handle_left.xyz = lh[p][c] - bp.handle_right.xyz = rh[p][c] - c += 1 - - object_data_add(context, curve_data, operator=self) - target.data.bevel_object = bpy.context.scene.objects.active - bpy.context.scene.objects.active = target - - -class add_tapercurve(Operator): - bl_idname = "curve.tapercurve" - bl_label = "Add Curve as Taper" - bl_description = ("Add taper curve to Active Curve\n" - "Needs an existing Active Curve") - bl_options = {'REGISTER', 'UNDO'} - - scale_ends1 = FloatProperty( - name="End Width Left", - description="Adjust left end taper", - default=0.0, - min=0.0 - ) - scale_ends2 = FloatProperty( - name="End Width Right", - description="Adjust right end taper", - default=0.0, - min=0.0 - ) - scale_mid = FloatProperty( - name="Center Width", - description="Adjust taper at center", - default=1.0, - min=0.0 - ) - link1 = BoolProperty( - name="Link Ends", - description="Link the End Width Left / Right settings\n" - "End Width Left will be editable ", - default=True - ) - link2 = BoolProperty( - name="Link Ends / Center", - description="Link the End Widths with the Center Width", - default=False - ) - diff = FloatProperty( - name="Difference", - default=1, - description="Difference between ends and center while linked" - ) - - @classmethod - def poll(cls, context): - obj = context.active_object - return context.mode == 'OBJECT' and obj and obj.type == "CURVE" - - def draw(self, context): - layout = self.layout - - col = layout.column(align=True) - col.label("Settings:") - split = layout.split(percentage=0.95, align=True) - split.active = not self.link2 - col = split.column(align=True) - col.prop(self, "scale_ends1") - - row = split.row(align=True) - row.scale_y = 2.0 - col_sub = col.column(align=True) - col_sub.active = not self.link1 - col_sub.prop(self, "scale_ends2") - row.prop(self, "link1", toggle=True, text="", icon="LINKED") - - split = layout.split(percentage=0.95, align=True) - col = split.column(align=True) - col.prop(self, "scale_mid") - - row = split.row(align=True) - row.scale_y = 2.0 - col_sub = col.column(align=True) - col_sub.active = self.link2 - row.prop(self, "link2", toggle=True, text="", icon="LINKED") - col_sub.prop(self, "diff") - - def execute(self, context): - if self.link1: - self.scale_ends2 = self.scale_ends1 - - if self.link2: - self.scale_ends2 = self.scale_ends1 = self.scale_mid - self.diff - - add_taper(self, context) - - return {'FINISHED'} - - -class add_bevelcurve(Operator, AddObjectHelper): - bl_idname = "curve.bevelcurve" - bl_label = "Add Curve as Bevel" - bl_description = ("Add bevel curve to Active Curve\n" - "Needs an existing Active Curve") - bl_options = {'REGISTER', 'UNDO'} - - types = IntProperty( - name="Type", - description="Type of bevel curve", - default=1, - min=1, max=5 - ) - scale_x = FloatProperty( - name="Scale X", - description="Scale on X axis", - default=1.0 - ) - scale_y = FloatProperty( - name="Scale Y", - description="Scale on Y axis", - default=1.0 - ) - link = BoolProperty( - name="Link XY", - description="Link the Scale on X/Y axis", - default=True - ) - - @classmethod - def poll(cls, context): - obj = context.active_object - return context.mode == 'OBJECT' and obj and obj.type == "CURVE" - - def draw(self, context): - layout = self.layout - - col = layout.column(align=True) - # AddObjectHelper props - col.prop(self, "view_align") - col.prop(self, "location") - col.prop(self, "rotation") - - col = layout.column(align=True) - col.label("Settings:") - col.prop(self, "types") - - split = layout.split(percentage=0.95, align=True) - col = split.column(align=True) - col.prop(self, "scale_x") - row = split.row(align=True) - row.scale_y = 2.0 - col.prop(self, "scale_y") - row.prop(self, "link", toggle=True, text="", icon="LINKED") - - def execute(self, context): - if self.link: - self.scale_y = self.scale_x - if self.types == 1: - add_type1(self, context) - if self.types == 2: - add_type2(self, context) - if self.types == 3: - add_type3(self, context) - if self.types == 4: - add_type4(self, context) - if self.types == 5: - add_type5(self, context) - - return {'FINISHED'} - - -class Bevel_Taper_Curve_Menu(Menu): - bl_label = "Bevel/Taper" - bl_idname = "OBJECT_MT_bevel_taper_curve_menu" - - def draw(self, context): - layout = self.layout - - layout.operator("curve.bevelcurve") - layout.operator("curve.tapercurve") - - -def menu_funcs(self, context): - if bpy.context.scene.objects.active.type == "CURVE": - self.layout.menu("OBJECT_MT_bevel_taper_curve_menu") - - -def register(): - bpy.utils.register_module(__name__) - bpy.types.VIEW3D_MT_object.append(menu_funcs) - - -def unregister(): - bpy.utils.unregister_module(__name__) - bpy.types.VIEW3D_MT_object.remove(menu_funcs) - - -if __name__ == "__main__": - register() diff --git a/tests/test_helpers/addons/dir_addon/123AA_sort_this_dir_first/__init__.py b/tests/test_helpers/addons/dir_addon/123AA_sort_this_dir_first/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_helpers/addons/add_curve_extra_objects/__init__.py b/tests/test_helpers/addons/dir_addon/__init__.py similarity index 100% rename from tests/test_helpers/addons/add_curve_extra_objects/__init__.py rename to tests/test_helpers/addons/dir_addon/__init__.py diff --git a/tests/test_helpers/addons/dir_addon/other.py b/tests/test_helpers/addons/dir_addon/other.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_helpers/addons/invalid_addon.py b/tests/test_helpers/addons/invalid_addon.py new file mode 100644 index 0000000..11388b1 --- /dev/null +++ b/tests/test_helpers/addons/invalid_addon.py @@ -0,0 +1,12 @@ +bl_info = { + "author": "testscreenings, PKHG, TrumanBlending", + "version": (0, 1, 2), + "blender": (2, 59, 0), + "location": "View3D > Add > Curve", + "description": "Adds generated ivy to a mesh object starting " + "at the 3D cursor", + "warning": "", + "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/" + "Scripts/Curve/Ivy_Gen", + "category": "Add Curve", +} diff --git a/tests/test_helpers/addons/not_an_addon.py b/tests/test_helpers/addons/not_an_addon.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_helpers/addons/add_curve_ivygen.py b/tests/test_helpers/addons/real_addon.py similarity index 100% rename from tests/test_helpers/addons/add_curve_ivygen.py rename to tests/test_helpers/addons/real_addon.py diff --git a/tests/test_helpers/addons/repo.json b/tests/test_helpers/addons/repo.json new file mode 100644 index 0000000..86db477 --- /dev/null +++ b/tests/test_helpers/addons/repo.json @@ -0,0 +1,70 @@ +{ + "packages": [ + { + "bl_info": { + "author": "Multiple Authors", + "blender": [ + 2, + 76, + 0 + ], + "category": "Add Curve", + "description": "Add extra curve object types", + "location": "View3D > Add > Curve > Extra Objects", + "name": "Extra Objects", + "version": [ + 0, + 1, + 2 + ], + "warning": "", + "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Curve/Curve_Objects" + }, + "type": "addon" + }, + { + "bl_info": { + "author": "testscreenings, PKHG, TrumanBlending", + "blender": [ + 2, + 59, + 0 + ], + "category": "Add Curve", + "description": "Adds generated ivy to a mesh object starting at the 3D cursor", + "location": "View3D > Add > Curve", + "name": "IvyGen", + "version": [ + 0, + 1, + 2 + ], + "warning": "", + "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Curve/Ivy_Gen" + }, + "type": "addon" + }, + { + "bl_info": { + "author": "Multiple Authors", + "blender": [ + 2, + 76, + 0 + ], + "category": "Add Curve", + "description": "Add extra curve object types", + "location": "View3D > Add > Curve > Extra Objects", + "name": "Extra Objects", + "version": [ + 0, + 1, + 2 + ], + "warning": "", + "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Curve/Curve_Objects" + }, + "type": "addon" + } + ] +} \ No newline at end of file diff --git a/tests/test_helpers/addons/zipped_addon.zip b/tests/test_helpers/addons/zipped_addon.zip new file mode 100644 index 0000000000000000000000000000000000000000..b7682c6d9a434aa6f0f6621d2f8675f26564012c GIT binary patch literal 4291 zcmb7{cT^MIx`zWqAao%h2vU@eL;@I!AT<(N5a|#IAiYKDMS7QJD1tyFDpjNi(xn## z!4^6w9qGM%^b5yx@40$B=bk&W_RRic=J~yQziZ8`=e9ZtkOFXaz!C=3{xVLx38=qh%C7DiejV-AT%Aa4)zj3SL z^XN_D|NIz7!-?04o4tkQB7LhlnR!WVsPAxf_vx}caj@vOg|cCR*I9A_0b|ZT!ZOE| ziiag|kec~1Y#p-v+oj6RKGTxD&Mp0MImKIU#fZ!88egNDJ{4HS@>$XHJC0kLB(-w9P*9iTN2=gD&(BJw74(M zs{zUvzm-fsgEEWPz9``cmc&ucS!;F6uk(YXI9toO;|PqU@#I+}p|iqN_9%}P=@%u{ zL5m;rN7!jPJuvbYv&WcYrF#zQI)b)V$&S94E}s{*djI(GL;xsBAP7#J&0LziaeK%k z@LYwUIfUwc=1u7B)v$Fw^SH!lgucp42zXtigg#2b^$_t_a@*yiq?~DU;R4BgUEwyQ z%=*G~maRN|>K&c3oJKIaNbw$ctwf2-3Woxvw2*JqD16V#!quP5EVUk#OV@C~x@%+m zs_Kf4U|Q*y%?gzsI{OTLmG5;U87I+idAE1w7P=t`IKy_3**3$2hSE7DR)$G zhaE5M;4bqVORAiy=MlRXWX*}weA5ximu-ab;oGF8<^}rmYqCS{T>M8@-m7Us3J!#kqfHM?#@QK&JH4~IlQ*F-CB2PA}d065cQo1C!d}J7> z6Az6P$oX>5yFsD|0Z1O6`t?<(M8P~4>3L!&Jb3LJ+~`_K*>>0`*CmkDjhVH@A@$i#fqTTc z)H*L?+*St&#|yn*B=_F2I5hMY*o+-6VxY9@Hey@`EI-oU%OBjiB!-8OLa2$+>dko!yi8qL!5nJ(e$mD}Y@>$th*Sy?K4H8m z(ymVtX%Qd?vhO|w8Sck4tzoK%m99rHv*e0fe5GroBAtFPsg&*gz(@72s?S#EnqtERB&|9N|CKD(srujO(^(NBAK!uE`B0!D5(!-6J zJ1;df%$P4S9KXH(Jk!&i2x7ycrOzl!AK)#mZ$-W`Tq0NQt&MIBu6kH~Z75ka!aL*X zFe&IO>P`p{MA@RnA^k+dAOmZl7*Is9 z9F8tVP$b`=;j~x+<}XDLxyB@G?721Zr3N17-xaBMmx)q@ZAr#QYdyY{8D;3%f3&ai z^hYeZy~X~LiB3>O&~UUni@ePvR)Vf^*{DNtbDL2-J4ff&@K{v|v3?W^{IbkNtpI2k zM*>3f(+2u9QnJ^@TYnHZZ{SP|cWSw?D>l5?+t$Fn=E*s$J6=TtDqPB(9{YUIeMPttAv*%i^X zC?3(|a;&J6dDO;Morx%=hJ>lZmuy^quj7N9G1tOkHd3=5aLd~|Lew}DrC?=wtub+LU(?;RpK3hsmZD-ZhFJ!sTOFE7fTDpCUD0=OT(x zg}S_4gXKf_=KXWNJ#ilJSn&+)gRDFu8RZLg^5+Qrt6`#5ce){3Yf;u?N3xQ&TzqN2 zL67zHo6o(tJy!Rf`j}-0GZG3rzPL@g+q$r8X>-rXI_ue%pBEZi?I%|~%v0&6=n~44 z9=B2DQ0_H$se&wk;gbxzw36)5?2I4LWMqH|H~zKLC#>oqnP!V<47|vMnL3BlH0-vD zr{SN*+ z-!8V5c8k``RRHik$hTWjqK~!p88kHJfnxaEd~Nl(H)++ddj~3bBc+<_3Ph$}E5pb6 zgu`SZVow6s^XKy;eXHx0GVcH@SM$Dsj@zUtwYgCxFayo9pqa@?m&Cu^Mt~p$^ApnCr5O9flcV#dNv2de6_#VwrGweVa+p~>}BlU6yZEOTO;!NP#@^~dB;*|fj9PZSw>4}ihL!v^y5HK0qxZ^=+@m>pE;3nZSSZ+ z@Ud}qyN36%C2u#Q9Of~FL>igv#9jlcn#Es|CH=WzULCD)$Uxk99kJjWqKt_F;z!`^ znx6X((p|SA_d6!^X5+NgQJY{)#rj4F^VQGNrOY}t!^E<@ahC*0kMOQ0= zPvPF|^G_o!`(bYe!CI==Yi>TpY$1BXAF;vZ;Zs&%-y%ei8C7NjTo&UmGP1enDakJ5 zQH%D?8a~GR6XrdRB9T?bLlWKBW&AQml;uFsZ`kEmFOrbR1izq-3qg*_4|1h}L?)v( zkpq7Fp7&&BQimP(=iWJF0-E_Ypj(`%i zbFxL7<$;+QS^;+C+YIaiSHmB)HFy?^ZDj0*URyr0HON^gOujnWd$<;81;@jUsv)FC zl9l-vC)Pi!8&C|-6{agR_Ay^yvR?|0u0jGH+8PHsOSjWIh@*sFaoBo!W%3egAs%IR zuHYT6@-|}|OZ6v-8@cZOk+!f=*3D+l=c}DZqwt!?D;-f1Gq*e4d35w(q|TTPb!TZ? z)Qy(G%3bwRFXx)pgJzExLm!R&*=cCQCznKw3B9;Kf2TSTW2jAvL4M@9>r%q%C9Q-%Ue!MyG z!{?bB+>h&tV%iA$FU7|Q$*RqM+EjO@mLE<;`B0P%O6Zs|@72Is_>G%vI(cA093$2x z%!X`Z3H%BEV^HF5)y2Rw=lbbw`q}-zcpLXG&IJ_}Q&2#=VO-tO?soQW=rg0Uv3GTI z|7mo(u^L7er{fPs_h>zxIK=KY(X-}RIJmmrM0owe(Kd*hjgo{fJhnuwU4 z&z`#(Z|?*#oXp@F}I{-ewPsw3w} T&w6+6^qf51`VXgX0|5U8W(G+z literal 0 HcmV?d00001 diff --git a/tests/test_helpers/extra_objects_blinfo.txt b/tests/test_helpers/dir_addon_output similarity index 100% rename from tests/test_helpers/extra_objects_blinfo.txt rename to tests/test_helpers/dir_addon_output diff --git a/tests/test_helpers/ivy_gen_blinfo.txt b/tests/test_helpers/real_addon.py_output similarity index 100% rename from tests/test_helpers/ivy_gen_blinfo.txt rename to tests/test_helpers/real_addon.py_output diff --git a/tests/test_helpers/zipped_addon.zip_output b/tests/test_helpers/zipped_addon.zip_output new file mode 100644 index 0000000..f7a28bd --- /dev/null +++ b/tests/test_helpers/zipped_addon.zip_output @@ -0,0 +1 @@ +{'name': 'Extra Objects', 'author': 'Multiple Authors', 'version': (0, 1, 2), 'blender': (2, 76, 0), 'location': 'View3D > Add > Curve > Extra Objects', 'description': 'Add extra curve object types', 'warning': '', 'wiki_url': 'https://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Curve/Curve_Objects', 'category': 'Add Curve'} \ No newline at end of file