Celtic Knot Generator

Very nice Nikitron! Congrats top-notch <

This is a very cool script ! Works like magic.

One thing though, for whatever reason on my PC, the script causes some kind of problem where I seemingly have to hard reset when I try it on a complicated mesh.

edit:
It looks like the problem happens when thickness >0.

Any chance of updating this one? Iā€™ve got some uses for it, just tried it and itā€™s not showing up in the add-ons list!

What needs updating? Other people have confirmed itā€™s working ok.

Thanks for the quick reply! I should have been more detailed. I install it but itā€™s not showing in the addons list. I can see it in the addons folder. Blender 2.70.

[edit] Iā€™m wondering if the problem is with me. I just tried installing Amaranth Toolset and failed with that as well. ugh. Iā€™ll try some other things. Sorry to bother.

[re-edit] just got amaranth working but still no celtic knot. :stuck_out_tongue:

[more] finally got it to work, and Iā€™m playing with it now. thanks! my own brain got in my way, Iā€™m afraidā€¦

Thanks for this. I spent hours trying to make it do what I wanted, but next time will be much easier. Learned quite a bit. hereā€™s the WIP where I used your Celtic knot addon.



The chalice on the left. THANKS AGAIN!!! :slight_smile:

Very nice, it seems like this can be used to make something more simple? Like if I wanted to make a knitted scarf? Just wondering. Havenā€™t really got the time to play around with it at the moment.

guitartom: thatā€™s awesome. Youā€™ve got a lot more imagination than I do on how to use the plugin.

RealityFox: Anything with quad tesselation will give a decent weaved look, which I think works as a good stand in for knitted. See the sweater example before. You can get additional control by changing the mesh. Subdividing edges in particular is helpful, that is how I made this chain mail.


The plugin doesnā€™t support it, but potentially you add walls (http://www.entrelacs.net/Walls) for even more control. I was considering hooking this up to edge sharpness or something (the hard part is input).

hey Boris! thanks, but I was only trying to somewhat match what I saw in a pictureā€¦Itā€™s tools like this that make everything much easier. Iā€™ll be VERY interested to see if you develop this further. As it is, this will be one of my fav addons for a while. Thanks again.

PS I love the link that talks about the celtic knotsā€¦makes every thing seem simple.

Ah okay, Iā€™ll have to play with this a bit then. Cool addon nonetheless, although I was hoping for something more like this:

Itā€™s a 3d model/render from the mitsuba-renderer site. Would love to find a way to make something like this. I saw it on youtube, but the software hasnā€™t been made available as far as I know :confused:

Video^

Ah, I see better now what you mean.

If you ignore the large scale assigning of patterns, and just consider the conversion of coloured polygons to the corresponding threads, then itā€™s not dissimilar to this plug in. But not within its capabilities at the moment.

Though adding the knit style threading is not too complicated, I think adding a UI that lets you control it to any reasonable extent is beyond my abilities. Iā€™m not really aware of any Blender addons that let you paint additional layers of data onto a mesh, nor specify orientations, which I could crib from.

Also, blender is likely to explode - itā€™s very easy to create a ludicrously hi poly model this way if youā€™re not careful.

Hi Boris. I like your addon and it is really fun to play with it. I try myself to make some weaven look. But one think was odd. the ā€œthicknessā€ was not applying correct, but just some splines. (Iā€™m trying this with the latest 2.70_VS13 version) The workaround is easy. Just set the curve radius for every spline. Since this was so simple I integrated it myself:


I hope that this simple solution will fix it, instead of breaking more.

Not sure I get it. For me the curve defaults to radius 1 - which works fine. Iā€™ll have to retry on 2.70 when I get the chance. Thanks for the feedback.

I had the same problem. the daily builds didnā€™t like the celtic knot generator, so my solution was to try the official release, which works fine. I also had a similar problem and solution with the b-surface addon.

Also, blender is likely to explode - itā€™s very easy to create a ludicrously hi poly model this way if youā€™re not careful.

Blender would probably explode. Which is why Iā€™m really looking forward to the new viewport that is apparently being worked on for the Google Summer of code. Should really give an increase to how many polygons Blender can handle.

But yah, Iā€™ll have to play with your addon, seems like I can still use it for some of my projects for sure anyway.

moksha, guitartom: Ok, I checked it in in case someone else comes across it.

I also fixed another bug that was affecting the weave up/down settings.

this isnā€™t working with 2.74 =/ any hints?

hi, works great still.
create mesh object to cover with knot,
subdivide base object for more weave.
add curve celtic knot from mesh

Yeah, it still seems fine to me.

hi, I made some changes to remove the mesh bevel object & just use the curve bevel.
on a subdivided mesh, the old way would crash blender creating too large mesh.


# Blender plugin for generating celtic knot curves from 3d meshes
# See README for more information
#
# The MIT License (MIT)
#
# Copyright (c) 2013 Adam Newgas
#

bl_info = {
    "name": "Celtic Knot",
    "description": "",
    "author": "Adam Newgas",
    "version": (0,1,1),
    "blender": (2, 68, 0),
    "location": "View3D &gt; Add &gt; Curve",
    "warning": "",
    "wiki_url": "https://github.com/BorisTheBrave/celtic-knot/wiki",
    "category": "Add Curve"}


import bpy
import bmesh
from collections import defaultdict
from mathutils import Vector
from math import pi,sin,cos


class CelticKnotOperator(bpy.types.Operator):
    bl_idname = "curve.celtic_knot"
    bl_label = "Celtic Knot"
    bl_description = 'Select low poly Mesh Object to cover with Knot'
    bl_options = {'REGISTER', 'UNDO', 'PRESET'}


    weave_up = bpy.props.FloatProperty(name="Weave Up",
                                       description="Distance to shift curve upwards over knots",
                                       subtype="DISTANCE",
                                       unit="LENGTH")
    weave_down = bpy.props.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 = bpy.props.EnumProperty(items=handle_types,
                                         name="Handle Type",
                                         description="Controls what type the bezier control points use",
                                         default="AUTO")
    crossing_angle = bpy.props.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 = bpy.props.FloatProperty(name="Crossing Strength",
                                                description="Aligned only: strenth of bezier control points",
                                                soft_min=0,
                                                subtype="DISTANCE",
                                                unit="LENGTH")




    handle_type_map = {"AUTO":"AUTOMATIC","ALIGNED":"ALIGNED"}


    @classmethod
    def poll(cls, context):
        ob = context.active_object
        #return True
        return ((ob is not None) and
                (ob.mode == "OBJECT") and
                (ob.type == "MESH") and
                (context.mode == "OBJECT"))


    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] == 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] == 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
        context.scene.objects.active = orig_obj
        # If thick, then give it a bevel_object and convert to mesh


        return {'FINISHED'}


def menu_func(self, context):
    self.layout.operator(CelticKnotOperator.bl_idname,
                         text="Celtic Knot From Mesh",
                         icon='PLUGIN')


def register():
    bpy.utils.register_module(__name__)
    bpy.types.INFO_MT_curve_add.append(menu_func)




def unregister():
    bpy.types.INFO_MT_curve_add.remove(menu_func)
    bpy.utils.unregister_module(__name__)


if __name__ == "__main__":
    register()



now you select mesh, add celtic cover/curve, edit parameters, then select the curve & in curve panel to adjust the bevel thickness.
cool addon. thanks.