1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
21 "name": "SpiroFit, BounceSpline and Catenary",
22 "author": "Antonio Osprite, Liero, Atom, Jimmy Hazevoet",
24 "blender": (2, 78, 0),
25 "location": "Toolshelf > Create Tab",
26 "description": "SpiroFit, BounceSpline and Catenary adds "
27 "splines to selected mesh or objects",
34 from bpy
.types
import (
38 from bpy
.props
import (
45 from mathutils
import (
57 # ------------------------------------------------------------
58 # "Build a spiral that fit the active object"
59 # Spirofit, original blender 2.45 script by: Antonio Osprite
60 # http://www.kino3d.com/forum/viewtopic.php?t=5374
61 # ------------------------------------------------------------
63 d
= (Vector(v1
) - Vector(v2
)).length
67 def spiral_point(step
, radius
, z_coord
, spires
, waves
, wave_iscale
, rndm
):
68 x
= radius
* cos(spires
* step
) + (r
.random() - 0.5) * rndm
69 y
= radius
* sin(spires
* step
) + (r
.random() - 0.5) * rndm
70 z
= z_coord
+ (cos(waves
* step
* pi
) * wave_iscale
) + (r
.random() - 0.5) * rndm
74 def spirofit_spline(obj
,
87 bb_xmin
= min([v
[0] for v
in bb
])
88 bb_ymin
= min([v
[1] for v
in bb
])
89 bb_zmin
= min([v
[2] for v
in bb
])
90 bb_xmax
= max([v
[0] for v
in bb
])
91 bb_ymax
= max([v
[1] for v
in bb
])
92 bb_zmax
= max([v
[2] for v
in bb
])
94 radius
= distance([bb_xmax
, bb_ymax
, bb_zmin
], [bb_xmin
, bb_ymin
, bb_zmin
]) / 2.0
95 height
= bb_zmax
- bb_zmin
96 cx
= (bb_xmax
+ bb_xmin
) / 2.0
97 cy
= (bb_ymax
+ bb_ymin
) / 2.0
98 steps
= spires
* spire_resolution
100 for i
in range(steps
+ 1):
101 t
= bb_zmin
+ (2 * pi
/ steps
) * i
102 z
= bb_zmin
+ (float(height
) / steps
) * i
105 cp
= spiral_point(t
, radius
, z
, spires
, waves
, wave_iscale
, rndm_spire
)
107 if map_method
== 'RAYCAST':
108 success
, hit
, nor
, index
= obj
.ray_cast(Vector(cp
), (Vector([cx
, cy
, z
]) - Vector(cp
)))
110 points
.append((hit
+ offset
* nor
))
112 elif map_method
== 'CLOSESTPOINT':
113 success
, hit
, nor
, index
= obj
.closest_point_on_mesh(cp
)
115 points
.append((hit
+ offset
* nor
))
120 class SpiroFitSpline(Operator
):
121 bl_idname
= "object.add_spirofit_spline"
122 bl_label
= "SpiroFit"
123 bl_description
= "Wrap selected mesh in a spiral"
124 bl_options
= {'REGISTER', 'UNDO', 'PRESET'}
126 map_method
= EnumProperty(
129 description
="Mapping method",
130 items
=[('RAYCAST', 'Ray cast', 'Ray casting'),
131 ('CLOSESTPOINT', 'Closest point', 'Closest point on mesh')]
133 direction
= BoolProperty(
135 description
="Spire direction",
138 spire_resolution
= IntProperty(
139 name
="Spire Resolution",
144 description
="Number of steps for one turn"
146 spires
= IntProperty(
152 description
="Number of turns"
154 offset
= FloatProperty(
158 description
="Use normal direction to offset spline"
164 description
="Wave amount"
166 wave_iscale
= FloatProperty(
167 name
="Wave Intensity",
171 description
="Wave intensity scale"
173 rndm_spire
= FloatProperty(
178 description
="Randomise spire"
180 spline_name
= StringProperty(
184 spline_type
= EnumProperty(
187 description
="Spline type",
188 items
=[('POLY', 'Poly', 'Poly spline'),
189 ('BEZIER', 'Bezier', 'Bezier spline')]
191 resolution_u
= IntProperty(
196 description
="Curve resolution u"
198 bevel
= FloatProperty(
203 description
="Bevel depth"
205 bevel_res
= IntProperty(
206 name
="Bevel Resolution",
210 description
="Bevel resolution"
212 extrude
= FloatProperty(
217 description
="Extrude amount"
219 twist_mode
= EnumProperty(
222 description
="Twist method, type of tilt calculation",
223 items
=[('Z_UP', "Z-Up", 'Z Up'),
224 ('MINIMUM', "Minimum", 'Minimum'),
225 ('TANGENT', "Tangent", 'Tangent')]
227 twist_smooth
= FloatProperty(
232 description
="Twist smoothing amount for tangents"
234 tilt
= FloatProperty(
238 description
="Spline handle tilt"
240 random_radius
= FloatProperty(
245 description
="Randomise radius of spline controlpoints"
247 x_ray
= BoolProperty(
250 description
="X-Ray - make the object draw in front of others"
252 random_seed
= IntProperty(
256 description
="Random seed number"
258 origin_to_start
= BoolProperty(
259 name
="Origin at Start",
260 description
="Set origin at first point of spline",
263 refresh
= BoolProperty(
265 description
="Refresh spline",
268 auto_refresh
= BoolProperty(
270 description
="Auto refresh spline",
274 def draw(self
, context
):
276 col
= layout
.column(align
=True)
277 row
= col
.row(align
=True)
279 if self
.auto_refresh
is False:
281 elif self
.auto_refresh
is True:
284 row
.prop(self
, "auto_refresh", toggle
=True, icon
="AUTO", icon_only
=True)
285 row
.prop(self
, "refresh", toggle
=True, icon
="FILE_REFRESH", icon_only
=True)
286 row
.operator("object.add_spirofit_spline", text
="Add")
287 row
.prop(self
, "x_ray", toggle
=True, icon_only
=True, icon
="RESTRICT_VIEW_OFF")
288 row
.prop(self
, "origin_to_start", toggle
=True, icon
="CURVE_DATA", icon_only
=True)
290 col
= layout
.column(align
=True)
291 col
.prop(self
, "spline_name")
293 col
.prop(self
, "map_method")
295 col
.prop(self
, "spire_resolution")
296 row
= col
.row(align
=True).split(0.9, align
=True)
297 row
.prop(self
, "spires")
298 row
.prop(self
, "direction", toggle
=True, text
="", icon
="ARROW_LEFTRIGHT")
299 col
.prop(self
, "offset")
300 col
.prop(self
, "waves")
301 col
.prop(self
, "wave_iscale")
302 col
.prop(self
, "rndm_spire")
303 col
.prop(self
, "random_seed")
304 draw_spline_settings(self
)
307 def poll(self
, context
):
308 ob
= context
.active_object
309 return ((ob
is not None) and
310 (context
.mode
== 'OBJECT'))
312 def invoke(self
, context
, event
):
314 return self
.execute(context
)
316 def execute(self
, context
):
318 return {'PASS_THROUGH'}
320 obj
= context
.active_object
321 if obj
.type != 'MESH':
322 self
.report({'WARNING'},
323 "Active Object is not a Mesh. Operation Cancelled")
326 undo
= context
.user_preferences
.edit
.use_global_undo
327 context
.user_preferences
.edit
.use_global_undo
= False
329 bpy
.ops
.object.select_all(action
='DESELECT')
331 r
.seed(self
.random_seed
)
333 points
= spirofit_spline(
335 self
.spire_resolution
,
361 if self
.origin_to_start
is True:
362 move_origin_to_start()
364 if self
.auto_refresh
is False:
367 context
.user_preferences
.edit
.use_global_undo
= undo
371 # ------------------------------------------------------------
372 # Bounce spline / Fiber mesh
373 # Original script by Liero and Atom
374 # https://blenderartists.org/forum/showthread.php?331750-Fiber-Mesh-Emulation
375 # ------------------------------------------------------------
377 rand
= Vector((r
.gauss(0, 1), r
.gauss(0, 1), r
.gauss(0, 1)))
378 vec
= rand
.normalized() * var
382 def bounce_spline(obj
,
390 dist
, points
= 1000, []
391 poly
= obj
.data
.polygons
397 print("No active face selected")
400 n
= r
.randint(0, len(poly
) - 1)
402 end
= poly
[n
].normal
.copy() * -1
403 start
= poly
[n
].center
404 points
.append(start
+ offset
* end
)
406 for i
in range(number
):
407 for ray
in range(extra
+ 1):
408 end
+= noise(ang_noise
)
410 hit
, nor
, index
= obj
.ray_cast(start
, end
* dist
)[-3:]
414 start
= hit
- nor
/ 10000
415 end
= end
.reflect(nor
).normalized()
416 points
.append(hit
+ offset
* nor
)
423 class BounceSpline(Operator
):
424 bl_idname
= "object.add_bounce_spline"
425 bl_label
= "Bounce Spline"
426 bl_description
= "Fill selected mesh with a spline"
427 bl_options
= {'REGISTER', 'UNDO', 'PRESET'}
429 bounce_number
= IntProperty(
435 description
="Number of bounces"
437 ang_noise
= FloatProperty(
438 name
="Angular Noise",
442 description
="Add some noise to ray direction"
444 offset
= FloatProperty(
448 description
="Use normal direction to offset spline"
455 description
="Number of extra tries if it fails to hit mesh"
457 active_face
= BoolProperty(
460 description
="Starts from active face or a random one"
462 spline_name
= StringProperty(
464 default
="BounceSpline"
466 spline_type
= EnumProperty(
469 description
="Spline type",
470 items
=[('POLY', "Poly", "Poly spline"),
471 ('BEZIER', "Bezier", "Bezier spline")]
473 resolution_u
= IntProperty(
478 description
="Curve resolution u"
480 bevel
= FloatProperty(
485 description
="Bevel depth"
487 bevel_res
= IntProperty(
488 name
="Bevel Resolution",
492 description
="Bevel resolution"
494 extrude
= FloatProperty(
499 description
="Extrude amount"
501 twist_mode
= EnumProperty(
504 description
="Twist method, type of tilt calculation",
505 items
=[('Z_UP', "Z-Up", 'Z Up'),
506 ('MINIMUM', "Minimum", 'Minimum'),
507 ('TANGENT', "Tangent", 'Tangent')]
509 twist_smooth
= FloatProperty(
514 description
="Twist smoothing amount for tangents"
516 tilt
= FloatProperty(
520 description
="Spline handle tilt"
522 random_radius
= FloatProperty(
527 description
="Randomise radius of spline controlpoints"
529 x_ray
= BoolProperty(
532 description
="X-Ray - make the object draw in front of others"
534 random_seed
= IntProperty(
538 description
="Random seed number"
540 origin_to_start
= BoolProperty(
541 name
="Origin at Start",
542 description
="Set origin at first point of spline",
545 refresh
= BoolProperty(
547 description
="Refresh spline",
550 auto_refresh
= BoolProperty(
552 description
="Auto refresh spline",
556 def draw(self
, context
):
558 col
= layout
.column(align
=True)
559 row
= col
.row(align
=True)
560 if self
.auto_refresh
is False:
562 elif self
.auto_refresh
is True:
565 row
.prop(self
, "auto_refresh", toggle
=True, icon
="AUTO", icon_only
=True)
566 row
.prop(self
, "refresh", toggle
=True, icon
="FILE_REFRESH", icon_only
=True)
567 row
.operator("object.add_bounce_spline", text
="Add")
568 row
.prop(self
, "x_ray", toggle
=True, icon_only
=True, icon
="RESTRICT_VIEW_OFF")
569 row
.prop(self
, "origin_to_start", toggle
=True, icon
="CURVE_DATA", icon_only
=True)
571 col
= layout
.column(align
=True)
572 col
.prop(self
, "spline_name")
574 col
.prop(self
, "bounce_number")
575 row
= col
.row(align
=True).split(0.9, align
=True)
576 row
.prop(self
, "ang_noise")
577 row
.prop(self
, "active_face", toggle
=True, text
="", icon
="SNAP_FACE")
578 col
.prop(self
, "offset")
579 col
.prop(self
, "extra")
580 col
.prop(self
, "random_seed")
581 draw_spline_settings(self
)
584 def poll(self
, context
):
585 ob
= context
.active_object
586 return ((ob
is not None) and
587 (context
.mode
== 'OBJECT'))
589 def invoke(self
, context
, event
):
591 return self
.execute(context
)
593 def execute(self
, context
):
595 return {'PASS_THROUGH'}
597 obj
= context
.active_object
598 if obj
.type != 'MESH':
601 undo
= context
.user_preferences
.edit
.use_global_undo
602 context
.user_preferences
.edit
.use_global_undo
= False
604 bpy
.ops
.object.select_all(action
='DESELECT')
606 r
.seed(self
.random_seed
)
608 points
= bounce_spline(
633 if self
.origin_to_start
is True:
634 move_origin_to_start()
636 if self
.auto_refresh
is False:
639 context
.user_preferences
.edit
.use_global_undo
= undo
643 # ------------------------------------------------------------
644 # Hang Catenary curve between two selected objects
645 # ------------------------------------------------------------
654 lx
= end
[0] - start
[0]
655 ly
= end
[1] - start
[1]
656 lr
= sqrt(pow(lx
, 2) + pow(ly
, 2))
657 lv
= lr
/ 2 - (end
[2] - start
[2]) * a
/ lr
658 zv
= start
[2] - pow(lv
, 2) / (2 * a
)
664 x
= start
[0] + i
* slx
665 y
= start
[1] + i
* sly
666 z
= zv
+ pow((i
* slr
) - lv
, 2) / (2 * a
)
667 points
.append([x
, y
, z
])
672 class CatenaryCurve(Operator
):
673 bl_idname
= "object.add_catenary_curve"
674 bl_label
= "Catenary"
675 bl_description
= "Hang a curve between two selected objects"
676 bl_options
= {'REGISTER', 'UNDO', 'PRESET'}
680 description
="Resolution of the curve",
685 var_a
= FloatProperty(
687 description
="Catenary variable a",
693 spline_name
= StringProperty(
697 spline_type
= EnumProperty(
700 description
="Spline type",
701 items
=[('POLY', "Poly", "Poly spline"),
702 ('BEZIER', "Bezier", "Bezier spline")]
704 resolution_u
= IntProperty(
709 description
="Curve resolution u"
711 bevel
= FloatProperty(
716 description
="Bevel depth"
718 bevel_res
= IntProperty(
719 name
="Bevel Resolution",
723 description
="Bevel resolution"
725 extrude
= FloatProperty(
730 description
="Extrude amount"
732 twist_mode
= EnumProperty(
735 description
="Twist method, type of tilt calculation",
736 items
=[('Z_UP', "Z-Up", 'Z Up'),
737 ('MINIMUM', "Minimum", "Minimum"),
738 ('TANGENT', "Tangent", "Tangent")]
740 twist_smooth
= FloatProperty(
745 description
="Twist smoothing amount for tangents"
747 tilt
= FloatProperty(
751 description
="Spline handle tilt"
753 random_radius
= FloatProperty(
758 description
="Randomise radius of spline controlpoints"
760 x_ray
= BoolProperty(
763 description
="X-Ray - make the object draw in front of others"
765 random_seed
= IntProperty(
769 description
="Random seed number"
771 origin_to_start
= BoolProperty(
772 name
="Origin at Start",
773 description
="Set origin at first point of spline",
776 refresh
= BoolProperty(
778 description
="Refresh spline",
781 auto_refresh
= BoolProperty(
783 description
="Auto refresh spline",
787 def draw(self
, context
):
789 col
= layout
.column(align
=True)
790 row
= col
.row(align
=True)
792 if self
.auto_refresh
is False:
794 elif self
.auto_refresh
is True:
797 row
.prop(self
, "auto_refresh", toggle
=True, icon
="AUTO", icon_only
=True)
798 row
.prop(self
, "refresh", toggle
=True, icon
="FILE_REFRESH", icon_only
=True)
799 row
.operator("object.add_catenary_curve", text
="Add")
800 row
.prop(self
, "x_ray", toggle
=True, icon_only
=True, icon
="RESTRICT_VIEW_OFF")
801 row
.prop(self
, "origin_to_start", toggle
=True, icon
="CURVE_DATA", icon_only
=True)
803 col
= layout
.column(align
=True)
804 col
.prop(self
, "spline_name")
806 col
.prop(self
, "steps")
807 col
.prop(self
, "var_a")
809 draw_spline_settings(self
)
810 col
= layout
.column(align
=True)
811 col
.prop(self
, "random_seed")
814 def poll(self
, context
):
815 ob
= context
.active_object
816 return ob
is not None
818 def invoke(self
, context
, event
):
820 return self
.execute(context
)
822 def execute(self
, context
):
824 return {'PASS_THROUGH'}
827 ob1
= bpy
.context
.active_object
829 ob2
= bpy
.context
.selected_objects
[0]
832 if (start
[0] == end
[0]) and (start
[1] == end
[1]):
833 self
.report({"WARNING"},
834 "Objects have the same X, Y location. Operation Cancelled")
838 self
.report({"WARNING"},
839 "Catenary could not be completed. Operation Cancelled")
842 bpy
.ops
.object.select_all(action
='DESELECT')
844 undo
= context
.user_preferences
.edit
.use_global_undo
845 context
.user_preferences
.edit
.use_global_undo
= False
847 r
.seed(self
.random_seed
)
849 points
= catenary_curve(
871 if self
.origin_to_start
is True:
872 move_origin_to_start()
874 bpy
.ops
.object.origin_set(type='ORIGIN_GEOMETRY')
876 if self
.auto_refresh
is False:
879 context
.user_preferences
.edit
.use_global_undo
= undo
883 # ------------------------------------------------------------
884 # Generate curve object from given points
885 # ------------------------------------------------------------
886 def add_curve_object(
889 spline_name
="Spline",
890 spline_type
='BEZIER',
896 twist_mode
='MINIMUM',
902 curve
= bpy
.data
.curves
.new(spline_name
, 'CURVE')
903 curve
.dimensions
= '3D'
904 spline
= curve
.splines
.new(spline_type
)
905 cur
= bpy
.data
.objects
.new(spline_name
, curve
)
907 spline
.radius_interpolation
= 'BSPLINE'
908 spline
.tilt_interpolation
= 'BSPLINE'
910 if spline_type
== 'BEZIER':
911 spline
.bezier_points
.add(int(len(verts
) - 1))
912 for i
in range(len(verts
)):
913 spline
.bezier_points
[i
].co
= verts
[i
]
914 spline
.bezier_points
[i
].handle_right_type
= 'AUTO'
915 spline
.bezier_points
[i
].handle_left_type
= 'AUTO'
916 spline
.bezier_points
[i
].radius
+= spline_radius
* r
.random()
917 spline
.bezier_points
[i
].tilt
= radians(tilt
)
919 spline
.points
.add(int(len(verts
) - 1))
920 for i
in range(len(verts
)):
921 spline
.points
[i
].co
= verts
[i
][0], verts
[i
][1], verts
[i
][2], 1
923 bpy
.context
.scene
.objects
.link(cur
)
924 cur
.data
.use_uv_as_generated
= True
925 cur
.data
.resolution_u
= resolution_u
926 cur
.data
.fill_mode
= 'FULL'
927 cur
.data
.bevel_depth
= bevel
928 cur
.data
.bevel_resolution
= bevel_resolution
929 cur
.data
.extrude
= extrude
930 cur
.data
.twist_mode
= twist_mode
931 cur
.data
.twist_smooth
= twist_smooth
932 cur
.matrix_world
= matrix
933 bpy
.context
.scene
.objects
.active
= cur
936 cur
.show_x_ray
= x_ray
940 def move_origin_to_start():
941 active
= bpy
.context
.active_object
942 spline
= active
.data
.splines
[0]
943 if spline
.type == 'BEZIER':
944 start
= active
.matrix_world
* spline
.bezier_points
[0].co
946 start
= active
.matrix_world
* spline
.points
[0].co
948 cursor
= bpy
.context
.scene
.cursor_location
.copy()
949 bpy
.context
.scene
.cursor_location
= start
950 bpy
.ops
.object.origin_set(type='ORIGIN_CURSOR')
951 bpy
.context
.scene
.cursor_location
= cursor
954 def draw_spline_settings(self
):
956 col
= layout
.column(align
=True)
958 col
.prop(self
, "spline_type")
960 col
.prop(self
, "resolution_u")
961 col
.prop(self
, "bevel")
962 col
.prop(self
, "bevel_res")
963 col
.prop(self
, "extrude")
965 if self
.spline_type
== 'BEZIER':
966 col
.prop(self
, "random_radius")
968 col
.prop(self
, "twist_mode")
971 if self
.twist_mode
== 'TANGENT':
972 col
.prop(self
, "twist_smooth")
974 if self
.spline_type
== 'BEZIER':
975 col
.prop(self
, "tilt")
978 # ------------------------------------------------------------
979 # Tools Panel > Create
980 # ------------------------------------------------------------
981 class SplinePanel(Panel
):
982 bl_idname
= "VIEW3D_PT_spirofit_spline"
983 bl_space_type
= "VIEW_3D"
984 bl_context
= "objectmode"
985 bl_region_type
= "TOOLS"
987 bl_category
= "Create"
988 bl_options
= {'DEFAULT_CLOSED'}
990 def draw(self
, context
):
991 col
= self
.layout
.column(align
=True)
992 col
.operator(SpiroFitSpline
.bl_idname
, icon
="FORCE_MAGNETIC")
993 col
.operator(BounceSpline
.bl_idname
, icon
="FORCE_HARMONIC")
994 col
.operator(CatenaryCurve
.bl_idname
, icon
="FORCE_CURVE")
997 # ------------------------------------------------------------
999 # ------------------------------------------------------------
1001 bpy
.utils
.register_class(SplinePanel
)
1002 bpy
.utils
.register_class(SpiroFitSpline
)
1003 bpy
.utils
.register_class(BounceSpline
)
1004 bpy
.utils
.register_class(CatenaryCurve
)
1008 bpy
.utils
.unregister_class(SplinePanel
)
1009 bpy
.utils
.unregister_class(SpiroFitSpline
)
1010 bpy
.utils
.unregister_class(BounceSpline
)
1011 bpy
.utils
.unregister_class(CatenaryCurve
)
1014 if __name__
== "__main__":