diff --git a/blenderpack.py b/blenderpack.py index c8244af..68f7d41 100755 --- a/blenderpack.py +++ b/blenderpack.py @@ -58,7 +58,7 @@ def extract_blinfo(path): addon_name = os.path.split(path.rstrip(os.path.sep))[1] if os.path.isdir(path): - with open(os.path.join(path, '__init__.py' 'r')) as f: + with open(os.path.join(path, '__init__.py'), 'r') as f: source = f.read() else: diff --git a/tests/test_blenderpack.py b/tests/test_blenderpack.py index 5146eaa..c685b05 100644 --- a/tests/test_blenderpack.py +++ b/tests/test_blenderpack.py @@ -22,6 +22,13 @@ class test_blenderpack_make_repo(unittest.TestCase): 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) + # def test_validpath(self): # blenderpack.make_repo(os.path.join('test_helpers', 'addons')) diff --git a/tests/test_helpers/addons/add_curve_extra_objects/__init__.py b/tests/test_helpers/addons/add_curve_extra_objects/__init__.py new file mode 100644 index 0000000..ae8cdf8 --- /dev/null +++ b/tests/test_helpers/addons/add_curve_extra_objects/__init__.py @@ -0,0 +1,301 @@ +# ##### 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 ##### +# Contributed to by: +# testscreenings, Alejandro Omar Chocano Vasquez, Jimmy Hazevoet, meta-androcto # +# Cmomoney, Jared Forsyth, Adam Newgas, Spivak Vladimir, Jared Forsyth, Atom # +# Antonio Osprite, Marius Giurgi (DolphinDream) + +bl_info = { + "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" + } + +if "bpy" in locals(): + import importlib + importlib.reload(add_curve_aceous_galore) + importlib.reload(add_curve_spirals) + importlib.reload(add_curve_torus_knots) + importlib.reload(add_surface_plane_cone) + importlib.reload(add_curve_curly) + importlib.reload(beveltaper_curve) + importlib.reload(add_curve_celtic_links) + importlib.reload(add_curve_braid) + importlib.reload(add_curve_simple) + importlib.reload(add_curve_spirofit_bouncespline) + +else: + from . import add_curve_aceous_galore + from . import add_curve_spirals + from . import add_curve_torus_knots + from . import add_surface_plane_cone + from . import add_curve_curly + from . import beveltaper_curve + from . import add_curve_celtic_links + from . import add_curve_braid + from . import add_curve_simple + from . import add_curve_spirofit_bouncespline + +import bpy +from bpy.types import ( + Menu, + AddonPreferences, + ) +from bpy.props import ( + StringProperty, + BoolProperty, + ) + + +def convert_old_presets(data_path, msg_data_path, old_preset_subdir, + new_preset_subdir, fixdic={}, ext=".py"): + """ + convert old presets + """ + + def convert_presets(self, context): + if not getattr(self, data_path, False): + return None + import os + + target_path = os.path.join("presets", old_preset_subdir) + target_path = bpy.utils.user_resource('SCRIPTS', + target_path) + + # created an anytype op to run against preset + op = type('', (), {})() + + files = [f for f in os.listdir(target_path) if f.endswith(ext)] + if not files: + print("No old presets in %s" % target_path) + setattr(self, msg_data_path, "No old presets") + return None + + new_target_path = os.path.join("presets", new_preset_subdir) + new_target_path = bpy.utils.user_resource('SCRIPTS', + new_target_path, + create=True) + for f in files: + file = open(os.path.join(target_path, f)) + for line in file: + if line.startswith("op."): + exec(line) + file.close() + for key, items in fixdic.items(): + if hasattr(op, key) and isinstance(getattr(op, key), int): + setattr(op, key, items[getattr(op, key)]) + # create a new one + new_file_path = os.path.join(new_target_path, f) + if os.path.isfile(new_file_path): + # do nothing + print("Preset %s already exists, passing..." % f) + continue + file_preset = open(new_file_path, 'w') + file_preset.write("import bpy\n") + file_preset.write("op = bpy.context.active_operator\n") + + for prop, value in vars(op).items(): + if isinstance(value, str): + file_preset.write("op.%s = '%s'\n" % (prop, str(value))) + else: + file_preset.write("op.%s = %s\n" % (prop, str(value))) + file_preset.close() + print("Writing new preset to %s" % new_file_path) + + setattr(self, msg_data_path, "Converted %d old presets" % len(files)) + return None + + return convert_presets + + +# Addons Preferences + +class CurveExtraObjectsAddonPreferences(AddonPreferences): + bl_idname = __name__ + + spiral_fixdic = { + "spiral_type": ['ARCH', 'ARCH', 'LOG', 'SPHERE', 'TORUS'], + "curve_type": ['POLY', 'NURBS'], + "spiral_direction": ['COUNTER_CLOCKWISE', 'CLOCKWISE'] + } + update_spiral_presets_msg = StringProperty( + default="Nothing to do" + ) + update_spiral_presets = BoolProperty( + name="Update Old Presets", + description="Update presets to reflect data changes", + default=False, + update=convert_old_presets( + "update_spiral_presets", # this props name + "update_spiral_presets_msg", # message prop + "operator/curve.spirals", + "curve_extras/curve.spirals", + fixdic=spiral_fixdic + ) + ) + show_menu_list = BoolProperty( + name="Menu List", + description="Show/Hide the Add Menu items", + default=False + ) + show_panel_list = BoolProperty( + name="Panels List", + description="Show/Hide the Panel items", + default=False + ) + + def draw(self, context): + layout = self.layout + box = layout.box() + box.label(text="Spirals:") + + if self.update_spiral_presets: + box.label(self.update_spiral_presets_msg, icon="FILE_TICK") + else: + box.prop(self, "update_spiral_presets") + + icon_1 = "TRIA_RIGHT" if not self.show_menu_list else "TRIA_DOWN" + box = layout.box() + box.prop(self, "show_menu_list", emboss=False, icon=icon_1) + + if self.show_menu_list: + box.label(text="Items located in the Add Menu > Curve (default shortcut Ctrl + A):", + icon="LAYER_USED") + box.label(text="2D Objects:", icon="LAYER_ACTIVE") + box.label(text="Angle, Arc, Circle, Distance, Ellipse, Line, Point, Polygon,", + icon="LAYER_USED") + box.label(text="Polygon ab, Rectangle, Rhomb, Sector, Segment, Trapezoid", + icon="LAYER_USED") + box.label(text="Curve Profiles:", icon="LAYER_ACTIVE") + box.label(text="Arc, Arrow, Cogwheel, Cycloid, Flower, Helix (3D),", + icon="LAYER_USED") + box.label(text="Noise (3D), Nsided, Profile, Rectangle, Splat, Star", + icon="LAYER_USED") + box.label(text="Curve Spirals:", icon="LAYER_ACTIVE") + box.label(text="Archemedian, Logarithmic, Spheric, Torus", + icon="LAYER_USED") + box.label(text="Knots:", icon="LAYER_ACTIVE") + box.label(text="Torus Knots Plus, Celtic Links, Braid Knot", + icon="LAYER_USED") + box.label(text="Curly Curve", icon="LAYER_ACTIVE") + box.label(text="Bevel/Taper:", icon="LAYER_ACTIVE") + box.label(text="Add Curve as Bevel, Add Curve as Taper", + icon="LAYER_USED") + + box.label(text="Items located in the Add Menu > Surface (default shortcut Ctrl + A):", + icon="LAYER_USED") + box.label(text="Wedge, Cone, Star, Plane", + icon="LAYER_ACTIVE") + + icon_2 = "TRIA_RIGHT" if not self.show_panel_list else "TRIA_DOWN" + box = layout.box() + box.prop(self, "show_panel_list", emboss=False, icon=icon_2) + + if self.show_panel_list: + box.label(text="Panel located in 3D View Tools Region > Create:", + icon="LAYER_ACTIVE") + box.label(text="Spline:", icon="LAYER_ACTIVE") + box.label(text="SpiroFit, Bounce Spline, Catenary", icon="LAYER_USED") + box.label(text="Panel located in 3D View Tools Region > Tools:", + icon="LAYER_ACTIVE") + box.label(text="Simple Curve:", icon="LAYER_ACTIVE") + box.label(text="Available if the Active Object is a Curve was created with 2D Objects", + icon="LAYER_USED") + + +class INFO_MT_curve_knots_add1(Menu): + # Define the "Extras" menu + bl_idname = "curve_knots_add" + bl_label = "Plants" + + def draw(self, context): + layout = self.layout + layout.operator_context = 'INVOKE_REGION_WIN' + + layout.operator("curve.torus_knot_plus", text="Torus Knot Plus") + layout.operator("curve.celtic_links", text="Celtic Links") + layout.operator("mesh.add_braid", text="Braid Knot") + + +# Define "Extras" menus +def menu_func(self, context): + if context.mode != 'OBJECT': + # fix in D2142 will allow to work in EDIT_CURVE + return None + + layout = self.layout + + layout.operator_menu_enum("mesh.curveaceous_galore", "ProfileType", + icon='CURVE_DATA') + layout.operator_menu_enum("curve.spirals", "spiral_type", + icon='CURVE_DATA') + layout.separator() + + layout.menu("curve_knots_add", text="Knots", icon='CURVE_DATA') + layout.separator() + layout.operator("curve.curlycurve", text="Curly Curve", + icon='CURVE_DATA') + layout.menu("OBJECT_MT_bevel_taper_curve_menu", text="Bevel/Taper", + icon='CURVE_DATA') + + +def menu_surface(self, context): + self.layout.separator() + if context.mode == 'EDIT_SURFACE': + self.layout.operator("curve.smooth_x_times", + text="Special Smooth", icon="MOD_CURVE") + elif context.mode == 'OBJECT': + self.layout.operator("object.add_surface_wedge", text="Wedge", + icon="SURFACE_DATA") + self.layout.operator("object.add_surface_cone", text="Cone", + icon="SURFACE_DATA") + self.layout.operator("object.add_surface_star", text="Star", + icon="SURFACE_DATA") + self.layout.operator("object.add_surface_plane", text="Plane", + icon="SURFACE_DATA") + + +def register(): + add_curve_simple.register() + bpy.utils.register_module(__name__) + + # Add "Extras" menu to the "Add Curve" menu + bpy.types.INFO_MT_curve_add.append(menu_func) + # Add "Extras" menu to the "Add Surface" menu + bpy.types.INFO_MT_surface_add.append(menu_surface) + + +def unregister(): + add_curve_simple.unregister() + # Remove "Extras" menu from the "Add Curve" menu. + bpy.types.INFO_MT_curve_add.remove(menu_func) + # Remove "Extras" menu from the "Add Surface" menu. + bpy.types.INFO_MT_surface_add.remove(menu_surface) + + bpy.utils.unregister_module(__name__) + + +if __name__ == "__main__": + register() diff --git a/tests/test_helpers/addons/add_curve_extra_objects/add_curve_aceous_galore.py b/tests/test_helpers/addons/add_curve_extra_objects/add_curve_aceous_galore.py new file mode 100644 index 0000000..59e1cae --- /dev/null +++ b/tests/test_helpers/addons/add_curve_extra_objects/add_curve_aceous_galore.py @@ -0,0 +1,1462 @@ +# ##### 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": "Curveaceous Galore!", + "author": "Jimmy Hazevoet, testscreenings", + "version": (0, 2, 1), + "blender": (2, 59), + "location": "View3D > 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 new file mode 100644 index 0000000..90b41e5 --- /dev/null +++ b/tests/test_helpers/addons/add_curve_extra_objects/add_curve_braid.py @@ -0,0 +1,261 @@ +# 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 new file mode 100644 index 0000000..66b800b --- /dev/null +++ b/tests/test_helpers/addons/add_curve_extra_objects/add_curve_celtic_links.py @@ -0,0 +1,284 @@ +# 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 new file mode 100644 index 0000000..779689a --- /dev/null +++ b/tests/test_helpers/addons/add_curve_extra_objects/add_curve_curly.py @@ -0,0 +1,491 @@ +# 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 new file mode 100644 index 0000000..3e85fa1 --- /dev/null +++ b/tests/test_helpers/addons/add_curve_extra_objects/add_curve_simple.py @@ -0,0 +1,1747 @@ +# ##### 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 new file mode 100644 index 0000000..522f637 --- /dev/null +++ b/tests/test_helpers/addons/add_curve_extra_objects/add_curve_spirals.py @@ -0,0 +1,424 @@ +# 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 new file mode 100644 index 0000000..33caf12 --- /dev/null +++ b/tests/test_helpers/addons/add_curve_extra_objects/add_curve_spirofit_bouncespline.py @@ -0,0 +1,1014 @@ +# ##### 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 new file mode 100644 index 0000000..e093257 --- /dev/null +++ b/tests/test_helpers/addons/add_curve_extra_objects/add_curve_torus_knots.py @@ -0,0 +1,726 @@ +# ##### 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 new file mode 100644 index 0000000..dcbb5b5 --- /dev/null +++ b/tests/test_helpers/addons/add_curve_extra_objects/add_surface_plane_cone.py @@ -0,0 +1,398 @@ +# 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 new file mode 100644 index 0000000..f91eb8b --- /dev/null +++ b/tests/test_helpers/addons/add_curve_extra_objects/beveltaper_curve.py @@ -0,0 +1,422 @@ +# ##### 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()