ef2a29a08557fb640cf66b58bda31f38ca584d45
[blender-addons-contrib.git] / add_mesh_clusters / __init__.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 #
20 #  Main author       : Clemens Barth (Blendphys@root-1.de)
21 #  Authors           : Clemens Barth, Christine Mottet (Icosahedra), ...
22 #
23 #  Homepage(Wiki)    : http://development.root-1.de/Atomic_Blender.php
24 #
25 #  Start of project              : 2012-03-25 by Clemens Barth
26 #  First publication in Blender  : 2012-05-27 by Clemens Barth
27 #  Last modified                 : 2014-08-19
28 #
29 #
30 #
31 #  To do:
32 #  ======
33 #
34 #  1. Include other shapes: dodecahedron, ...
35 #  2. Skin option for parabolic shaped clusters
36 #  3. Skin option for icosahedron
37 #  4. Icosahedron: unlimited size ...
38 #
39
40 bl_info = {
41     "name": "Atomic Blender - Cluster",
42     "description": "Creating cluster formed by atoms",
43     "author": "Clemens Barth",
44     "version": (0, 5),
45     "blender": (2, 71, 0),
46     "location": "Panel: View 3D - Tools (left side)",
47     "warning": "",
48     "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
49         "Scripts/Add_Mesh/Cluster",
50     "tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/",
51     "category": "Add Mesh"}
52
53
54 import os
55 import io
56 import bpy
57 from bpy.types import Operator, Panel
58 from bpy_extras.io_utils import ImportHelper, ExportHelper
59 from bpy.props import (StringProperty,
60                        BoolProperty,
61                        EnumProperty,
62                        IntProperty,
63                        FloatProperty)
64
65 from . import add_mesh_cluster
66
67 ATOM_Cluster_PANEL = 0
68
69 # -----------------------------------------------------------------------------
70 #                                                                           GUI
71
72
73 class CLASS_ImportCluster(bpy.types.Operator):
74     bl_idname = "mesh.cluster"
75     bl_label = "Atom cluster"
76     bl_options = {'REGISTER', 'UNDO', 'PRESET'}
77
78     def execute(self, context):
79
80         global ATOM_Cluster_PANEL
81         ATOM_Cluster_PANEL = 1
82
83         return {'FINISHED'}
84
85
86
87 class CLASS_atom_cluster_panel(Panel):
88     bl_label       = "Atomic Blender - Cluster"
89     bl_space_type  = "VIEW_3D"
90     bl_region_type = "TOOL_PROPS"
91
92
93     @classmethod
94     def poll(self, context):
95         global ATOM_Cluster_PANEL
96
97         if ATOM_Cluster_PANEL == 0:
98             return False
99
100         return True
101
102     def draw(self, context):
103         layout = self.layout
104
105         scn = context.scene.atom_cluster
106
107         row = layout.row()
108         row.label(text="Cluster properties")
109         box = layout.box()
110         row = box.row()
111         row.prop(scn, "shape")
112
113         if scn.shape in ["parabolid_square","parabolid_ab","parabolid_abc"]:
114             row = box.row()
115             row.prop(scn, "parabol_diameter")
116             row = box.row()
117             row.prop(scn, "parabol_height")
118         elif scn.shape in ["icosahedron"]:
119             row = box.row()
120             row.prop(scn, "icosahedron_size")
121         else:
122             row = box.row()
123             row.prop(scn, "size")
124             row = box.row()
125             row.prop(scn, "skin")
126
127         row = box.row()
128         row.prop(scn, "lattice_parameter")
129         row = box.row()
130         row.prop(scn, "element")
131         row = box.row()
132         row.prop(scn, "radius_type")
133         row = box.row()
134         row.prop(scn, "scale_radius")
135         row = box.row()
136         row.prop(scn, "scale_distances")
137
138         row = layout.row()
139         row.label(text="Load cluster")
140         box = layout.box()
141         row = box.row()
142         row.operator("atom_cluster.load")
143         row = box.row()
144         row.label(text="Number of atoms")
145         row = box.row()
146         row.prop(scn, "atom_number_total")
147         row = box.row()
148         row.prop(scn, "atom_number_drawn")
149
150         row = layout.row()
151         row.label(text="Modify cluster")
152         box = layout.box()
153         row = box.row()
154         row.label(text="All changes concern:")
155         row = box.row()
156         row.prop(scn, "radius_how")
157         row = box.row()
158         row.label(text="1. Change type of radii")
159         row = box.row()
160         row.prop(scn, "radius_type")
161         row = box.row()
162         row.label(text="2. Change atom radii by scale")
163         row = box.row()
164         col = row.column()
165         col.prop(scn, "radius_all")
166         col = row.column(align=True)
167         col.operator( "atom_cluster.radius_all_bigger" )
168         col.operator( "atom_cluster.radius_all_smaller" )
169
170
171 # The properties (gadgets) in the panel. They all go to scene
172 # during initialization (see end)
173 class CLASS_atom_cluster_Properties(bpy.types.PropertyGroup):
174
175     def Callback_radius_type(self, context):
176         scn = bpy.context.scene.atom_cluster
177         DEF_atom_cluster_radius_type(scn.radius_type,
178                                      scn.radius_how,)
179
180     size: FloatProperty(
181         name = "Size", default=30.0, min=0.1,
182         description = "Size of cluster in Angstroem")
183     skin: FloatProperty(
184         name = "Skin", default=1.0, min=0.0, max = 1.0,
185         description = "Skin of cluster in % of size (skin=1.0: show all atoms, skin=0.1: show only the outer atoms)")
186     parabol_diameter: FloatProperty(
187         name = "Diameter", default=30.0, min=0.1,
188         description = "Top diameter in Angstroem")
189     parabol_height: FloatProperty(
190         name = "Height", default=30.0, min=0.1,
191         description = "Height in Angstroem")
192     icosahedron_size: IntProperty(
193         name = "Size", default=1, min=1, max=13,
194         description = "Size n: 1 (13 atoms), 2 (55 atoms), 3 (147 atoms), 4 (309 atoms), 5 (561 atoms), ..., 13 (8217 atoms)")
195     shape: EnumProperty(
196         name="",
197         description="Choose the shape of the cluster",
198         items=(('sphere_square',  "Sphere - square",   "Sphere with square lattice"),
199                ('sphere_hex_ab',  "Sphere - hex ab",  "Sphere with hexagonal ab-lattice"),
200                ('sphere_hex_abc', "Sphere - hex abc", "Sphere with hexagonal abc-lattice"),
201                ('pyramide_square',  "Pyramide - Square",    "Pyramide: square (abc-lattice)"),
202                ('pyramide_hex_abc', "Pyramide - Tetraeder", "Pyramide: tetraeder (abcabc-lattice)"),
203                ('octahedron',           "Octahedron",           "Octahedron"),
204                ('truncated_octahedron', "Truncated octahedron", "Truncated octahedron"),
205                ('icosahedron', "Icosahedron", "Icosahedron"),
206                ('parabolid_square', "Paraboloid: square",  "Paraboloid with square lattice"),
207                ('parabolid_ab',     "Paraboloid: hex ab",  "Paraboloid with ab-lattice"),
208                ('parabolid_abc',    "Paraboloid: hex abc", "Paraboloid with abc-lattice")),
209                default='sphere_square',)
210     lattice_parameter: FloatProperty(
211         name = "Lattice", default=4.08, min=1.0,
212         description = "Lattice parameter in Angstroem")
213     element: StringProperty(name="Element",
214         default="Gold", description = "Enter the name of the element")
215     radius_type: EnumProperty(
216         name="Radius",
217         description="Which type of atom radii?",
218         items=(('0',"predefined", "Use pre-defined radii"),
219                ('1',"atomic", "Use atomic radii"),
220                ('2',"van der Waals","Use van der Waals radii")),
221                default='0',)
222     scale_radius: FloatProperty(
223         name = "Scale R", default=1.0, min=0.0,
224         description = "Scale radius of atoms")
225     scale_distances: FloatProperty(
226         name = "Scale d", default=1.0, min=0.0,
227         description = "Scale distances")
228
229     atom_number_total: StringProperty(name="Total",
230         default="---", description = "Number of all atoms in the cluster")
231     atom_number_drawn: StringProperty(name="Drawn",
232         default="---", description = "Number of drawn atoms in the cluster")
233
234     radius_how: EnumProperty(
235         name="",
236         description="Which objects shall be modified?",
237         items=(('ALL_ACTIVE',"all active objects", "in the current layer"),
238                ('ALL_IN_LAYER',"all"," in active layer(s)")),
239                default='ALL_ACTIVE',)
240     radius_type: EnumProperty(
241         name="Type",
242         description="Which type of atom radii?",
243         items=(('0',"predefined", "Use pre-defined radii"),
244                ('1',"atomic", "Use atomic radii"),
245                ('2',"van der Waals","Use van der Waals radii")),
246                default='0',update=Callback_radius_type)
247     radius_all: FloatProperty(
248         name="Scale", default = 1.05, min=0.0,
249         description="Put in the scale factor")
250
251
252 # The button for reloading the atoms and creating the scene
253 class CLASS_atom_cluster_load_button(Operator):
254     bl_idname = "atom_cluster.load"
255     bl_label = "Load"
256     bl_description = "Load the cluster"
257
258     def execute(self, context):
259         scn    = context.scene.atom_cluster
260
261         add_mesh_cluster.DEF_atom_read_atom_data()
262         del add_mesh_cluster.ATOM_CLUSTER_ALL_ATOMS[:]
263
264         if scn.shape in ["parabolid_ab", "parabolid_abc", "parabolid_square"]:
265             parameter1 = scn.parabol_height
266             parameter2 = scn.parabol_diameter
267         elif scn.shape == "pyramide_hex_abc":
268             parameter1 = scn.size * 1.6
269             parameter2 = scn.skin
270         elif scn.shape == "pyramide_square":
271             parameter1 = scn.size * 1.4
272             parameter2 = scn.skin
273         elif scn.shape in ["octahedron", "truncated_octahedron"]:
274             parameter1 = scn.size * 1.4
275             parameter2 = scn.skin
276         elif scn.shape in ["icosahedron"]:
277             parameter1 = scn.icosahedron_size
278         else:
279             parameter1 = scn.size
280             parameter2 = scn.skin
281
282         if scn.shape in ["octahedron", "truncated_octahedron", "sphere_square", "pyramide_square", "parabolid_square"]:
283             numbers = add_mesh_cluster.create_square_lattice(
284                                 scn.shape,
285                                 parameter1,
286                                 parameter2,
287                                 (scn.lattice_parameter/2.0))
288
289         if scn.shape in ["sphere_hex_ab", "parabolid_ab"]:
290             numbers = add_mesh_cluster.create_hexagonal_abab_lattice(
291                                 scn.shape,
292                                 parameter1,
293                                 parameter2,
294                                 scn.lattice_parameter)
295
296         if scn.shape in ["sphere_hex_abc", "pyramide_hex_abc", "parabolid_abc"]:
297             numbers = add_mesh_cluster.create_hexagonal_abcabc_lattice(
298                                 scn.shape,
299                                 parameter1,
300                                 parameter2,
301                                 scn.lattice_parameter)
302
303         if scn.shape in ["icosahedron"]:
304             numbers = add_mesh_cluster.create_icosahedron(
305                                 parameter1,
306                                 scn.lattice_parameter)
307
308         DEF_atom_draw_atoms(scn.element,
309                             scn.radius_type,
310                             scn.scale_radius,
311                             scn.scale_distances)
312
313         scn.atom_number_total = str(numbers[0])
314         scn.atom_number_drawn = str(numbers[1])
315
316         return {'FINISHED'}
317
318
319 def DEF_atom_draw_atoms(prop_element,
320                         prop_radius_type,
321                         prop_scale_radius,
322                         prop_scale_distances):
323
324     current_layers=bpy.context.scene.layers
325
326     for element in add_mesh_cluster.ATOM_CLUSTER_ELEMENTS:
327         if prop_element in element.name:
328             number = element.number
329             name = element.name
330             color = element.color
331             radii = element.radii
332             break
333
334     material = bpy.data.materials.new(name)
335     material.name = name
336     material.diffuse_color = color
337
338     atom_vertices = []
339     for atom in add_mesh_cluster.ATOM_CLUSTER_ALL_ATOMS:
340         atom_vertices.append( atom.location * prop_scale_distances )
341
342     # Build the mesh
343     atom_mesh = bpy.data.meshes.new("Mesh_"+name)
344     atom_mesh.from_pydata(atom_vertices, [], [])
345     atom_mesh.update()
346     new_atom_mesh = bpy.data.objects.new(name, atom_mesh)
347     bpy.context.scene.objects.link(new_atom_mesh)
348
349     bpy.ops.surface.primitive_nurbs_surface_sphere_add(
350                             view_align=False, enter_editmode=False,
351                             location=(0,0,0), rotation=(0.0, 0.0, 0.0),
352                             layers=current_layers)
353
354     ball = bpy.context.view_layer.objects.active
355     ball.scale  = (radii[int(prop_radius_type)]*prop_scale_radius,) * 3
356
357     ball.active_material = material
358     ball.parent = new_atom_mesh
359     new_atom_mesh.instance_type = 'VERTS'
360
361     # ------------------------------------------------------------------------
362     # SELECT ALL LOADED OBJECTS
363     bpy.ops.object.select_all(action='DESELECT')
364     new_atom_mesh.select_set(True)
365     bpy.context.view_layer.objects.active = new_atom_mesh
366
367     return True
368
369
370 # Routine to modify the radii via the type: predefined, atomic or van der Waals
371 # Explanations here are also valid for the next 3 DEFs.
372 def DEF_atom_cluster_radius_type(rtype,how):
373
374     if how == "ALL_IN_LAYER":
375
376         # Note all layers that are active.
377         layers = []
378         for i in range(20):
379             if bpy.context.scene.layers[i] == True:
380                 layers.append(i)
381
382         # Put all objects, which are in the layers, into a list.
383         change_objects = []
384         for obj in bpy.context.scene.objects:
385             for layer in layers:
386                 if obj.layers[layer] == True:
387                     change_objects.append(obj)
388
389         # Consider all objects, which are in the list 'change_objects'.
390         for obj in change_objects:
391             if len(obj.children) != 0:
392                 if obj.children[0].type == "SURFACE" or obj.children[0].type  == "MESH":
393                     for element in add_mesh_cluster.ATOM_CLUSTER_ELEMENTS:
394                         if element.name in obj.name:
395                             obj.children[0].scale = (element.radii[int(rtype)],) * 3
396             else:
397                 if obj.type == "SURFACE" or obj.type == "MESH":
398                     for element in add_mesh_cluster.ATOM_CLUSTER_ELEMENTS:
399                         if element.name in obj.name:
400                             obj.scale = (element.radii[int(rtype)],) * 3
401
402
403 # Routine to scale the radii of all atoms
404 def DEF_atom_cluster_radius_all(scale, how):
405
406     if how == "ALL_IN_LAYER":
407
408         layers = []
409         for i in range(20):
410             if bpy.context.scene.layers[i] == True:
411                 layers.append(i)
412
413         change_objects = []
414         for obj in bpy.context.scene.objects:
415             for layer in layers:
416                 if obj.layers[layer] == True:
417                     change_objects.append(obj)
418
419         for obj in change_objects:
420             if len(obj.children) != 0:
421                 if obj.children[0].type == "SURFACE" or obj.children[0].type  == "MESH":
422                     if "Stick" not in obj.name:
423                         obj.children[0].scale *= scale
424             else:
425                 if obj.type == "SURFACE" or obj.type == "MESH":
426                     if "Stick" not in obj.name:
427                         obj.scale *= scale
428
429     if how == "ALL_ACTIVE":
430         for obj in bpy.context.selected_objects:
431             if len(obj.children) != 0:
432                 if obj.children[0].type == "SURFACE" or obj.children[0].type  == "MESH":
433                     if "Stick" not in obj.name:
434                         obj.children[0].scale *= scale
435             else:
436                 if obj.type == "SURFACE" or obj.type == "MESH":
437                     if "Stick" not in obj.name:
438                         obj.scale *= scale
439
440
441 # Button for increasing the radii of all atoms
442 class CLASS_atom_cluster_radius_all_bigger_button(Operator):
443     bl_idname = "atom_cluster.radius_all_bigger"
444     bl_label = "Bigger ..."
445     bl_description = "Increase the radii of the atoms"
446
447     def execute(self, context):
448         scn = bpy.context.scene.atom_cluster
449         DEF_atom_cluster_radius_all(
450                 scn.radius_all,
451                 scn.radius_how,)
452         return {'FINISHED'}
453
454
455 # Button for decreasing the radii of all atoms
456 class CLASS_atom_cluster_radius_all_smaller_button(Operator):
457     bl_idname = "atom_cluster.radius_all_smaller"
458     bl_label = "Smaller ..."
459     bl_description = "Decrease the radii of the atoms"
460
461     def execute(self, context):
462         scn = bpy.context.scene.atom_cluster
463         DEF_atom_cluster_radius_all(
464                 1.0/scn.radius_all,
465                 scn.radius_how,)
466         return {'FINISHED'}
467
468
469 # Routine to scale the radii of all atoms
470 def DEF_atom_cluster_radius_all(scale, how):
471
472     if how == "ALL_IN_LAYER":
473
474         layers = []
475         for i in range(20):
476             if bpy.context.scene.layers[i] == True:
477                 layers.append(i)
478
479         change_objects = []
480         for obj in bpy.context.scene.objects:
481             for layer in layers:
482                 if obj.layers[layer] == True:
483                     change_objects.append(obj)
484
485
486         for obj in change_objects:
487             if len(obj.children) != 0:
488                 if obj.children[0].type == "SURFACE" or obj.children[0].type  == "MESH":
489                     if "Stick" not in obj.name:
490                         obj.children[0].scale *= scale
491             else:
492                 if obj.type == "SURFACE" or obj.type == "MESH":
493                     if "Stick" not in obj.name:
494                         obj.scale *= scale
495
496     if how == "ALL_ACTIVE":
497         for obj in bpy.context.selected_objects:
498             if len(obj.children) != 0:
499                 if obj.children[0].type == "SURFACE" or obj.children[0].type  == "MESH":
500                     if "Stick" not in obj.name:
501                         obj.children[0].scale *= scale
502             else:
503                 if obj.type == "SURFACE" or obj.type == "MESH":
504                     if "Stick" not in obj.name:
505                         obj.scale *= scale
506
507
508 # The entry into the menu 'file -> import'
509 def DEF_menu_func(self, context):
510     self.layout.operator(CLASS_ImportCluster.bl_idname, icon='PLUGIN')
511
512 def register():
513     bpy.utils.register_module(__name__)
514     bpy.types.Scene.atom_cluster = bpy.props.PointerProperty(type=
515                                                   CLASS_atom_cluster_Properties)
516     bpy.types.VIEW3D_MT_mesh_add.append(DEF_menu_func)
517
518 def unregister():
519     bpy.utils.unregister_module(__name__)
520     bpy.types.VIEW3D_MT_mesh_add.remove(DEF_menu_func)
521
522 if __name__ == "__main__":
523
524     register()