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