36
36
37
37
import bpy
38
38
import bmesh
39
+ from bpy_extras import object_utils
39
40
from collections import defaultdict
40
41
from mathutils import Vector
41
42
from math import pi , sin , cos
42
43
43
44
HANDLE_TYPE_MAP = {"AUTO" : "AUTOMATIC" , "ALIGNED" : "ALIGNED" }
44
45
46
+ # Twist types
45
47
TWIST_CW = "TWIST_CW"
46
48
STRAIGHT = "STRAIGHT"
47
49
TWIST_CCW = "TWIST_CCW"
48
50
IGNORE = "IGNORE"
49
51
52
+ # output types
53
+ BEZIER = "BEZIER"
54
+ PIPE = "PIPE"
55
+ RIBBON = "RIBBON"
56
+
50
57
51
58
def get_celtic_twists (bm ):
52
59
twists = []
@@ -58,6 +65,59 @@ def get_celtic_twists(bm):
58
65
return twists
59
66
60
67
68
+ class RibbonBuilder :
69
+ def __init__ (self , weave_up , weave_down ):
70
+ self .weave_up = weave_up
71
+ self .weave_down = weave_down
72
+ self .vertices = []
73
+ self .faces = []
74
+ self .prev_out_verts = None
75
+ self .first_in_verts = None
76
+
77
+ def start_strand (self ):
78
+ self .first_in_verts = None
79
+ self .prev_out_verts = None
80
+
81
+ def add_loop (self , prev_loop , loop , forward ):
82
+ normal = loop .calc_normal () + prev_loop .calc_normal ()
83
+ normal .normalize ()
84
+ offset = (self .weave_up if forward else self .weave_down ) * normal
85
+
86
+ center1 = prev_loop .face .calc_center_median ()
87
+ center2 = loop .face .calc_center_median ()
88
+ v1 = loop .vert .co
89
+ v2 = loop .link_loop_next .vert .co
90
+ i = len (self .vertices )
91
+ self .vertices .append (v1 + offset )
92
+ self .vertices .append (center1 + offset )
93
+ self .vertices .append (v2 + offset )
94
+ self .vertices .append (center2 + offset )
95
+ #self.faces.append([i, i+1, i+2, i+3])
96
+ self .faces .append ([i , i + 1 , i + 2 ])
97
+ self .faces .append ([i , i + 2 , i + 3 ])
98
+ if forward :
99
+ in_verts = [i + 1 , i + 0 ]
100
+ out_verts = [i + 3 , i + 2 ]
101
+ else :
102
+ in_verts = [i + 2 , i + 1 ]
103
+ out_verts = [i + 0 , i + 3 ]
104
+
105
+ if self .first_in_verts is None :
106
+ self .first_in_verts = in_verts
107
+ if self .prev_out_verts is not None :
108
+ self .faces .append (self .prev_out_verts + in_verts )
109
+ self .prev_out_verts = out_verts
110
+
111
+ def end_strand (self ):
112
+ self .faces .append (self .prev_out_verts + self .first_in_verts )
113
+
114
+ def make_mesh (self ):
115
+ me = bpy .data .meshes .new ("" )
116
+ me .from_pydata (self .vertices , [], self .faces )
117
+ me .update (calc_edges = True )
118
+ return me
119
+
120
+
61
121
class BezierBuilder :
62
122
def __init__ (self , bm , crossing_angle , crossing_strength , handle_type , weave_up , weave_down ):
63
123
# Cache some values
@@ -191,7 +251,6 @@ def create_bezier(context, bm, twists,
191
251
192
252
orig_obj = context .active_object
193
253
# Create an object from the curve
194
- from bpy_extras import object_utils
195
254
object_utils .object_data_add (context , curve , operator = None )
196
255
# Set the handle type (this is faster than setting it pointwise)
197
256
bpy .ops .object .editmode_toggle ()
@@ -205,6 +264,16 @@ def create_bezier(context, bm, twists,
205
264
context .scene .objects .active = orig_obj
206
265
return curve_obj
207
266
267
+ def create_ribbon (context , bm , twists , weave_up , weave_down ):
268
+ builder = RibbonBuilder (weave_up , weave_down )
269
+ visit_strands (bm , twists , builder )
270
+ mesh = builder .make_mesh ()
271
+ orig_obj = context .active_object
272
+ object_utils .object_data_add (context , mesh , operator = None )
273
+ mesh_obj = context .active_object
274
+ context .scene .objects .active = orig_obj
275
+ return mesh_obj
276
+
208
277
209
278
def create_pipe_from_bezier (context , curve_obj , thickness ):
210
279
bpy .ops .curve .primitive_bezier_circle_add ()
@@ -237,6 +306,14 @@ class CelticKnotOperator(bpy.types.Operator):
237
306
description = "Distance to shift curve downward under knots" ,
238
307
subtype = "DISTANCE" ,
239
308
unit = "LENGTH" )
309
+ output_types = [(BEZIER , "Bezier" , "Bezier curve" ),
310
+ (PIPE , "Pipe" , "Rounded solid mesh" ),
311
+ (RIBBON , "Ribbon" , "Flat plane mesh" )]
312
+ output_type = bpy .props .EnumProperty (items = output_types ,
313
+ name = "Output Type" ,
314
+ description = "Controls what type of curve/mesh is generated" ,
315
+ default = BEZIER )
316
+
240
317
handle_types = [("ALIGNED" ,"Aligned" ,"Points at a fixed crossing angle" ),
241
318
("AUTO" ,"Auto" ,"Automatic control points" )]
242
319
handle_type = bpy .props .EnumProperty (items = handle_types ,
@@ -260,6 +337,18 @@ class CelticKnotOperator(bpy.types.Operator):
260
337
subtype = "DISTANCE" ,
261
338
unit = "LENGTH" )
262
339
340
+ def draw (self , context ):
341
+ layout = self .layout
342
+ layout .prop (self , "weave_up" )
343
+ layout .prop (self , "weave_down" )
344
+ layout .prop (self , "output_type" )
345
+ if self .output_type in (BEZIER , PIPE ):
346
+ layout .prop (self , "handle_type" )
347
+ layout .prop (self , "crossing_angle" )
348
+ layout .prop (self , "crossing_strength" )
349
+ if self .output_type == PIPE :
350
+ layout .prop (self , "thickness" )
351
+
263
352
@classmethod
264
353
def poll (cls , context ):
265
354
ob = context .active_object
@@ -274,16 +363,19 @@ def execute(self, context):
274
363
bm = bmesh .new ()
275
364
bm .from_mesh (obj .data )
276
365
twists = get_celtic_twists (bm )
277
- curve_obj = create_bezier (context , bm , twists ,
278
- self .crossing_angle ,
279
- self .crossing_strength ,
280
- self .handle_type ,
281
- self .weave_up ,
282
- self .weave_down )
283
-
284
- # If thick, then give it a bevel_object and convert to mesh
285
- if self .thickness > 0 :
286
- create_pipe_from_bezier (context , curve_obj , self .thickness )
366
+ if self .output_type in (BEZIER , PIPE ):
367
+ curve_obj = create_bezier (context , bm , twists ,
368
+ self .crossing_angle ,
369
+ self .crossing_strength ,
370
+ self .handle_type ,
371
+ self .weave_up ,
372
+ self .weave_down )
373
+
374
+ # If thick, then give it a bevel_object and convert to mesh
375
+ if self .output_type == PIPE and self .thickness > 0 :
376
+ create_pipe_from_bezier (context , curve_obj , self .thickness )
377
+ else :
378
+ create_ribbon (context , bm , twists , self .weave_up , self .weave_down )
287
379
return {'FINISHED' }
288
380
289
381
def menu_func (self , context ):
0 commit comments