「Dev:IT/2.5/Py/Scripts/Cookbook/Code snippets/Multi-File packages」の版間の差分
細 |
(相違点なし)
|
2011年5月15日 (日) 05:47時点における版
Multi-file packages
I Packages sono un modo di strutturare lo spazio dei nomi dei moduli di Python usando nomi di modulo separati da punti. Per esempio, il nome del modulo A.B
designa un sottomodulo chiamato B
in un package chiamato A
. Proprio come l'uso dei moduli permette ad autori di moduli differenti di non doversi preoccupare dei nomi delle rispettive variabili globali, usare nomi di moduli separati da punti risparmia agli autori di package multi-modulari ogni preoccupazione circa i nomi dei moduli. Per ulteriori informazioni sui Python packages, fai riferimento alla documentazione Python al seguente indirizzo Packages
Ogni package deve contenere un file chiamato __init__.py
. Questo file è richiesto per permettere a Python di trattare la directory come contenitore di package; questo è fatto per evitare che le directory con un nome comune, come stringa, nascondano involontariamente moduli validi che si verificano in seguito sul modulo di ricerca del percorso. Nel caso più semplice, __init__.py
può essere semplicemente un file vuoto, ma può anche eseguire del codice di inizializzazione per il package. In Blender, __init__.py
spesso contiene l'interfaccia utente e le informazioni sull'add-on, mentre il lavoro reale è fatto in altri file.
Diversamente dagli altri script in questo libro, un multi-file package non può essere eseguito dal text editor. Deve essere copiato in una directory che è inclusa nel percorso di ricerca di Blender, per es. addons
o addons-contrib
, vedi la sezione Blender add-ons. Fortunatamente non devi riavviare Blender per ricaricare i files dopo ogni modifica. Premi F8 per ricaricare tutti gli add-ons in Blender.
Un semplice esempio
Questo package è diviso in quattro file. Tre di questi creano delle mesh; rispettivamente un cubo, un cilindro, e una sfera. Questi file sono script indipendenti che possono essere eseguiti nella finestra text editor per poter fare il debugging. La condizione (__name__ == "__main__")
è vera quando il file è eseguito in modalità indipendente.
mycube.py
#----------------------------------------------------------
# File mycube.py
#----------------------------------------------------------
import bpy
def makeMesh(z):
bpy.ops.mesh.primitive_cube_add(location=(0,0,z))
return bpy.context.object
if __name__ == "__main__":
ob = makeMesh(1)
print(ob, "created")
mycylinder.py
#----------------------------------------------------------
# File mycylinder.py
#----------------------------------------------------------
import bpy
def makeMesh(z):
bpy.ops.mesh.primitive_cylinder_add(location=(0,0,z))
return bpy.context.object
if __name__ == "__main__":
ob = makeMesh(5)
print(ob, "created")
mysphere.py
#----------------------------------------------------------
# File mysphere.py
#----------------------------------------------------------
import bpy
def makeMesh(z):
bpy.ops.mesh.primitive_ico_sphere_add(location=(0,0,z))
return bpy.context.object
if __name__ == "__main__":
ob = makeMesh(3)
print(ob, "created")
__init__.py
Il quarto file contiene il dizionario bl_info
e la registrazione necessaria per gli add-on e l'interfaccia utente. Contiene anche il seguente pezzo di codice per importare gli altri file nel package.
# To support reload properly, try to access a package var,
# if it's there, reload everything
if "bpy" in locals():
import imp
imp.reload(mycube)
imp.reload(mysphere)
imp.reload(mycylinder)
print("Reloaded multifiles")
else:
from . import mycube, mysphere, mycylinder
print("Imported multifiles")
Questo codice lavora nella seguente maniera.
- Se
__init__.py()
è eseguito per la prima volta, per es. all'avvio di Blender con l'add-on abiilitato,"bpy"
inlocals()
è False.Gli altri file nel package sono importati, e Imported multifiles è stampato nella console. - Se
__init__.py()
è eseguito per la prima volta dopo l'avvio di Blender, con l'add-on disabilitato, e clicchi su abilita add-on,"bpy"
inlocals()
è False. Gli altri file nel package sono importati, e Imported multifiles è stampato nella console. - Una volta che l'add-on è abilitato, ogni volta che premi F8 per ricaricare gli add-on,
"bpy"
inlocals()
è True. Gli altri file nel package sono ricaricati, e Reloaded multifiles è stampato nellam console.
#----------------------------------------------------------
# File __init__.py
#----------------------------------------------------------
# Addon info
bl_info = {
"name": "Multifile",
'author': 'Thomas Larsson',
"location": "View3D > UI panel > Add meshes",
"category": "3D View"
}
# To support reload properly, try to access a package var,
# if it's there, reload everything
if "bpy" in locals():
import imp
imp.reload(mycube)
imp.reload(mysphere)
imp.reload(mycylinder)
print("Reloaded multifiles")
else:
from . import mycube, mysphere, mycylinder
print("Imported multifiles")
import bpy
from bpy.props import *
#
# class AddMeshPanel(bpy.types.Panel):
#
class AddMeshPanel(bpy.types.Panel):
bl_label = "Add meshes"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
def draw(self, context):
self.layout.operator("multifile.add",
text="Add cube").mesh = "cube"
self.layout.operator("multifile.add",
text="Add cylinder").mesh = "cylinder"
self.layout.operator("multifile.add",
text="Add sphere").mesh = "sphere"
#
# class OBJECT_OT_AddButton(bpy.types.Operator):
#
class OBJECT_OT_AddButton(bpy.types.Operator):
bl_idname = "multifile.add"
bl_label = "Add"
mesh = bpy.props.StringProperty()
def execute(self, context):
if self.mesh == "cube":
mycube.makeMesh(-8)
elif self.mesh == "cylinder":
mycylinder.makeMesh(-5)
elif self.mesh == "sphere":
mysphere.makeMesh(-2)
return{'FINISHED'}
#
# Registration
#
def register():
bpy.utils.register_module(__name__)
def unregister():
bpy.utils.unregister_module(__name__)
if __name__ == "__main__":
register()
Un semplice importatore esportatore per obj
Il formato obj è comunemente usato per scambiare i dati delle mesh tra differenti applicazioni. Originariamente inventato per Wavefront Maya, è diventato uno standard industriale. E' un semplice formato ASCII che contiene linee nel seguente formato:
- v x y z
Coordinate del vertice, sono (x, y, z) - vt u v
Coordinate del vertice della texture, sono (u, v) - f v1 v2 ... vn
Facce con n angoli, al vertice v1, v2, ... vn. Per mesh senza UVs. - f v1/vt1 v2/vt2 ... vn/vtn
Facce con n angoli. Gli angoli sono al vertice v1, v2, ... vn nello spazio 3D e a vt1, vt2, ... vtn nello spazio della texture.
Esistono altri costrutti, per es. per le impostazioni del materiale e gruppi di facce, negli importatori/esportatori completi.
Ci sono due cose da sapere. Primo, molte applicazioni (a quanto ne so, tutte eccetto Blender) usano la convenzione in cui l'asse dell Y punto in alto, mentre in Blender è l'asse delle Z che punta in alto. Secondo, Maya inizia a contare i vertici da 1, mentre Blender inizia a contare da 0. Questo significa che gli angoli delle facce sono realmente situate a v1-1, v2-1, ... vn-1 nello spazio 3D e a vt1-1, vt2-1, ... vtn-1 nello spazio delle texture.
Il semplice importatore-esportatore di obj è un package Python composto da tre file: due file fanno il lavoro di import/export, e __init__.py
che trasforma la directory in un package.
Simple obj exporter
Questo script esporta la mesh selezionata in un file obj.
#----------------------------------------------------------
# File export_simple_obj.py
# Simple obj exporter which writes only verts, faces, and texture verts
#----------------------------------------------------------
import bpy, os
def export_simple_obj(filepath, ob, rot90, scale):
name = os.path.basename(filepath)
realpath = os.path.realpath(os.path.expanduser(filepath))
fp = open(realpath, 'w')
print('Exporting %s' % realpath)
if not ob or ob.type != 'MESH':
raise NameError('Cannot export: active object %s is not a mesh.' % ob)
me = ob.data
for v in me.vertices:
x = scale*v.co
if rot90:
fp.write("v %.5f %.5f %.5f\n" % (x[0], x[2], -x[1]))
else:
fp.write("v %.5f %.5f %.5f\n" % (x[0], x[1], x[2]))
if len(me.uv_textures) > 0:
uvtex = me.uv_textures[0]
for f in me.faces:
data = uvtex.data[f.index]
fp.write("vt %.5f %.5f\n" % (data.uv1[0], data.uv1[1]))
fp.write("vt %.5f %.5f\n" % (data.uv2[0], data.uv2[1]))
fp.write("vt %.5f %.5f\n" % (data.uv3[0], data.uv3[1]))
if len(f.vertices) == 4:
fp.write("vt %.5f %.5f\n" % (data.uv4[0], data.uv4[1]))
vt = 1
for f in me.faces:
vs = f.vertices
fp.write("f %d/%d %d/%d %d/%d" % (vs[0]+1, vt, vs[1]+1, vt+1, vs[2]+1, vt+2))
vt += 3
if len(f.vertices) == 4:
fp.write(" %d/%d\n" % (vs[3]+1, vt))
vt += 1
else:
fp.write("\n")
else:
for f in me.faces:
vs = f.vertices
fp.write("f %d %d %d" % (vs[0]+1, vs[1]+1, vs[2]+1))
if len(f.vertices) == 4:
fp.write(" %d\n" % (vs[3]+1))
else:
fp.write("\n")
print('%s successfully exported' % realpath)
fp.close()
return
Simple obj import
Questo script è il compagno di importazione di quello precedente. Si può naturalmente anche utilizzare per importare i file obj da altre applicazioni.
#----------------------------------------------------------
# File import_simple_obj.py
# Simple obj importer which reads only verts, faces, and texture verts
#----------------------------------------------------------
import bpy, os
def import_simple_obj(filepath, rot90, scale):
name = os.path.basename(filepath)
realpath = os.path.realpath(os.path.expanduser(filepath))
fp = open(realpath, 'rU') # Universal read
print('Importing %s' % realpath)
verts = []
faces = []
texverts = []
texfaces = []
for line in fp:
words = line.split()
if len(words) == 0:
pass
elif words[0] == 'v':
(x,y,z) = (float(words[1]), float(words[2]), float(words[3]))
if rot90:
verts.append( (scale*x, -scale*z, scale*y) )
else:
verts.append( (scale*x, scale*y, scale*z) )
elif words[0] == 'vt':
texverts.append( (float(words[1]), float(words[2])) )
elif words[0] == 'f':
(f,tf) = parseFace(words)
faces.append(f)
if tf:
texfaces.append(tf)
else:
pass
print('%s successfully imported' % realpath)
fp.close()
me = bpy.data.meshes.new(name)
me.from_pydata(verts, [], faces)
me.update()
if texverts:
uvtex = me.uv_textures.new()
uvtex.name = name
data = uvtex.data
for n in range(len(texfaces)):
tf = texfaces[n]
data[n].uv1 = texverts[tf[0]]
data[n].uv2 = texverts[tf[1]]
data[n].uv3 = texverts[tf[2]]
if len(tf) == 4:
data[n].uv4 = texverts[tf[3]]
scn = bpy.context.scene
ob = bpy.data.objects.new(name, me)
scn.objects.link(ob)
scn.objects.active = ob
return
def parseFace(words):
face = []
texface = []
for n in range(1, len(words)):
li = words[n].split('/')
face.append( int(li[0])-1 )
try:
texface.append( int(li[1])-1 )
except:
pass
return (face, texface)
__init__.py
Questo file contiene l'interfaccia utente, ovvero le due classi che creano le voci di menu per l'esportatore e l'importatore. Il semplice esportatore è richiamto dal menù File » Export. Ci sono due opzioni: una scelta booleana per ruotare la mesh di 90 gradi (per scambiare l'asse Y con l'asse Z), e un fattore di scala. Il semplice importatore è richiamato dal menù File » Import. Ci sono due opzioni: una scelta booleana per ruotare la mesh di 90 gradi (per far puntare in alto l'asse Z) e un fattore di scala.
__init__.py
contiene anche il dizionario bl_info
che rende il package un add-on di Blender, il codice di registrazione, e il codice per importare/ricaricare gli altri due file.
#----------------------------------------------------------
# File __init__.py
#----------------------------------------------------------
# Addon info
bl_info = {
"name": "Simple OBJ format",
"author": "Thomas Larsson",
"location": "File > Import-Export",
"description": "Simple Wavefront obj import/export. Does meshes and UV coordinates",
"category": "Import-Export"}
# To support reload properly, try to access a package var,
# if it's there, reload everything
if "bpy" in locals():
import imp
if 'simple_obj_import' in locals():
imp.reload(simple_obj_import)
if 'simple_obj_export' in locals():
imp.reload(simple_obj_export)
import bpy
from bpy.props import *
from io_utils import ExportHelper, ImportHelper
#
# Import menu
#
class IMPORT_OT_simple_obj(bpy.types.Operator, ImportHelper):
bl_idname = "io_import_scene.simple_obj"
bl_description = 'Import from simple OBJ file format (.obj)'
bl_label = "Import simple OBJ"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
filename_ext = ".obj"
filter_glob = StringProperty(default="*.obj;*.mtl", options={'HIDDEN'})
filepath = bpy.props.StringProperty(
name="File Path",
description="File path used for importing the simple OBJ file",
maxlen= 1024, default= "")
rot90 = bpy.props.BoolProperty(
name = "Rotate 90 degrees",
description="Rotate mesh to Z up",
default = True)
scale = bpy.props.FloatProperty(
name = "Scale",
description="Scale mesh",
default = 0.1, min = 0.001, max = 1000.0)
def execute(self, context):
from . import simple_obj_import
print("Load", self.properties.filepath)
simple_obj_import.import_simple_obj(
self.properties.filepath,
self.rot90,
self.scale)
return {'FINISHED'}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}
#
# Export menu
#
class EXPORT_OT_simple_obj(bpy.types.Operator, ExportHelper):
bl_idname = "io_export_scene.simple_obj"
bl_description = 'Export from simple OBJ file format (.obj)'
bl_label = "Export simple OBJ"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
# From ExportHelper. Filter filenames.
filename_ext = ".obj"
filter_glob = StringProperty(default="*.obj", options={'HIDDEN'})
filepath = bpy.props.StringProperty(
name="File Path",
description="File path used for exporting the simple OBJ file",
maxlen= 1024, default= "")
rot90 = bpy.props.BoolProperty(
name = "Rotate 90 degrees",
description="Rotate mesh to Y up",
default = True)
scale = bpy.props.FloatProperty(
name = "Scale",
description="Scale mesh",
default = 0.1, min = 0.001, max = 1000.0)
def execute(self, context):
print("Load", self.properties.filepath)
from . import simple_obj_export
simple_obj_export.export_simple_obj(
self.properties.filepath,
context.object,
self.rot90,
1.0/self.scale)
return {'FINISHED'}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}
#
# Registration
#
def menu_func_import(self, context):
self.layout.operator(IMPORT_OT_simple_obj.bl_idname, text="Simple OBJ (.obj)...")
def menu_func_export(self, context):
self.layout.operator(EXPORT_OT_simple_obj.bl_idname, text="Simple OBJ (.obj)...")
def register():
bpy.utils.register_module(__name__)
bpy.types.INFO_MT_file_import.append(menu_func_import)
bpy.types.INFO_MT_file_export.append(menu_func_export)
def unregister():
bpy.utils.unregister_module(__name__)
bpy.types.INFO_MT_file_import.remove(menu_func_import)
bpy.types.INFO_MT_file_export.remove(menu_func_export)
if __name__ == "__main__":
register()