Dev:IT/2.5/Py/Scripts/Cookbook/Code snippets/Multi-File packages

提供: wiki
移動先: 案内検索

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" in locals() è 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" in locals() è 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" in locals() è 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()