38fbb92af4901c67ee2adbd0303ecb254c4837cc
[blender-addons-contrib.git] / add_advanced_objects_menu / arrange_on_curve.py
1 # gpl author: Mano-Wii
2
3 bl_info = {
4     "name": "Arrange on Curve",
5     "author": "Mano-Wii",
6     "version": (6, 3, 0),
7     "blender": (2, 77, 0),
8     "location": "3D View > Toolshelf > Create > Arrange on Curve",
9     "description": "Arrange objects along a curve",
10     "warning": "Select curve",
11     "wiki_url": "",
12     "category": "3D View"
13     }
14
15 # Note: scene properties are moved into __init__
16 # search for patterns advanced_objects and adv_obj
17
18 import bpy
19 import mathutils
20 from bpy.types import (
21         Operator,
22         Panel,
23         )
24 from bpy.props import (
25         EnumProperty,
26         FloatProperty,
27         IntProperty,
28         )
29
30 FLT_MIN = 0.004
31
32
33 class PanelDupliCurve(Panel):
34     bl_idname = "VIEW3D_PT_arranjar_numa_curva"
35     bl_space_type = "VIEW_3D"
36     bl_region_type = "TOOLS"
37     bl_context = "objectmode"
38     bl_category = "Create"
39     bl_label = "Arrange on Curve"
40     bl_options = {'DEFAULT_CLOSED'}
41
42     @classmethod
43     def poll(cls, context):
44         return context.object and context.mode == 'OBJECT' and context.object.type == 'CURVE'
45
46     def draw(self, context):
47         layout = self.layout
48         adv_obj = context.scene.advanced_objects
49
50         layout.prop(adv_obj, "arrange_c_use_selected")
51
52         if not adv_obj.arrange_c_use_selected:
53             layout.prop(adv_obj, "arrange_c_select_type", expand=True)
54             if adv_obj.arrange_c_select_type == 'O':
55                 layout.column(align=True).prop_search(
56                               adv_obj, "arrange_c_obj_arranjar",
57                               bpy.data, "objects"
58                               )
59             elif adv_obj.arrange_c_select_type == 'G':
60                 layout.column(align=True).prop_search(
61                               adv_obj, "arrange_c_obj_arranjar",
62                               bpy.data, "collections"
63                               )
64         if context.object.type == 'CURVE':
65             layout.operator("object.arranjar_numa_curva", text="Arrange Objects")
66
67
68 class DupliCurve(Operator):
69     bl_idname = "object.arranjar_numa_curva"
70     bl_label = "Arrange Objects along a Curve"
71     bl_description = "Arange chosen / selected objects along the Active Curve"
72     bl_options = {'REGISTER', 'UNDO'}
73
74     use_distance: EnumProperty(
75             name="Arrangement",
76             items=[
77                 ("D", "Distance", "Objects are arranged depending on the distance", 0),
78                 ("Q", "Quantity", "Objects are arranged depending on the quantity", 1),
79                 ("R", "Range", "Objects are arranged uniformly between the corners", 2)
80                 ]
81             )
82     distance: FloatProperty(
83             name="Distance",
84             description="Distance between Objects",
85             default=1.0,
86             min=FLT_MIN,
87             soft_min=0.1,
88             unit='LENGTH',
89             )
90     object_qt: IntProperty(
91             name="Quantity",
92             description="Object amount",
93             default=2,
94             min=0,
95             )
96     scale: FloatProperty(
97             name="Scale",
98             description="Object Scale",
99             default=1.0,
100             min=FLT_MIN,
101             unit='LENGTH',
102                 )
103     Yaw: FloatProperty(
104             name="X",
105             description="Rotate around the X axis (Yaw)",
106             default=0.0,
107             unit='ROTATION'
108             )
109     Pitch: FloatProperty(
110             default=0.0,
111             description="Rotate around the Y axis (Pitch)",
112             name="Y",
113             unit='ROTATION'
114             )
115     Roll: FloatProperty(
116             default=0.0,
117             description="Rotate around the Z axis (Roll)",
118             name="Z",
119             unit='ROTATION'
120             )
121     max_angle: FloatProperty(
122             default=1.57079,
123             max=3.141592,
124             name="Angle",
125             unit='ROTATION'
126             )
127     offset: FloatProperty(
128             default=0.0,
129             name="Offset",
130             unit='LENGTH'
131             )
132
133     @classmethod
134     def poll(cls, context):
135         return context.mode == 'OBJECT'
136
137     def draw(self, context):
138         layout = self.layout
139         col = layout.column()
140         col.prop(self, "use_distance", text="")
141         col = layout.column(align=True)
142         if self.use_distance == "D":
143             col.prop(self, "distance")
144         elif self.use_distance == "Q":
145             col.prop(self, "object_qt")
146         else:
147             col.prop(self, "distance")
148             col.prop(self, "max_angle")
149             col.prop(self, "offset")
150
151         col = layout.column(align=True)
152         col.prop(self, "scale")
153         col.prop(self, "Yaw")
154         col.prop(self, "Pitch")
155         col.prop(self, "Roll")
156
157     def Glpoints(self, curve):
158         Gpoints = []
159         for i, spline in enumerate(curve.data.splines):
160             segments = len(spline.bezier_points)
161             if segments >= 2:
162                 r = spline.resolution_u + 1
163
164                 points = []
165                 for j in range(segments):
166                     bp1 = spline.bezier_points[j]
167                     inext = (j + 1)
168                     if inext == segments:
169                         if not spline.use_cyclic_u:
170                             break
171                         inext = 0
172                     bp2 = spline.bezier_points[inext]
173                     if bp1.handle_right_type == bp2.handle_left_type == 'VECTOR':
174                         _points = (bp1.co, bp2.co) if j == 0 else (bp2.co,)
175                     else:
176                         knot1 = bp1.co
177                         handle1 = bp1.handle_right
178                         handle2 = bp2.handle_left
179                         knot2 = bp2.co
180                         _points = mathutils.geometry.interpolate_bezier(knot1, handle1, handle2, knot2, r)
181                     points.extend(_points)
182                 Gpoints.append(tuple((curve.matrix_world * p for p in points)))
183             elif len(spline.points) >= 2:
184                 l = [curve.matrix_world * p.co.xyz for p in spline.points]
185                 if spline.use_cyclic_u:
186                     l.append(l[0])
187                 Gpoints.append(tuple(l))
188
189             if self.use_distance == "R":
190                 max_angle = self.max_angle
191                 tmp_Gpoints = []
192                 sp = Gpoints[i]
193                 sp2 = [sp[0], sp[1]]
194                 lp = sp[1]
195                 v1 = lp - sp[0]
196                 for p in sp[2:]:
197                     v2 = p - lp
198                     try:
199                         if (3.14158 - v1.angle(v2)) < max_angle:
200                             tmp_Gpoints.append(tuple(sp2))
201                             sp2 = [lp]
202                     except Exception as e:
203                         print("\n[Add Advanced  Objects]\nOperator: "
204                               "object.arranjar_numa_curva\nError: {}".format(e))
205                         pass
206                     sp2.append(p)
207                     v1 = v2
208                     lp = p
209                 tmp_Gpoints.append(tuple(sp2))
210                 Gpoints = Gpoints[:i] + tmp_Gpoints
211
212         lengths = []
213         if self.use_distance != "D":
214             for sp in Gpoints:
215                 lp = sp[1]
216                 leng = (lp - sp[0]).length
217                 for p in sp[2:]:
218                     leng += (p - lp).length
219                     lp = p
220                 lengths.append(leng)
221         return Gpoints, lengths
222
223     def execute(self, context):
224         if context.object.type != 'CURVE':
225             return {'CANCELLED'}
226
227         curve = context.active_object
228         Gpoints, lengs = self.Glpoints(curve)
229         adv_obj = context.scene.advanced_objects
230
231         if adv_obj.arrange_c_use_selected:
232             G_Objeto = context.selected_objects
233             G_Objeto.remove(curve)
234
235             if not G_Objeto:
236                 return {'CANCELLED'}
237
238         elif adv_obj.arrange_c_select_type == 'O':
239             G_Objeto = bpy.data.objects[adv_obj.arrange_c_obj_arranjar],
240         elif adv_obj.arrange_c_select_type == 'G':
241             G_Objeto = bpy.data.collections[adv_obj.arrange_c_obj_arranjar].objects
242
243         yawMatrix = mathutils.Matrix.Rotation(self.Yaw, 4, 'X')
244         pitchMatrix = mathutils.Matrix.Rotation(self.Pitch, 4, 'Y')
245         rollMatrix = mathutils.Matrix.Rotation(self.Roll, 4, 'Z')
246
247         max_angle = self.max_angle  # max_angle is called in Glpoints
248
249         if self.use_distance == "D":
250             dist = self.distance
251             for sp_points in Gpoints:
252                 dx = 0.0  # Length of initial calculation of section
253                 last_point = sp_points[0]
254                 j = 0
255                 for point in sp_points[1:]:
256                     vetorx = point - last_point  # Vector spline section
257                     quat = mathutils.Vector.to_track_quat(vetorx, 'X', 'Z')  # Tracking the selected objects
258                     quat = quat.to_matrix().to_4x4()
259
260                     v_len = vetorx.length
261                     if v_len > 0.0:
262                         dx += v_len  # Defined length calculation equal total length of the spline section
263                         v_norm = vetorx / v_len
264                         while dx > dist:
265                             object = G_Objeto[j % len(G_Objeto)]
266                             j += 1
267                             dx -= dist  # Calculating the remaining length of the section
268                             obj = object.copy()
269                             context.collection.objects.link(obj)
270                             obj.matrix_world = quat * yawMatrix * pitchMatrix * rollMatrix
271                             # Placing in the correct position
272                             obj.matrix_world.translation = point - v_norm * dx
273                             obj.scale *= self.scale
274                         last_point = point
275
276         elif self.use_distance == "Q":
277             object_qt = self.object_qt + 1
278             for i, sp_points in enumerate(Gpoints):
279                 dx = 0.0  # Length of initial calculation of section
280                 dist = lengs[i] / object_qt
281                 last_point = sp_points[0]
282                 j = 0
283                 for point in sp_points[1:]:
284                     vetorx = point - last_point  # Vector spline section
285                     # Tracking the selected objects
286                     quat = mathutils.Vector.to_track_quat(vetorx, 'X', 'Z')
287                     quat = quat.to_matrix().to_4x4()
288
289                     v_len = vetorx.length
290                     if v_len > 0.0:
291                         # Defined length calculation equal total length of the spline section
292                         dx += v_len
293                         v_norm = vetorx / v_len
294                         while dx > dist:
295                             object = G_Objeto[j % len(G_Objeto)]
296                             j += 1
297                             dx -= dist  # Calculating the remaining length of the section
298                             obj = object.copy()
299                             context.collection.objects.link(obj)
300                             obj.matrix_world = quat * yawMatrix * pitchMatrix * rollMatrix
301                             # Placing in the correct position
302                             obj.matrix_world.translation = point - v_norm * dx
303                             obj.scale *= self.scale
304                         last_point = point
305
306         else:
307             dist = self.distance
308             offset2 = 2 * self.offset
309             for i, sp_points in enumerate(Gpoints):
310                 leng = lengs[i] - offset2
311                 rest = leng % dist
312                 offset = offset2 + rest
313                 leng -= rest
314                 offset /= 2
315                 last_point = sp_points[0]
316
317                 dx = dist - offset  # Length of initial calculation of section
318                 j = 0
319                 for point in sp_points[1:]:
320                     vetorx = point - last_point  # Vector spline section
321                     # Tracking the selected objects
322                     quat = mathutils.Vector.to_track_quat(vetorx, 'X', 'Z')
323                     quat = quat.to_matrix().to_4x4()
324
325                     v_len = vetorx.length
326                     if v_len > 0.0:
327                         dx += v_len
328                         v_norm = vetorx / v_len
329                         while dx >= dist and leng >= 0.0:
330                             leng -= dist
331                             dx -= dist  # Calculating the remaining length of the section
332                             object = G_Objeto[j % len(G_Objeto)]
333                             j += 1
334                             obj = object.copy()
335                             context.collection.objects.link(obj)
336                             obj.matrix_world = quat * yawMatrix * pitchMatrix * rollMatrix
337                             # Placing in the correct position
338                             obj.matrix_world.translation = point - v_norm * dx
339                             obj.scale *= self.scale
340                         last_point = point
341
342         return {"FINISHED"}
343
344
345 def register():
346     bpy.utils.register_class(PanelDupliCurve)
347     bpy.utils.register_class(DupliCurve)
348
349
350 def unregister():
351     bpy.utils.unregister_class(PanelDupliCurve)
352     bpy.utils.unregister_class(DupliCurve)
353
354
355 if __name__ == "__main__":
356     register()