Basically, I have done same changes in the XYZ importer as in the PDB IO.
[blender-addons-contrib.git] / io_mesh_xyz / __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 bl_info = {
20     "name": "XYZ Atomic Blender",
21     "description": "Loading and manipulating atoms from XYZ files",
22     "author": "Clemens Barth",
23     "version": (0,6),
24     "blender": (2,6),
25     "location": "File -> Import -> XYZ (.xyz), Panel: View 3D - Tools",
26     "warning": "",
27     "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/"
28                 "Import-Export/XYZ",
29     "tracker_url": "http://projects.blender.org/tracker/"
30                    "index.php?func=detail&aid=29646&group_id=153&atid=468",
31     "category": "Import-Export"
32 }
33
34 import os
35 import io
36 import bpy
37 from bpy.types import Operator, Panel
38 from bpy_extras.io_utils import ImportHelper
39 from bpy.props import (StringProperty,
40                        BoolProperty,
41                        EnumProperty,
42                        IntProperty,
43                        FloatProperty)
44
45 from . import import_xyz
46 ATOM_XYZ_ERROR = ""
47 ATOM_XYZ_NOTE  = ""
48 ATOM_XYZ_PANEL = ""
49
50 # -----------------------------------------------------------------------------
51 #                                                                           GUI
52
53 # This is the panel, which can be used to prepare the scene.
54 # It is loaded after the file has been chosen via the menu 'File -> Import'
55 class CLASS_atom_xyz_prepare_panel(Panel):
56     bl_label       = "XYZ - Atomic Blender"
57     bl_space_type  = "VIEW_3D"
58     bl_region_type = "TOOL_PROPS"
59
60     @classmethod
61     def poll(self, context):
62         global ATOM_XYZ_PANEL
63         
64         if ATOM_XYZ_PANEL == "0" and import_xyz.ATOM_XYZ_FILEPATH == "":
65             return False
66         if ATOM_XYZ_PANEL == "0" and import_xyz.ATOM_XYZ_FILEPATH != "":
67             return True
68         if ATOM_XYZ_PANEL == "1":
69             return True
70         if ATOM_XYZ_PANEL == "2":
71             return False
72         
73         return True
74
75
76     def draw(self, context):
77         layout = self.layout
78         scn    = context.scene.atom_xyz[0]
79
80         row = layout.row()
81         row.label(text="Outputs and custom data file")
82         box = layout.box()
83         row = box.row()
84         row.label(text="Custom data file")
85         row = box.row()
86         col = row.column()
87         col.prop(scn, "datafile")
88         col.operator("atom_xyz.datafile_apply")
89         row = box.row()
90         col = row.column(align=True)
91         col.prop(scn, "XYZ_file")
92         row = box.row()
93         row.prop(scn, "number_atoms")
94         row = box.row()
95         row.operator("atom_xyz.button_distance")
96         row.prop(scn, "distance")
97         row = layout.row()
98         row.label(text="Choice of atom radii")
99         box = layout.box()
100         row = box.row()
101         row.label(text="All changes concern:")
102         row = box.row()
103         row.prop(scn, "radius_how")
104         row = box.row()
105         row.label(text="1. Change type of radii")
106         row = box.row()
107         row.prop(scn, "radius_type")
108         row = box.row()
109         row.label(text="2. Change atom radii in pm")
110         row = box.row()
111         row.prop(scn, "radius_pm_name")
112         row = box.row()
113         row.prop(scn, "radius_pm")
114         row = box.row()
115         row.label(text="3. Change atom radii by scale")
116         row = box.row()
117         col = row.column()
118         col.prop(scn, "radius_all")
119         col = row.column(align=True)
120         col.operator( "atom_xyz.radius_all_bigger" )
121         col.operator( "atom_xyz.radius_all_smaller" )
122
123         if bpy.context.mode == 'EDIT_MESH':
124             layout.separator()
125             row = box.row()
126             row.operator( "atom_xyz.separate_atom" )
127
128         row = layout.row()
129         row.label(text="Loading frames")
130         box = layout.box()
131         row = box.row()
132         col = row.column()
133         col.label(text="Frames")
134         col = row.column()
135         col.prop(scn, "number_frames")
136         row = box.row()
137         col = row.column()
138         col.label(text="Skip frames")
139         col = row.column()
140         col.prop(scn, "skip_frames")
141         row = box.row()
142         col = row.column()
143         col.label(text="Frames/key")
144         col = row.column()
145         col.prop(scn, "images_per_key")        
146         row = box.row()
147         row.operator("atom_xyz.load_frames")
148         row = box.row()
149         row.operator("atom_xyz.delete_keys")
150         row = box.row()
151         row.operator( "atom_xyz.create_command")
152         row = box.row()
153         row.operator( "atom_xyz.render")
154
155
156 class CLASS_atom_xyz_Properties(bpy.types.PropertyGroup):
157
158     def Callback_radius_type(self, context):
159         scn = bpy.context.scene.atom_xyz[0]
160         import_xyz.DEF_atom_xyz_radius_type(
161                 scn.radius_type,
162                 scn.radius_how,)
163
164     def Callback_radius_pm(self, context):
165         scn = bpy.context.scene.atom_xyz[0]
166         import_xyz.DEF_atom_xyz_radius_pm(
167                 scn.radius_pm_name,
168                 scn.radius_pm,
169                 scn.radius_how,)
170
171     # In the file dialog window
172     use_camera = BoolProperty(
173         name="Camera", default=False,
174         description="Do you need a camera?")
175     use_lamp = BoolProperty(
176         name="Lamp", default=False,
177         description = "Do you need a lamp?")
178     use_mesh = BoolProperty(
179         name = "Mesh balls", default=False,
180         description = "Do you want to use mesh balls instead of NURBS?")
181     mesh_azimuth = IntProperty(
182         name = "Azimuth", default=32, min=0,
183         description = "Number of sectors (azimuth)")
184     mesh_zenith = IntProperty(
185         name = "Zenith", default=32, min=0,
186         description = "Number of sectors (zenith)")
187     scale_ballradius = FloatProperty(
188         name = "Balls", default=1.0, min=0.0,
189         description = "Scale factor for all atom radii")
190     scale_distances = FloatProperty (
191         name = "Distances", default=1.0, min=0.0,
192         description = "Scale factor for all distances")
193     use_center = BoolProperty(
194         name = "Object to origin", default=False,
195         description = "Shall the object first put into the global origin "
196         "before applying the offsets on the left?")
197     atomradius = EnumProperty(
198         name="Type of radius",
199         description="Choose type of atom radius",
200         items=(('0', "Pre-defined", "Use pre-defined radii"),
201                ('1', "Atomic", "Use atomic radii"),
202                ('2', "van der Waals", "Use van der Waals radii")),
203                default='0',)
204     # In the panel, first part
205     datafile = StringProperty(
206         name = "", description="Path to your custom data file",
207         maxlen = 256, default = "", subtype='FILE_PATH')
208     XYZ_file = StringProperty(
209         name = "Path to file", default="",
210         description = "Path of the XYZ file")
211     number_atoms = StringProperty(name="",
212         default="Number", description = "This output shows "
213         "the number of atoms which have been loaded")
214     distance = StringProperty(
215         name="", default="Distance (A)",
216         description="Distance of 2 objects in Angstrom")
217     radius_how = EnumProperty(
218         name="",
219         description="Which objects shall be modified?",
220         items=(('ALL_ACTIVE',"all active objects", "in the current layer"),
221                ('ALL_IN_LAYER',"all"," in active layer(s)")),
222                default='ALL_ACTIVE',)
223     radius_type = EnumProperty(
224         name="Type",
225         description="Which type of atom radii?",
226         items=(('0',"predefined", "Use pre-defined radii"),
227                ('1',"atomic", "Use atomic radii"),
228                ('2',"van der Waals","Use van der Waals radii")),
229                default='0',update=Callback_radius_type)
230     radius_pm_name = StringProperty(
231         name="", default="Atom name",
232         description="Put in the name of the atom (e.g. Hydrogen)")
233     radius_pm = FloatProperty(
234         name="", default=100.0, min=0.0,
235         description="Put in the radius of the atom (in pm)",
236         update=Callback_radius_pm)
237     radius_all = FloatProperty(
238         name="Scale", default = 1.05, min=1.0, max=5.0,
239         description="Put in the scale factor")
240     # In the panel, second part
241     number_frames = StringProperty(
242         name="", default="0",
243         description="This is the total number of frames stored in the xyz file")
244     skip_frames = IntProperty(
245         name="", default=0, min=0,
246         description="Number of frames you want to skip.")
247     images_per_key = IntProperty(
248         name="", default=1, min=1,
249         description="Choose the number of images between 2 keys.")
250
251
252
253 # Button for creating a file that contains the command for rendering
254 class CLASS_atom_xyz_create_command(Operator):
255     bl_idname = "atom_xyz.create_command"
256     bl_label = "Create command"
257     bl_description = "Create a shell command for rendering the scene"
258
259     def execute(self, context):
260         global ATOM_XYZ_ERROR
261         global ATOM_XYZ_NOTE
262  
263         scn = bpy.context.scene
264
265         fstart = scn.frame_start
266         fend = scn.frame_end
267         file_blend = bpy.context.blend_data.filepath
268         
269         if file_blend == "":
270             ATOM_XYZ_ERROR = "Save your scene first !"
271             bpy.ops.atom_xyz.error_dialog('INVOKE_DEFAULT')
272             return {'FINISHED'}
273             
274         cameras = []    
275         FOUND = False    
276         for obj in bpy.context.scene.objects:  
277             if obj.type == "CAMERA":
278                 cameras.append(obj)
279                 FOUND = True   
280         if FOUND == False:
281             ATOM_XYZ_ERROR = "No camera => no images !"
282             bpy.ops.atom_xyz.error_dialog('INVOKE_DEFAULT')
283             return {'FINISHED'}      
284         if bpy.context.scene.camera == None:
285             bpy.context.scene.camera = cameras[0]
286             
287         KEYS_PRESENT = True
288         for element in import_xyz.STRUCTURE:
289             bpy.ops.object.select_all(action='DESELECT')
290             bpy.context.scene.objects.active = element
291             element.select = True
292             if element.data.shape_keys == None:
293                 KEYS_PRESENT = False
294                 break       
295         if KEYS_PRESENT == False:
296             ATOM_XYZ_ERROR = "No frames => no movie !"
297             bpy.ops.atom_xyz.error_dialog('INVOKE_DEFAULT')
298             return {'FINISHED'}     
299         
300         bpy.ops.wm.save_mainfile()
301         file_name = bpy.path.basename(file_blend)
302         file_path = file_blend.replace(file_name,"")
303         file_movie = bpy.path.display_name_from_filepath(file_blend)
304         blender_exe = bpy.app.binary_path
305                 
306         if os.name == "posix":
307             execute = (blender_exe+" -b \'"+file_blend+"\' -x 1 -o //"+file_movie+
308                   "_ -F AVIJPEG -s "+str(fstart)+" -e "+str(scn.frame_end)+" -a")
309         else:
310             execute = ("\""+blender_exe+"\" -b "+file_blend+" -x 1 -o //"+file_movie+
311                   "_ -F AVIJPEG -s "+str(fstart)+" -e "+str(scn.frame_end)+" -a")
312
313         if os.name == "posix":
314             command_file = file_path + file_movie + ".sh"
315         else:
316             command_file = file_path + file_movie + ".txt"
317         command_fp = open(command_file,"w")
318            
319         if os.name == "posix":        
320             command_fp.write("#!/bin/sh\n")   
321         command_fp.write("\n"+execute+"\n")     
322         command_fp.close()
323         
324         ATOM_XYZ_NOTE = "The command has been stored (dir. of the .blend file)"
325         bpy.ops.atom_xyz.note_dialog('INVOKE_DEFAULT')
326
327         return {'FINISHED'}
328
329
330 # Button for rendering the scene in a terminal
331 class CLASS_atom_xyz_render(Operator):
332     bl_idname = "atom_xyz.render"
333     bl_label = "Render"
334     bl_description = "Render the scene"
335
336     def execute(self, context):
337         global ATOM_XYZ_ERROR
338         scn = bpy.context.scene
339
340         fstart = scn.frame_start
341         fend = scn.frame_end
342         file_blend = bpy.context.blend_data.filepath
343         
344         if file_blend == "":
345             ATOM_XYZ_ERROR = "Save your scene first!"
346             bpy.ops.atom_xyz.error_dialog('INVOKE_DEFAULT')
347             return {'FINISHED'}
348             
349         cameras = []    
350         FOUND = False    
351         for obj in bpy.context.scene.objects:  
352             if obj.type == "CAMERA":
353                 cameras.append(obj)
354                 FOUND = True   
355         if FOUND == False:
356             ATOM_XYZ_ERROR = "No camera => no images !"
357             bpy.ops.atom_xyz.error_dialog('INVOKE_DEFAULT')
358             return {'FINISHED'}      
359         if bpy.context.scene.camera == None:
360             bpy.context.scene.camera = cameras[0]
361             
362             
363         KEYS_PRESENT = True
364         for element in import_xyz.STRUCTURE:
365             bpy.ops.object.select_all(action='DESELECT')
366             bpy.context.scene.objects.active = element
367             element.select = True
368             if element.data.shape_keys == None:
369                 KEYS_PRESENT = False
370                 break       
371         if KEYS_PRESENT == False:
372             ATOM_XYZ_ERROR = "No frames => no movie !"
373             bpy.ops.atom_xyz.error_dialog('INVOKE_DEFAULT')
374             return {'FINISHED'}    
375             
376         bpy.ops.wm.save_mainfile()    
377         
378         file_name = bpy.path.basename(file_blend)
379         file_path = file_blend.replace(file_name,"")
380         file_movie = bpy.path.display_name_from_filepath(file_blend)
381         blender_exe = bpy.app.binary_path
382  
383         if os.name == "posix":
384             execute = (blender_exe+" -b \'"+file_blend+"\' -x 1 -o //"+file_movie+
385                   "_ -F AVIJPEG -s "+str(fstart)+" -e "+str(scn.frame_end)+" -a")
386             os_str = "xterm -e \"" + execute + "\" &"
387         else:
388             execute = ("\""+blender_exe+"\" -b "+file_blend+" -x 1 -o //"+file_movie+
389                   "_ -F AVIJPEG -s "+str(fstart)+" -e "+str(scn.frame_end)+" -a")
390             os_str = "C:\WINDOWS\system32\cmd.exe /C " + execute
391              
392         os.system(os_str)    
393         
394         return {'FINISHED'}
395
396
397 # Button deleting all shape keys of the structure
398 class CLASS_atom_xyz_delete_keys(Operator):
399     bl_idname = "atom_xyz.delete_keys"
400     bl_label = "Delete keys"
401     bl_description = "Delete the shape keys"
402
403     def execute(self, context):
404         for element in import_xyz.STRUCTURE:
405             if element.data.shape_keys == None:
406                 break
407         
408             bpy.ops.object.select_all(action='DESELECT')
409             bpy.context.scene.objects.active = element
410             element.select = True
411         
412             for key in element.data.shape_keys.key_blocks:
413                 bpy.ops.object.shape_key_remove()
414         
415         return {'FINISHED'}
416
417
418 # Button loading the shape keys
419 class CLASS_atom_xyz_load_frames(Operator):
420     bl_idname = "atom_xyz.load_frames"
421     bl_label = "Load frames"
422     bl_description = "Load the frames"
423
424     def execute(self, context):
425         global ATOM_XYZ_ERROR
426         scn = bpy.context.scene.atom_xyz[0]
427         
428         KEYS_PRESENT = False
429         for element in import_xyz.STRUCTURE:
430             bpy.ops.object.select_all(action='DESELECT')
431             bpy.context.scene.objects.active = element
432             element.select = True
433             if element.data.shape_keys != None:
434                 KEYS_PRESENT = True
435                 break
436                 
437         if KEYS_PRESENT == True:
438             ATOM_XYZ_ERROR = "Delete first the keys"
439             bpy.ops.atom_xyz.error_dialog('INVOKE_DEFAULT')
440             return {'FINISHED'}
441         
442         
443         import_xyz.DEF_atom_xyz_build_frames(scn.images_per_key, 
444                                              scn.skip_frames)
445
446         return {'FINISHED'}
447
448
449
450 # Button loading a custom data file
451 class CLASS_atom_xyz_datafile_apply(Operator):
452     bl_idname = "atom_xyz.datafile_apply"
453     bl_label = "Apply"
454     bl_description = "Use color and radii values stored in the custom file"
455
456     def execute(self, context):
457         scn = bpy.context.scene.atom_xyz[0]
458
459         if scn.datafile == "":
460             return {'FINISHED'}
461
462         import_xyz.DEF_atom_xyz_custom_datafile(scn.datafile)
463
464         # TODO, move this into 'import_xyz' and call the function
465         for obj in bpy.context.selected_objects:
466             if len(obj.children) != 0:
467                 child = obj.children[0]
468                 if child.type == "SURFACE" or child.type  == "MESH":
469                     for element in import_xyz.ATOM_XYZ_ELEMENTS:
470                         if element.name in obj.name:
471                             child.scale = (element.radii[0],) * 3
472                             child.active_material.diffuse_color = element.color
473             else:
474                 if obj.type == "SURFACE" or obj.type == "MESH":
475                     for element in import_xyz.ATOM_XYZ_ELEMENTS:
476                         if element.name in obj.name:
477                             obj.scale = (element.radii[0],) * 3
478                             obj.active_material.diffuse_color = element.color
479
480         return {'FINISHED'}
481
482
483 # Button for separating a single atom from a structure
484 class CLASS_atom_xyz_separate_atom(Operator):
485     bl_idname = "atom_xyz.separate_atom"
486     bl_label = "Separate atom"
487     bl_description = "Separate the atom you have chosen"
488
489     def execute(self, context):
490         scn = bpy.context.scene.atom_xyz[0]
491
492         # Get first all important properties from the atom which the user
493         # has chosen: location, color, scale
494         obj = bpy.context.edit_object
495         name = obj.name
496         loc_obj_vec = obj.location
497         scale = obj.children[0].scale
498         material = obj.children[0].active_material
499
500         # Separate the vertex from the main mesh and create a new mesh.
501         bpy.ops.mesh.separate()
502         new_object = bpy.context.scene.objects[0]
503         # Keep in mind the coordinates <= We only need this
504         loc_vec = new_object.data.vertices[0].co
505
506         # And now, switch to the OBJECT mode such that we can ...
507         bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
508         # ... delete the new mesh including the separated vertex
509         bpy.ops.object.select_all(action='DESELECT')
510         new_object.select = True
511         bpy.ops.object.delete()
512
513         # Create a new atom/vacancy at the position of the old atom
514         current_layers=bpy.context.scene.layers
515
516         if "Vacancy" not in name:
517             if scn.use_mesh == False:
518                 bpy.ops.surface.primitive_nurbs_surface_sphere_add(
519                                     view_align=False, enter_editmode=False,
520                                     location=loc_vec+loc_obj_vec,
521                                     rotation=(0.0, 0.0, 0.0),
522                                     layers=current_layers)
523             else:
524                 bpy.ops.mesh.primitive_uv_sphere_add(
525                                 segments=scn.mesh_azimuth,
526                                 ring_count=scn.mesh_zenith,
527                                 size=1, view_align=False, enter_editmode=False,
528                                 location=loc_vec+loc_obj_vec,
529                                 rotation=(0, 0, 0),
530                                 layers=current_layers)
531         else:
532             bpy.ops.mesh.primitive_cube_add(
533                                view_align=False, enter_editmode=False,
534                                location=loc_vec+loc_obj_vec,
535                                rotation=(0.0, 0.0, 0.0),
536                                layers=current_layers)
537
538         new_atom = bpy.context.scene.objects.active
539         # Scale, material and name it.
540         new_atom.scale = scale
541         new_atom.active_material = material
542         new_atom.name = name + "_sep"
543
544         # Switch back into the 'Edit mode' because we would like to seprate
545         # other atoms may be (more convinient)
546         new_atom.select = False
547         obj.select = True
548         bpy.context.scene.objects.active = obj
549         bpy.ops.object.select_all(action='DESELECT')
550         bpy.ops.object.mode_set(mode='EDIT', toggle=False)
551
552         return {'FINISHED'}
553
554
555 # Button for measuring the distance of the active objects
556 class CLASS_atom_xyz_distance_button(Operator):
557     bl_idname = "atom_xyz.button_distance"
558     bl_label = "Measure ..."
559     bl_description = "Measure the distance between two objects"
560
561     def execute(self, context):
562         scn  = bpy.context.scene.atom_xyz[0]
563         dist = import_xyz.DEF_atom_xyz_distance()
564
565         if dist != "N.A.":
566            # The string length is cut, 3 digits after the first 3 digits
567            # after the '.'. Append also "Angstrom".
568            # Remember: 1 Angstrom = 10^(-10) m
569            pos    = str.find(dist, ".")
570            dist   = dist[:pos+4]
571            dist   = dist + " A"
572
573         # Put the distance into the string of the output field.
574         scn.distance = dist
575         return {'FINISHED'}
576
577
578 # Button for increasing the radii of all atoms
579 class CLASS_atom_xyz_radius_all_bigger_button(Operator):
580     bl_idname = "atom_xyz.radius_all_bigger"
581     bl_label = "Bigger ..."
582     bl_description = "Increase the radii of the atoms"
583
584     def execute(self, context):
585         scn = bpy.context.scene.atom_xyz[0]
586         import_xyz.DEF_atom_xyz_radius_all(
587                 scn.radius_all,
588                 scn.radius_how,
589                 )
590         return {'FINISHED'}
591
592
593 # Button for decreasing the radii of all atoms
594 class CLASS_atom_xyz_radius_all_smaller_button(Operator):
595     bl_idname = "atom_xyz.radius_all_smaller"
596     bl_label = "Smaller ..."
597     bl_description = "Decrease the radii of the atoms"
598
599     def execute(self, context):
600         scn = bpy.context.scene.atom_xyz[0]
601         import_xyz.DEF_atom_xyz_radius_all(
602                 1.0/scn.radius_all,
603                 scn.radius_how,
604                 )
605         return {'FINISHED'}
606
607
608 def DEF_panel_yes_no():
609     global ATOM_XYZ_PANEL
610
611     datafile_path = bpy.utils.user_resource('SCRIPTS', path='', create=False)
612     if os.path.isdir(datafile_path) == False:
613         bpy.utils.user_resource('SCRIPTS', path='', create=True)
614         
615     datafile_path = os.path.join(datafile_path, "presets")
616     if os.path.isdir(datafile_path) == False:
617         os.mkdir(datafile_path)   
618         
619     datafile = os.path.join(datafile_path, "io_mesh_xyz.pref")
620     if os.path.isfile(datafile):
621         datafile_fp = io.open(datafile, "r")
622         for line in datafile_fp:
623             if "Panel" in line:
624                 ATOM_XYZ_PANEL = line[-2:]
625                 ATOM_XYZ_PANEL = ATOM_XYZ_PANEL[0:1]
626                 bpy.context.scene.use_panel = ATOM_XYZ_PANEL
627                 break       
628         datafile_fp.close()
629     else:
630         DEF_panel_write_pref("0") 
631
632
633 def DEF_panel_write_pref(value): 
634     datafile_path = bpy.utils.user_resource('SCRIPTS', path='', create=False)
635     datafile_path = os.path.join(datafile_path, "presets")
636     datafile = os.path.join(datafile_path, "io_mesh_xyz.pref")
637     datafile_fp = io.open(datafile, "w")
638     datafile_fp.write("Atomic Blender XYZ - Import/Export - Preferences\n")
639     datafile_fp.write("================================================\n")
640     datafile_fp.write("\n")
641     datafile_fp.write("Panel: "+value+"\n\n\n")
642     datafile_fp.close()
643
644
645 class CLASS_atom_xyz_error_dialog(bpy.types.Operator):
646     bl_idname = "atom_xyz.error_dialog"
647     bl_label = "Attention !"
648     
649     def draw(self, context):
650         layout = self.layout
651         row = layout.row()
652         row.label(text="                          "+ATOM_XYZ_ERROR) 
653     def execute(self, context):
654         print("Atomic Blender - Error: "+ATOM_XYZ_ERROR+"\n")
655         return {'FINISHED'}
656     def invoke(self, context, event):
657         return context.window_manager.invoke_props_dialog(self)
658
659
660 class CLASS_atom_xyz_note_dialog(bpy.types.Operator):
661     bl_idname = "atom_xyz.note_dialog"
662     bl_label = "Attention !"
663     
664     def draw(self, context):
665         layout = self.layout
666         row = layout.row()
667         row.label(text=ATOM_XYZ_NOTE) 
668     def execute(self, context):
669         print("Atomic Blender - Note: "+ATOM_XYZ_NOTE+"\n")
670         return {'FINISHED'}
671     def invoke(self, context, event):
672         return context.window_manager.invoke_props_dialog(self)
673
674
675 # This is the class for the file dialog.
676 class CLASS_ImportXYZ(Operator, ImportHelper):
677     bl_idname = "import_mesh.xyz"
678     bl_label  = "Import XYZ (*.xyz)"
679     bl_options = {'PRESET', 'UNDO'}
680     
681     filename_ext = ".xyz"
682     filter_glob  = StringProperty(default="*.xyz", options={'HIDDEN'},)
683
684     bpy.types.Scene.use_panel = EnumProperty(
685         name="Panel",
686         description="Choose whether the panel shall appear or not in the View 3D.",
687         items=(('0', "Once", "The panel appears only in this session"),
688                ('1', "Always", "The panel always appears when Blender is started"),
689                ('2', "Never", "The panel never appears")),
690                default='0') 
691     use_camera = BoolProperty(
692         name="Camera", default=False,
693         description="Do you need a camera?")
694     use_lamp = BoolProperty(
695         name="Lamp", default=False,
696         description = "Do you need a lamp?")
697     use_mesh = BoolProperty(
698         name = "Mesh balls", default=False,
699         description = "Use mesh balls instead of NURBS")
700     mesh_azimuth = IntProperty(
701         name = "Azimuth", default=32, min=1,
702         description = "Number of sectors (azimuth)")
703     mesh_zenith = IntProperty(
704         name = "Zenith", default=32, min=1,
705         description = "Number of sectors (zenith)")
706     scale_ballradius = FloatProperty(
707         name = "Balls", default=1.0, min=0.0001,
708         description = "Scale factor for all atom radii")
709     scale_distances = FloatProperty (
710         name = "Distances", default=1.0, min=0.0001,
711         description = "Scale factor for all distances")
712     atomradius = EnumProperty(
713         name="Type of radius",
714         description="Choose type of atom radius",
715         items=(('0', "Pre-defined", "Use pre-defined radius"),
716                ('1', "Atomic", "Use atomic radius"),
717                ('2', "van der Waals", "Use van der Waals radius")),
718                default='0',)            
719     use_center = BoolProperty(
720         name = "Object to origin", default=True,
721         description = "Put the object into the global origin")           
722     datafile = StringProperty(
723         name = "", description="Path to your custom data file",
724         maxlen = 256, default = "", subtype='FILE_PATH')    
725
726     def draw(self, context):
727         layout = self.layout
728         row = layout.row()
729         row.prop(self, "use_camera")
730         row.prop(self, "use_lamp")
731         row = layout.row()
732         col = row.column()
733         col.prop(self, "use_mesh")
734         col = row.column(align=True)
735         col.prop(self, "mesh_azimuth")
736         col.prop(self, "mesh_zenith")
737         row = layout.row()
738         col = row.column()
739         col.label(text="Scaling factors")
740         col = row.column(align=True)
741         col.prop(self, "scale_ballradius")
742         col.prop(self, "scale_distances")
743         row = layout.row()
744         row.prop(self, "use_center")
745         row = layout.row()
746         row.prop(self, "atomradius")
747         row = layout.row()
748         row.prop(bpy.context.scene, "use_panel")
749
750     def execute(self, context):
751         
752         import_xyz.ALL_FRAMES[:] = []
753         import_xyz.NUMBER_FRAMES = 0
754         import_xyz.ATOM_XYZ_ELEMENTS[:] = []
755         import_xyz.ATOM_XYZ_FILEPATH = ""
756         import_xyz.STRUCTURE[:] = []
757
758         # This is in order to solve this strange 'relative path' thing.
759         import_xyz.ATOM_XYZ_FILEPATH = bpy.path.abspath(self.filepath)
760
761         # Execute main routine
762         atom_number = import_xyz.DEF_atom_xyz_main(
763                       self.use_mesh,
764                       self.mesh_azimuth,
765                       self.mesh_zenith,
766                       self.scale_ballradius,
767                       self.atomradius,
768                       self.scale_distances,
769                       self.use_center,
770                       self.use_camera,
771                       self.use_lamp,
772                       self.datafile)
773                       
774         # Copy the whole bunch of values into the property collection.
775         scn = context.scene.atom_xyz[0]
776         scn.use_mesh = self.use_mesh
777         scn.mesh_azimuth = self.mesh_azimuth
778         scn.mesh_zenith = self.mesh_zenith
779         scn.scale_ballradius = self.scale_ballradius
780         scn.atomradius = self.atomradius
781         scn.scale_distances = self.scale_distances
782         scn.use_center = self.use_center
783         scn.use_camera = self.use_camera
784         scn.use_lamp = self.use_lamp
785         scn.datafile = self.datafile              
786                       
787         scn.number_atoms = str(atom_number) + " atoms"
788         scn.number_frames = str(import_xyz.NUMBER_FRAMES)
789         scn.XYZ_file = import_xyz.ATOM_XYZ_FILEPATH
790
791         global ATOM_XYZ_PANEL
792         ATOM_XYZ_PANEL = bpy.context.scene.use_panel
793         DEF_panel_write_pref(bpy.context.scene.use_panel)
794         
795         return {'FINISHED'}
796         
797
798 # The entry into the menu 'file -> import'
799 def menu_func(self, context):
800     self.layout.operator(CLASS_ImportXYZ.bl_idname, text="XYZ (.xyz)")
801
802
803 def register():
804     DEF_panel_yes_no()
805     bpy.utils.register_module(__name__)
806     bpy.types.INFO_MT_file_import.append(menu_func)
807     bpy.types.Scene.atom_xyz = bpy.props.CollectionProperty(type=CLASS_atom_xyz_Properties)    
808     bpy.context.scene.atom_xyz.add()
809     
810 def unregister():
811     bpy.utils.unregister_module(__name__)
812     bpy.types.INFO_MT_file_import.remove(menu_func)
813
814 if __name__ == "__main__":
815
816     register()