a1e4f70ce92e6353060545d1e97b12bb9234ae7d
[blender-addons-contrib.git] / add_advanced_objects_menu / mesh_easylattice.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
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.
7 #
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.
12 #
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.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 bl_info = {
20     "name": "Easy Lattice Object",
21     "author": "Kursad Karatas",
22     "version": (0, 6, 0),
23     "blender": (2, 66, 0),
24     "location": "View3D > Easy Lattice",
25     "description": "Create a lattice for shape editing",
26     "warning": "",
27     "wiki_url": "https://wiki.blender.org/index.php/Easy_Lattice_Editing_Addon",
28     "tracker_url": "https://bitbucket.org/kursad/blender_addons_easylattice/src",
29     "category": "Mesh",
30 }
31
32
33 import bpy
34 from mathutils import (
35     Matrix,
36     Vector,
37 )
38 from bpy.types import Operator
39 from bpy.props import (
40     EnumProperty,
41     FloatProperty,
42     IntProperty,
43 )
44
45
46 def createLattice(context, obj, props):
47     # Create lattice and object
48     lat = bpy.data.lattices.new('EasyLattice')
49     ob = bpy.data.objects.new('EasyLattice', lat)
50
51     # Take into consideration any selected vertices (default: all vertices)
52     selectedVertices = createVertexGroup(obj)
53
54     size, pos = findBBox(obj, selectedVertices)
55     loc, rot = getTransformations(obj)
56
57     # the position comes from the bbox
58     ob.location = pos
59
60     # the size from bbox * the incoming scale factor
61     ob.scale = size * props[3]
62
63     # the rotation comes from the combined obj world
64     # matrix which was converted to euler pairs
65     ob.rotation_euler = buildRot_World(obj)
66     ob.show_in_front = True
67
68     # Link object to scene
69     scn = context.scene
70
71     # Take care of the local view
72     base = scn.objects.link(ob)
73     scn.objects.active = ob
74
75     v3d = None
76     if context.space_data and context.space_data.type == 'VIEW_3D':
77         v3d = context.space_data
78
79     if v3d and v3d.local_view:
80         base.layers_from_view(v3d)
81
82     scn.update()
83
84     # Set lattice attributes
85     lat.points_u = props[0]
86     lat.points_v = props[1]
87     lat.points_w = props[2]
88
89     lat.interpolation_type_u = props[4]
90     lat.interpolation_type_v = props[4]
91     lat.interpolation_type_w = props[4]
92
93     lat.use_outside = False
94
95     return ob
96
97
98 def createVertexGroup(obj):
99     vertices = obj.data.vertices
100     selverts = []
101
102     if obj.mode == "EDIT":
103         bpy.ops.object.editmode_toggle()
104
105     group = obj.vertex_groups.new(name="easy_lattice_group")
106
107     for vert in vertices:
108         if vert.select is True:
109             selverts.append(vert)
110             group.add([vert.index], 1.0, "REPLACE")
111
112     # Default: use all vertices
113     if not selverts:
114         for vert in vertices:
115             selverts.append(vert)
116             group.add([vert.index], 1.0, "REPLACE")
117
118     return selverts
119
120
121 def getTransformations(obj):
122     rot = obj.rotation_euler
123     loc = obj.location
124
125     return [loc, rot]
126
127
128 def findBBox(obj, selvertsarray):
129
130     mat = buildTrnScl_WorldMat(obj)
131     mat_world = obj.matrix_world
132
133     minx, miny, minz = selvertsarray[0].co
134     maxx, maxy, maxz = selvertsarray[0].co
135
136     c = 1
137
138     for c in range(len(selvertsarray)):
139         co = selvertsarray[c].co
140
141         if co.x < minx:
142             minx = co.x
143         if co.y < miny:
144             miny = co.y
145         if co.z < minz:
146             minz = co.z
147
148         if co.x > maxx:
149             maxx = co.x
150         if co.y > maxy:
151             maxy = co.y
152         if co.z > maxz:
153             maxz = co.z
154         c += 1
155
156     minpoint = Vector((minx, miny, minz))
157     maxpoint = Vector((maxx, maxy, maxz))
158
159     # The middle position has to be calculated based on the real world matrix
160     pos = ((minpoint + maxpoint) / 2)
161
162     minpoint = mat * minpoint    # Calculate only based on loc/scale
163     maxpoint = mat * maxpoint    # Calculate only based on loc/scale
164     pos = mat_world * pos        # the middle position has to be calculated based on the real world matrix
165
166     size = maxpoint - minpoint
167     size = Vector((max(0.1, abs(size.x)), max(0.1, abs(size.y)), max(0.1, abs(size.z)))) # Prevent zero size dimensions
168
169     return [size, pos]
170
171
172 def buildTrnSclMat(obj):
173     # This function builds a local matrix that encodes translation
174     # and scale and it leaves out the rotation matrix
175     # The rotation is applied at object level if there is any
176     mat_trans = Matrix.Translation(obj.location)
177     mat_scale = Matrix.Scale(obj.scale[0], 4, (1, 0, 0))
178     mat_scale *= Matrix.Scale(obj.scale[1], 4, (0, 1, 0))
179     mat_scale *= Matrix.Scale(obj.scale[2], 4, (0, 0, 1))
180
181     mat_final = mat_trans * mat_scale
182
183     return mat_final
184
185
186 def buildTrnScl_WorldMat(obj):
187     # This function builds a real world matrix that encodes translation
188     # and scale and it leaves out the rotation matrix
189     # The rotation is applied at object level if there is any
190     loc, rot, scl = obj.matrix_world.decompose()
191     mat_trans = Matrix.Translation(loc)
192
193     mat_scale = Matrix.Scale(scl[0], 4, (1, 0, 0))
194     mat_scale *= Matrix.Scale(scl[1], 4, (0, 1, 0))
195     mat_scale *= Matrix.Scale(scl[2], 4, (0, 0, 1))
196
197     mat_final = mat_trans * mat_scale
198
199     return mat_final
200
201
202 # Feature use
203 def buildRot_WorldMat(obj):
204     # This function builds a real world matrix that encodes rotation
205     # and it leaves out translation and scale matrices
206     loc, rot, scl = obj.matrix_world.decompose()
207     rot = rot.to_euler()
208
209     mat_rot = Matrix.Rotation(rot[0], 4, 'X')
210     mat_rot *= Matrix.Rotation(rot[1], 4, 'Z')
211     mat_rot *= Matrix.Rotation(rot[2], 4, 'Y')
212     return mat_rot
213
214
215 def buildTrn_WorldMat(obj):
216     # This function builds a real world matrix that encodes translation
217     # and scale and it leaves out the rotation matrix
218     # The rotation is applied at object level if there is any
219     loc, rot, scl = obj.matrix_world.decompose()
220     mat_trans = Matrix.Translation(loc)
221
222     return mat_trans
223
224
225 def buildScl_WorldMat(obj):
226     # This function builds a real world matrix that encodes translation
227     # and scale and it leaves out the rotation matrix
228     # The rotation is applied at object level if there is any
229     loc, rot, scl = obj.matrix_world.decompose()
230
231     mat_scale = Matrix.Scale(scl[0], 4, (1, 0, 0))
232     mat_scale *= Matrix.Scale(scl[1], 4, (0, 1, 0))
233     mat_scale *= Matrix.Scale(scl[2], 4, (0, 0, 1))
234
235     return mat_scale
236
237
238 def buildRot_World(obj):
239     # This function builds a real world rotation values
240     loc, rot, scl = obj.matrix_world.decompose()
241     rot = rot.to_euler()
242
243     return rot
244
245
246 def main(context, lat_props):
247     obj = context.object
248
249     if obj.type == "MESH":
250         lat = createLattice(context, obj, lat_props)
251
252         modif = obj.modifiers.new("EasyLattice", "LATTICE")
253         modif.object = lat
254         modif.vertex_group = "easy_lattice_group"
255
256         bpy.ops.object.select_all(action='DESELECT')
257         bpy.ops.object.select_pattern(pattern=lat.name, extend=False)
258         context.view_layer.objects.active = lat
259
260         context.view_layer.update()
261
262     return
263
264
265 class EasyLattice(Operator):
266     bl_idname = "object.easy_lattice"
267     bl_label = "Easy Lattice Creator"
268     bl_description = ("Create a Lattice modifier ready to edit\n"
269                       "Needs an existing Active Mesh Object\n")
270
271     lat_u: IntProperty(
272             name="Lattice u",
273             description="Points in u direction",
274             default=3
275             )
276     lat_v: IntProperty(
277             name="Lattice v",
278             description="Points in v direction",
279             default=3
280             )
281     lat_w: IntProperty(
282             name="Lattice w",
283             description="Points in w direction",
284             default=3
285             )
286     lat_scale_factor: FloatProperty(
287             name="Lattice scale factor",
288             description="Adjustment to the lattice scale",
289             default=1,
290             min=0.1,
291             step=1,
292             precision=2
293             )
294     lat_types = (('KEY_LINEAR', "Linear", "Linear Interpolation type"),
295                  ('KEY_CARDINAL', "Cardinal", "Cardinal Interpolation type"),
296                  ('KEY_CATMULL_ROM', "Catmull-Rom", "Catmull-Rom Interpolation type"),
297                  ('KEY_BSPLINE', "BSpline", "Key BSpline Interpolation Type")
298                 )
299     lat_type: EnumProperty(
300             name="Lattice Type",
301             description="Choose Lattice Type",
302             items=lat_types,
303             default='KEY_BSPLINE'
304             )
305
306     @classmethod
307     def poll(cls, context):
308         obj = context.active_object
309         return obj is not None and obj.type == "MESH"
310
311     def draw(self, context):
312         layout = self.layout
313
314         col = layout.column(align=True)
315         col.prop(self, "lat_u")
316         col.prop(self, "lat_v")
317         col.prop(self, "lat_w")
318
319         layout.prop(self, "lat_scale_factor")
320
321         layout.prop(self, "lat_type")
322
323     def execute(self, context):
324         lat_u = self.lat_u
325         lat_v = self.lat_v
326         lat_w = self.lat_w
327
328         lat_scale_factor = self.lat_scale_factor
329
330         # enum property no need to complicate things
331         lat_type = self.lat_type
332         # XXX, should use keyword args
333         lat_props = [lat_u, lat_v, lat_w, lat_scale_factor, lat_type]
334         try:
335             main(context, lat_props)
336
337         except Exception as ex:
338             print("\n[Add Advanced Objects]\nOperator:object.easy_lattice\n{}\n".format(ex))
339             self.report(
340                 {'WARNING'},
341                 "Easy Lattice Creator could not be completed (See Console for more info)"
342             )
343             return {"CANCELLED"}
344
345         return {"FINISHED"}
346
347     def invoke(self, context, event):
348         wm = context.window_manager
349         return wm.invoke_props_dialog(self)
350
351
352 def register():
353     bpy.utils.register_class(EasyLattice)
354
355
356 def unregister():
357     bpy.utils.unregister_class(EasyLattice)
358
359
360 if __name__ == "__main__":
361     register()