Skip to content
Snippets Groups Projects
add_curve_braid.py 7.84 KiB
Newer Older
  • Learn to ignore specific revisions
  • # SPDX-License-Identifier: GPL-2.0-or-later
    
    bl_info = {
        "name": "New Braid",
        "author": "Jared Forsyth <github.com/jaredly>",
    
        "version": (1, 0, 3),
        "blender": (2, 80, 0),
    
        "location": "View3D > Add > Mesh > New Braid",
        "description": "Adds a new Braid",
        "warning": "",
    
        "doc_url": "{BLENDER_MANUAL_URL}/addons/add_curve/extra_objects.html",
    
    """
    
    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_select = True
    
        return circle
    
    
    def defaultStar():
        star = poly_lines('star', 'staz', [tuple(star_pts(points=5, r=.5, ir=.05))], type='NURBS')
    
        star.hide_select = 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)
    
        bl_idname = "curve.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
                )
    
        edit_mode : BoolProperty(
                name="Show in edit mode",
                default=True,
                description="Show in edit mode"
                )
    
    
        def draw(self, context):
            layout = self.layout
    
            box = layout.box()
            col = box.column(align=True)
    
            col.label(text="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(text="Geometry Options:")
    
            col.prop(self, "strandsize")
            col.prop(self, "resolution")
    
            col = layout.column()
            col.row().prop(self, "edit_mode", expand=True)
    
    
        def execute(self, context):
    
             # turn off 'Enter Edit Mode'
            use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode
            bpy.context.preferences.edit.use_enter_edit_mode = False
    
            circle = defaultCircle(self.strandsize)
    
            context.scene.collection.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.collection.objects.link(braid)
    
    
            for ob in context.scene.objects:
    
                ob.select_set(False)
    
            braid.select_set(True)
            bpy.context.view_layer.objects.active = braid
    
            if use_enter_edit_mode:
                bpy.ops.object.mode_set(mode = 'EDIT')
    
            # restore pre operator state
            bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode
    
            if self.edit_mode:
                bpy.ops.object.mode_set(mode = 'EDIT')
            else:
                bpy.ops.object.mode_set(mode = 'OBJECT')
    
    
    
    
    def register():
        bpy.utils.register_class(Braid)
    
    
    def unregister():
        bpy.utils.unregister_class(Braid)
    
    
    if __name__ == "__main__":
        register()