• R/O
  • HTTP
  • SSH
  • HTTPS

pymeshio: Commit

pmdとmqoの入出力ライブラリと、それを使ったBlender2.5向けのaddon。


Commit MetaInfo

Revision56e8906d87aa792e34cd819396e4970f4aac7372 (tree)
Time2011-10-12 16:31:31
Authorousttrue <ousttrue@gmai...>
Commiterousttrue

Log Message

replace blender25-meshio to blender26-meshio

Change Summary

Incremental Difference

--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@ dist
44 pymeshio.egg-info
55 *.pyc
66 __pycache__/*
7+blender26-meshio/pymeshio
--- a/blender25-meshio/__init__.py
+++ /dev/null
@@ -1,183 +0,0 @@
1-bl_info = {
2- "name": "meshio. (.pmd)(.mqo)",
3- "version": (1, 9),
4- "author": "ousttrue",
5- "blender": (2, 5, 9),
6- "api": 35622,
7- "location": "File > Import-Export",
8- "description": "Import-Export PMD/MQO meshes",
9- "warning": "",
10- "url": "http://meshio.sourceforge.jp/",
11- "tracker_url": "",
12- "support": 'COMMUNITY',
13- "category": "Import-Export"
14-}
15-
16-# To support reload properly, try to access a package var, if it's there, reload everything
17-if "bpy" in locals():
18- import imp
19- if "import_pmd" in locals():
20- imp.reload(import_pmd)
21- if "export_pmd" in locals():
22- imp.reload(export_pmd)
23-
24-
25-import bpy
26-from bpy.props import StringProperty, FloatProperty, BoolProperty
27-try:
28- from io_utils import ImportHelper, ExportHelper
29-except:
30- from bpy_extras.io_utils import ImportHelper, ExportHelper
31-from . import bl25 as bl
32-
33-
34-'''
35-PMD IMOPORTER
36-'''
37-class ImportPmd(bpy.types.Operator, ImportHelper):
38- '''Import from PMD file format (.pmd)'''
39- bl_idname = "import_scene.mmd_pmd"
40- bl_label = 'Import PMD'
41-
42- filename_ext = ".pmd"
43- filter_glob = StringProperty(default="*.pmd", options={'HIDDEN'})
44-
45- def execute(self, context):
46- from . import import_pmd
47- bl.initialize('pmd_import', context.scene)
48- import_pmd._execute(**self.as_keywords(
49- ignore=("filter_glob",)))
50- bl.finalize()
51- return {'FINISHED'}
52-
53-def menu_pmd_import(self, context):
54- self.layout.operator(ImportPmd.bl_idname,
55- text="MikuMikuDance model (.pmd)",
56- icon='PLUGIN'
57- )
58-
59-
60-'''
61-PMD EXPORTER
62-'''
63-class ExportPMD(bpy.types.Operator, ExportHelper):
64- '''Export to PMD file format (.pmd)'''
65- bl_idname = "export_scene.mmd_pmd"
66- bl_label = 'Export PMD'
67-
68- filename_ext = ".pmd"
69- filter_glob = StringProperty(default="*.pmd", options={'HIDDEN'})
70-
71- use_selection = BoolProperty(name="Selection Only", description="Export selected objects only", default=False)
72-
73- def execute(self, context):
74- from . import export_pmd
75- bl.initialize('pmd_export', context.scene)
76- export_pmd._execute(**self.as_keywords(
77- ignore=("check_existing", "filter_glob", "use_selection")))
78- bl.finalize()
79- return {'FINISHED'}
80-
81-def menu_pmd_export(self, context):
82- default_path=bpy.data.filepath.replace(".blend", ".pmd")
83- self.layout.operator(
84- ExportPMD.bl_idname,
85- text="Miku Miku Dance Model(.pmd)",
86- icon='PLUGIN'
87- ).filepath=default_path
88-
89-
90-'''
91-MQO IMPORTER
92-'''
93-class ImportMQO(bpy.types.Operator, ImportHelper):
94- '''Import from MQO file format (.mqo)'''
95- bl_idname = "import_scene.metasequioa_mqo"
96- bl_label = 'Import MQO'
97-
98- filename_ext = ".mqo"
99- filter_glob = StringProperty(default="*.mqo", options={'HIDDEN'})
100-
101- scale = bpy.props.FloatProperty(
102- name="Scale",
103- description="Scale the MQO by this value",
104- min=0.0001, max=1000000.0,
105- soft_min=0.001, soft_max=100.0, default=0.1)
106-
107- def execute(self, context):
108- from . import import_mqo
109- bl.initialize('mqo_import', context.scene)
110- import_mqo._execute(**self.as_keywords(
111- ignore=("filter_glob",)))
112- bl.finalize()
113- return {'FINISHED'}
114-
115-def menu_mqo_import(self, context):
116- self.layout.operator(
117- ImportMQO.bl_idname,
118- text="Metasequoia (.mqo)",
119- icon='PLUGIN'
120- )
121-
122-
123-'''
124-MQO EXPORTER
125-'''
126-class ExportMQO(bpy.types.Operator, ExportHelper):
127- '''Save a Metasequoia MQO file.'''
128- bl_idname = "export_scene.metasequioa_mqo"
129- bl_label = 'Export MQO'
130-
131- filename_ext = ".mqo"
132- filter_glob = StringProperty(default="*.mqo", options={'HIDDEN'})
133-
134- use_selection = BoolProperty(name="Selection Only", description="Export selected objects only", default=False)
135-
136- scale = bpy.props.FloatProperty(
137- name="Scale",
138- description="Scale the MQO by this value",
139- min=0.0001, max=1000000.0,
140- soft_min=0.001, soft_max=100.0, default=10.0)
141-
142- apply_modifier = bpy.props.BoolProperty(
143- name="ApplyModifier",
144- description="Would apply modifiers",
145- default=False)
146-
147- def execute(self, context):
148- from . import export_mqo
149- bl.initialize('mqo_export', context.scene)
150- export_mqo._execute(**self.as_keywords(
151- ignore=("check_existing", "filter_glob", "use_selection")))
152- bl.finalize()
153- return {'FINISHED'}
154-
155-def menu_mqo_export(self, context):
156- default_path=bpy.data.filepath.replace(".blend", ".mqo")
157- self.layout.operator(
158- ExportMQO.bl_idname,
159- text="Metasequoia (.mqo)",
160- icon='PLUGIN'
161- ).filepath=default_path
162-
163-
164-'''
165-REGISTER
166-'''
167-def register():
168- bpy.utils.register_module(__name__)
169- bpy.types.INFO_MT_file_import.append(menu_pmd_import)
170- bpy.types.INFO_MT_file_export.append(menu_pmd_export)
171- bpy.types.INFO_MT_file_import.append(menu_mqo_import)
172- bpy.types.INFO_MT_file_export.append(menu_mqo_export)
173-
174-def unregister():
175- bpy.utils.unregister_module(__name__)
176- bpy.types.INFO_MT_file_import.remove(menu_pmd_import)
177- bpy.types.INFO_MT_file_export.remove(menu_pmd_export)
178- bpy.types.INFO_MT_file_import.remove(menu_mqo_import)
179- bpy.types.INFO_MT_file_export.remove(menu_mqo_export)
180-
181-if __name__ == "__main__":
182- register()
183-
--- a/blender26-meshio/__init__.py
+++ b/blender26-meshio/__init__.py
@@ -2,56 +2,240 @@
22
33 bl_info={
44 'category': 'Import-Export',
5- 'name': 'extended MikuMikuDance model format(.pmx)',
5+ 'name': 'meshio. (.pmd)(.pmx)(.mqo)',
66 'author': 'ousttrue',
77 'blender': (2, 6, 0),
88 'location': 'File > Import-Export',
9- 'description': 'Import from the extended MikuMikuDance Model Format(.pmx)',
9+ 'description': 'Import-Export PMD/PMX/MQO meshes',
1010 'warning': '', # used for warning icon and text in addons panel
11- 'wiki_url': 'http://sourceforge.jp/projects/meshio/wiki/FrontPage',
11+ 'wiki_url': 'http://meshio.sourceforge.jp/',
12+ 'support': 'COMMUNITY',
1213 }
1314
14-if "bpy" in locals():
15- import imp
16- if "import_pmx" in locals():
17- imp.reaload(import_pmx)
1815
16+# To support reload properly, try to access a package var, if it's there, reload everything
17+if 'bpy' in locals():
18+ import imp
19+ def reload_module(name):
20+ if name in locals():
21+ imp.reaload(locals()[name])
22+ reload_module('import_pmx')
23+ reload_module('export_pmx')
24+ reload_module('import_pmd')
25+ reload_module('export_pmd')
26+ reload_module('import_mqo')
27+ reload_module('export_mqo')
28+
1929
2030 import bpy
21-import bpy_extras
31+import bpy_extras.io_utils
32+from . import bl
33+
34+
35+class ImportPmd(bpy.types.Operator, bpy_extras.io_utils.ImportHelper):
36+ '''Import from PMD file format (.pmd)'''
37+ bl_idname = 'import_scene.mmd_pmd'
38+ bl_label = 'Import PMD'
39+ bl_options={'UNDO'}
40+ filename_ext = '.pmd'
41+ filter_glob = bpy.props.StringProperty(
42+ default='*.pmd', options={'HIDDEN'})
43+
44+ def execute(self, context):
45+ from . import import_pmd
46+ bl.initialize('pmd_import', context.scene)
47+ import_pmd._execute(**self.as_keywords(
48+ ignore=('filter_glob',)))
49+ bl.finalize()
50+ return {'FINISHED'}
51+
52+ @classmethod
53+ def menu_func(klass, self, context):
54+ self.layout.operator(klass.bl_idname,
55+ text='MikuMikuDance model (.pmd)',
56+ icon='PLUGIN'
57+ )
2258
2359
24-class ImportPMX(bpy.types.Operator, bpy_extras.io_utils.ImportHelper):
25- '''Import from the extended MikuMikuDance Model Format(.pmx)'''
60+class ImportPmx(bpy.types.Operator, bpy_extras.io_utils.ImportHelper):
61+ '''Import from PMX Format(.pmx)'''
2662 bl_idname='import_scene.mmd_pmx'
2763 bl_label='Import PMX'
2864 bl_options={'UNDO'}
2965 filename_ext='.pmx'
3066 filter_glob=bpy.props.StringProperty(
31- default="*.pmx", options={'HIDDEN'})
32-
67+ default='*.pmx', options={'HIDDEN'})
3368
3469 def execute(self, context):
3570 from . import import_pmx
36- keywords=self.as_keywords()
37- return import_pmx.load(self, context, **keywords)
71+ bl.initialize('pmd_import', context.scene)
72+ import_pmx._execute(**self.as_keywords(
73+ ignore=('filter_glob',)))
74+ bl.finalize()
75+ return {'FINISHED'}
76+
77+ @classmethod
78+ def menu_func(klass, self, context):
79+ self.layout.operator(klass.bl_idname,
80+ text='MikuMikuDance model (.pmx)',
81+ icon='PLUGIN'
82+ )
83+
84+
85+class ImportMqo(bpy.types.Operator, bpy_extras.io_utils.ImportHelper):
86+ '''Import from MQO file format (.mqo)'''
87+ bl_idname = 'import_scene.metasequioa_mqo'
88+ bl_label = 'Import MQO'
89+ bl_options={'UNDO'}
90+ filename_ext = '.mqo'
91+ filter_glob = bpy.props.StringProperty(
92+ default='*.mqo', options={'HIDDEN'})
93+
94+ scale = bpy.props.FloatProperty(
95+ name='Scale',
96+ description='Scale the MQO by this value',
97+ min=0.0001, max=1000000.0,
98+ soft_min=0.001, soft_max=100.0, default=0.1)
99+
100+ def execute(self, context):
101+ from . import import_mqo
102+ bl.initialize('mqo_import', context.scene)
103+ import_mqo._execute(**self.as_keywords(
104+ ignore=('filter_glob',)))
105+ bl.finalize()
106+ return {'FINISHED'}
107+
108+ @classmethod
109+ def menu_func(klass, self, context):
110+ self.layout.operator(klass.bl_idname,
111+ text="Metasequoia (.mqo)",
112+ icon='PLUGIN'
113+ )
114+
115+
116+class ExportPmd(bpy.types.Operator, bpy_extras.io_utils.ExportHelper):
117+ '''Export to PMD file format (.pmd)'''
118+ bl_idname = 'export_scene.mmd_pmd'
119+ bl_label = 'Export PMD'
120+
121+ filename_ext = '.pmd'
122+ filter_glob = bpy.props.StringProperty(
123+ default='*.pmd', options={'HIDDEN'})
124+
125+ use_selection = bpy.props.BoolProperty(
126+ name='Selection Only',
127+ description='Export selected objects only',
128+ default=False)
129+
130+ def execute(self, context):
131+ from . import export_pmd
132+ bl.initialize('pmd_export', context.scene)
133+ export_pmd._execute(**self.as_keywords(
134+ ignore=('check_existing', 'filter_glob', 'use_selection')))
135+ bl.finalize()
136+ return {'FINISHED'}
137+
138+ @classmethod
139+ def menu_func(klass, self, context):
140+ default_path=bpy.data.filepath.replace('.blend', '.pmd')
141+ self.layout.operator(klass.bl_idname,
142+ text='Miku Miku Dance Model(.pmd)',
143+ icon='PLUGIN'
144+ ).filepath=default_path
145+
146+
147+class ExportPmx(bpy.types.Operator, bpy_extras.io_utils.ExportHelper):
148+ '''Export to PMX file format (.pmx)'''
149+ bl_idname = 'export_scene.mmd_pmx'
150+ bl_label = 'Export PMX'
151+
152+ filename_ext = '.pmx'
153+ filter_glob = bpy.props.StringProperty(
154+ default='*.pmx', options={'HIDDEN'})
155+
156+ use_selection = bpy.props.BoolProperty(
157+ name='Selection Only',
158+ description='Export selected objects only',
159+ default=False)
160+
161+ def execute(self, context):
162+ from . import export_pmx
163+ bl.initialize('pmx_export', context.scene)
164+ export_pmx._execute(**self.as_keywords(
165+ ignore=('check_existing', 'filter_glob', 'use_selection')))
166+ bl.finalize()
167+ return {'FINISHED'}
168+
169+ @classmethod
170+ def menu_func(klass, self, context):
171+ default_path=bpy.data.filepath.replace('.blend', '.pmx')
172+ self.layout.operator(klass.bl_idname,
173+ text='Miku Miku Dance Model(.pmx)',
174+ icon='PLUGIN'
175+ ).filepath=default_path
176+
177+
178+class ExportMqo(bpy.types.Operator, bpy_extras.io_utils.ExportHelper):
179+ '''Save a Metasequoia MQO file.'''
180+ bl_idname = 'export_scene.metasequioa_mqo'
181+ bl_label = 'Export MQO'
182+
183+ filename_ext = '.mqo'
184+ filter_glob = bpy.props.StringProperty(
185+ default='*.mqo', options={'HIDDEN'})
186+
187+ use_selection = bpy.props.BoolProperty(
188+ name='Selection Only',
189+ description='Export selected objects only',
190+ default=False)
191+
192+ scale = bpy.props.FloatProperty(
193+ name='Scale',
194+ description='Scale the MQO by this value',
195+ min=0.0001, max=1000000.0,
196+ soft_min=0.001, soft_max=100.0, default=10.0)
197+
198+ apply_modifier = bpy.props.BoolProperty(
199+ name='ApplyModifier',
200+ description='Would apply modifiers',
201+ default=False)
202+
203+ def execute(self, context):
204+ from . import export_mqo
205+ bl.initialize('mqo_export', context.scene)
206+ export_mqo._execute(**self.as_keywords(
207+ ignore=('check_existing', 'filter_glob', 'use_selection')))
208+ bl.finalize()
209+ return {'FINISHED'}
38210
211+ @classmethod
212+ def menu_func(klass, self, context):
213+ default_path=bpy.data.filepath.replace('.blend', '.mqo')
214+ self.layout.operator(klass.bl_idname,
215+ text='Metasequoia (.mqo)',
216+ icon='PLUGIN'
217+ ).filepath=default_path
39218
40-def menu_func_import(self, context):
41- self.layout.operator(ImportPMX.bl_idname,
42- text="MikuMikuDance model (.pmx)",
43- icon='PLUGIN'
44- )
45219
46220 def register():
47221 bpy.utils.register_module(__name__)
48- bpy.types.INFO_MT_file_import.append(menu_func_import)
222+ bpy.types.INFO_MT_file_import.append(ImportPmd.menu_func)
223+ bpy.types.INFO_MT_file_import.append(ImportPmx.menu_func)
224+ bpy.types.INFO_MT_file_import.append(ImportMqo.menu_func)
225+ bpy.types.INFO_MT_file_export.append(ExportPmd.menu_func)
226+ bpy.types.INFO_MT_file_export.append(ExportPmx.menu_func)
227+ bpy.types.INFO_MT_file_export.append(ExportMqo.menu_func)
49228
50229 def unregister():
51230 bpy.utils.unregister_module(__name__)
52- bpy.types.INFO_MT_file_import.remove(menu_func_import)
231+ bpy.types.INFO_MT_file_import.remove(ImportPmd.menu_func)
232+ bpy.types.INFO_MT_file_import.remove(ImportPmx.menu_func)
233+ bpy.types.INFO_MT_file_import.remove(ImportMqo.menu_func)
234+ bpy.types.INFO_MT_file_export.remove(ExportPmd.menu_func)
235+ bpy.types.INFO_MT_file_export.remove(ExportPmx.menu_func)
236+ bpy.types.INFO_MT_file_export.remove(ExportMqo.menu_func)
53237
54238
55-if __name__=="__main__":
239+if __name__=='__main__':
56240 register()
57241
similarity index 99%
rename from blender25-meshio/bl25.py
rename to blender26-meshio/bl.py
index 20a647bd6b07336f898512a308c96183d6d0de79..c7353ec7c7c3c70fc6fb24d5f7ffe7d3d6d2dcd5
--- a/blender25-meshio/bl25.py
+++ b/blender26-meshio/bl.py
@@ -473,7 +473,7 @@ class mesh:
473473 uv_face.uv=uv_array
474474 if image:
475475 uv_face.image=image
476- uv_face.use_image=True
476+ #uv_face.use_image=True
477477
478478 @staticmethod
479479 def vertsDelete(m, remove_vertices):
similarity index 99%
rename from blender25-meshio/export_mqo.py
rename to blender26-meshio/export_mqo.py
index fbe3a77b2cf399082cfc14983d36dca22ef7bc9f..7d8ed4b29b65d728db83ea188d23f76bd5f6d6f1
--- a/blender25-meshio/export_mqo.py
+++ b/blender26-meshio/export_mqo.py
@@ -72,7 +72,7 @@ class MQOMaterial(object):
7272 import bpy
7373
7474 # wrapper
75-from . import bl25 as bl
75+from . import bl
7676
7777 def materialToMqo(m):
7878 material=MQOMaterial(m.name, 3)
similarity index 99%
rename from blender25-meshio/export_pmd.py
rename to blender26-meshio/export_pmd.py
index f3ad899862c8e7747666b71832e9a9932b4c1482..dd63db6617abeeb89260c3be9d750d468719950f
--- a/blender25-meshio/export_pmd.py
+++ b/blender26-meshio/export_pmd.py
@@ -66,7 +66,7 @@ import bpy
6666 import mathutils
6767
6868 # wrapper
69-from . import bl25 as bl
69+from . import bl
7070
7171 xrange=range
7272
--- /dev/null
+++ b/blender26-meshio/export_pmx.py
@@ -0,0 +1,5 @@
1+# coding: utf-8
2+
3+def write(self, path):
4+ pass
5+
similarity index 96%
rename from blender25-meshio/import_mqo.py
rename to blender26-meshio/import_mqo.py
index 457bc28919286f873dadb1073800c48085eaf689..a75563266bae184b73610e502f73289e8c2c319d
--- a/blender25-meshio/import_mqo.py
+++ b/blender26-meshio/import_mqo.py
@@ -1,609 +1,609 @@
1-#!BPY
2-# coding: utf-8
3-"""
4-Name: 'Metasequoia(.mqo)...'
5-Blender: 245
6-Group: 'Import'
7-Tooltip: 'Import from Metasequoia file format (.mqo)'
8-"""
9-__author__=['ousttrue']
10-__url__ = ["http://gunload.web.fc2.com/blender/"]
11-__bpydoc__= '''\
12-
13-MQO Importer
14-
15-This script imports a mqo into Blender for editing.
16-
17-20080123: update.
18-20091125: modify for linux.
19-20100310: rewrite.
20-20100311: create armature from mikoto bone.
21-20100505: C extension.
22-20100606: integrate 2.4 and 2.5.
23-20100619: fix multibyte object name.
24-20100626: refactoring.
25-20100724: update for Blender2.53.
26-20100731: add full python module.
27-20101005: update for Blender2.54.
28-20101228: update for Blender2.55.
29-20110429: update for Blender2.57b.
30-20110918: update for Blender2.59.
31-20111002: update for pymeshio-2.1.0
32-'''
33-
34-bl_addon_info = {
35- 'category': 'Import/Export',
36- 'name': 'Import: Metasequioa Model Format (.mqo)',
37- 'author': 'ousttrue',
38- 'version': (2, 0),
39- 'blender': (2, 5, 3),
40- 'location': 'File > Import',
41- 'description': 'Import from the Metasequioa Model Format (.mqo)',
42- 'warning': '', # used for warning icon and text in addons panel
43- 'wiki_url': 'http://sourceforge.jp/projects/meshio/wiki/FrontPage',
44- 'tracker_url': 'http://sourceforge.jp/ticket/newticket.php?group_id=5081',
45- }
46-
47-import os
48-import sys
49-from .pymeshio.mqo import reader
50-
51-# for 2.5
52-import bpy
53-
54-# wrapper
55-from . import bl25 as bl
56-
57-def createMqoMaterial(m):
58- material = bpy.data.materials.new(m.name.decode("cp932"))
59- # shader
60- if m.shader==1:
61- material.diffuse_shader='FRESNEL'
62- else:
63- material.diffuse_shader='LAMBERT'
64- # diffuse
65- material.diffuse_color=[m.color.r, m.color.g, m.color.b]
66- material.diffuse_intensity=m.diffuse
67- material.alpha=m.color.a
68- # other
69- material.ambient = m.ambient
70- #material.specular = m.specular
71- material.emit=m.emit
72- material.use_shadeless=True
73- return material
74-
75-
76-def has_mikoto(mqo):
77- return False
78-
79-
80-def __createMaterials(mqo, directory):
81- """
82- create blender materials and renturn material list.
83- """
84- materials = []
85- textureMap={}
86- imageMap={}
87- if len(mqo.materials)>0:
88- for material_index, m in enumerate(mqo.materials):
89- # material
90- material=createMqoMaterial(m)
91- materials.append(material)
92- # texture
93- texture_name=m.tex.decode("cp932")
94- if texture_name!=b'':
95- if texture_name in textureMap:
96- texture=textureMap[texture_name]
97- else:
98- # load texture image
99- if os.path.isabs(texture_name):
100- # absolute
101- path = texture_name
102- else:
103- # relative
104- path = os.path.join(directory, texture_name)
105- # texture
106- path=path.replace("\\", "/")
107- if os.path.exists(path):
108- print("create texture:", path)
109- texture, image=bl.texture.create(path)
110- textureMap[texture_name]=texture
111- imageMap[material_index]=image
112- else:
113- print("%s not exits" % path)
114- continue
115- bl.material.addTexture(material, texture)
116- else:
117- # default material
118- pass
119- return materials, imageMap
120-
121-
122-def __createObjects(mqo, root, materials, imageMap, scale):
123- """
124- create blender mesh objects.
125- """
126- # tree stack
127- stack=[root]
128- objects=[]
129- for o in mqo.objects:
130- mesh, mesh_object=bl.mesh.create(o.name.decode("cp932"))
131-
132- # add hierarchy
133- stack_depth=len(stack)-1
134- #print(o.depth, stack_depth)
135- if o.depth<stack_depth:
136- for i in range(stack_depth-o.depth):
137- stack.pop()
138- bl.object.makeParent(stack[-1], mesh_object)
139- stack.append(mesh_object)
140-
141- obj_name=o.name.decode("cp932")
142- if obj_name.startswith('sdef'):
143- objects.append(mesh_object)
144- elif obj_name.startswith('anchor'):
145- bl.object.setLayerMask(mesh_object, [0, 1])
146- elif obj_name.startswith('bone'):
147- bl.object.setLayerMask(mesh_object, [0, 1])
148-
149- # geometry
150- vertices=[(v.x * scale, -v.z * scale, v.y * scale) for v in o.vertices]
151- faces=[]
152- materialMap={}
153- for f in o.faces:
154- face_indices=[]
155- # flip face
156- for i in reversed(range(f.index_count)):
157- face_indices.append(f.getIndex(i))
158- faces.append(face_indices)
159- materialMap[f.material_index]=True
160- bl.mesh.addGeometry(mesh, vertices, faces)
161-
162- # blender limits 16 materials per mesh
163- for i, material_index in enumerate(materialMap.keys()):
164- if i>=16:
165- # split a mesh ?
166- print("over 16 materials!")
167- break
168- bl.mesh.addMaterial(mesh, materials[material_index])
169- materialMap[material_index]=i
170-
171- # set face params
172- assert(len(o.faces)==len(mesh.faces))
173- bl.mesh.addUV(mesh)
174- for i, (f, face) in enumerate(zip(o.faces, mesh.faces)):
175- uv_array=[]
176- # ToDo FIX
177- # flip face
178- for j in reversed(range(f.index_count)):
179- uv_array.append((f.getUV(j).x, 1.0-f.getUV(j).y))
180- bl.mesh.setFaceUV(mesh, i, face, uv_array,
181- imageMap.get(f.material_index, None))
182- if f.material_index in materialMap:
183- bl.face.setMaterial(face, materialMap[f.material_index])
184- bl.face.setSmooth(face, True)
185-
186- # mirror modifier
187- if o.mirror:
188- bl.modifier.addMirror(mesh_object)
189-
190- # set smoothing
191- bl.mesh.setSmooth(mesh, o.smoothing)
192-
193- # calc normal
194- bl.mesh.recalcNormals(mesh_object)
195-
196- return objects
197-
198-
199-###############################################################################
200-# for mqo mikoto bone.
201-###############################################################################
202-class MikotoBone(object):
203- __slots__=[
204- 'name',
205- 'iHead', 'iTail', 'iUp',
206- 'vHead', 'vTail', 'vUp',
207- 'parent', 'isFloating',
208- 'children',
209- ]
210- def __init__(self, face=None, vertices=None, materials=None):
211- self.parent=None
212- self.isFloating=False
213- self.children=[]
214- if not face:
215- self.name='root'
216- return
217-
218- self.name=materials[face.material_index].name.encode('utf-8')
219-
220- i0=face.getIndex(0)
221- i1=face.getIndex(1)
222- i2=face.getIndex(2)
223- v0=vertices[i0]
224- v1=vertices[i1]
225- v2=vertices[i2]
226- e01=v1-v0
227- e12=v2-v1
228- e20=v0-v2
229- sqNorm0=e01.getSqNorm()
230- sqNorm1=e12.getSqNorm()
231- sqNorm2=e20.getSqNorm()
232- if sqNorm0>sqNorm1:
233- if sqNorm1>sqNorm2:
234- # e01 > e12 > e20
235- self.iHead=i2
236- self.iTail=i1
237- self.iUp=i0
238- else:
239- if sqNorm0>sqNorm2:
240- # e01 > e20 > e12
241- self.iHead=i2
242- self.iTail=i0
243- self.iUp=i1
244- else:
245- # e20 > e01 > e12
246- self.iHead=i1
247- self.iTail=i0
248- self.iUp=i2
249- else:
250- # 0 < 1
251- if sqNorm1<sqNorm2:
252- # e20 > e12 > e01
253- self.iHead=i1
254- self.iTail=i2
255- self.iUp=i0
256- else:
257- if sqNorm0<sqNorm2:
258- # e12 > e20 > e01
259- self.iHead=i0
260- self.iTail=i2
261- self.iUp=i1
262- else:
263- # e12 > e01 > e20
264- self.iHead=i0
265- self.iTail=i1
266- self.iUp=i2
267- self.vHead=vertices[self.iHead]
268- self.vTail=vertices[self.iTail]
269- self.vUp=vertices[self.iUp]
270-
271- if self.name.endswith('[]'):
272- basename=self.name[0:-2]
273- # expand LR name
274- if self.vTail.x>0:
275- self.name="%s_L" % basename
276- else:
277- self.name="%s_R" % basename
278-
279-
280- def setParent(self, parent, floating=False):
281- if floating:
282- self.isFloating=True
283- self.parent=parent
284- parent.children.append(self)
285-
286- def printTree(self, indent=''):
287- print("%s%s" % (indent, self.name))
288- for child in self.children:
289- child.printTree(indent+' ')
290-
291-
292-def build_armature(armature, mikotoBone, parent=None):
293- """
294- create a armature bone.
295- """
296- bone = Armature.Editbone()
297- bone.name = mikotoBone.name.encode('utf-8')
298- armature.bones[bone.name] = bone
299-
300- bone.head = Mathutils.Vector(*mikotoBone.vHead.to_a())
301- bone.tail = Mathutils.Vector(*mikotoBone.vTail.to_a())
302- if parent:
303- bone.parent=parent
304- if mikotoBone.isFloating:
305- pass
306- else:
307- bone.options=[Armature.CONNECTED]
308-
309- for child in mikotoBone.children:
310- build_armature(armature, child, bone)
311-
312-
313-def create_armature(mqo):
314- """
315- create armature
316- """
317- boneObject=None
318- for o in mqo.objects:
319- if o.name.startswith('bone'):
320- boneObject=o
321- break
322- if not boneObject:
323- return
324-
325- tailMap={}
326- for f in boneObject.faces:
327- if f.index_count!=3:
328- print("invalid index_count: %d" % f.index_count)
329- continue
330- b=MikotoBone(f, boneObject.vertices, mqo.materials)
331- tailMap[b.iTail]=b
332-
333- ####################
334- # build mikoto bone tree
335- ####################
336- mikotoRoot=MikotoBone()
337-
338- for b in tailMap.values():
339- # each bone has unique parent or is root bone.
340- if b.iHead in tailMap:
341- b.setParent(tailMap[b.iHead])
342- else:
343- isFloating=False
344- for e in boneObject.edges:
345- if b.iHead==e.indices[0]:
346- # floating bone
347- if e.indices[1] in tailMap:
348- b.setParent(tailMap[e.indices[1]], True)
349- isFloating=True
350- break
351- elif b.iHead==e.indices[1]:
352- # floating bone
353- if e.indices[0] in tailMap:
354- b.setParent(tailMap[e.indices[0]], True)
355- isFloating=True
356- break
357- if isFloating:
358- continue
359-
360- # no parent bone
361- b.setParent(mikotoRoot, True)
362-
363- if len(mikotoRoot.children)==0:
364- print("no root bone")
365- return
366-
367- if len(mikotoRoot.children)==1:
368- # single root
369- mikotoRoot=mikotoRoot.children[0]
370- mikotoRoot.parent=None
371- else:
372- mikotoRoot.vHead=Vector3(0, 10, 0)
373- mikotoRoot.vTail=Vector3(0, 0, 0)
374-
375- ####################
376- # create armature
377- ####################
378- armature = Armature.New()
379- # link to object
380- armature_object = scene.objects.new(armature)
381- # create action
382- act = Armature.NLA.NewAction()
383- act.setActive(armature_object)
384- # set XRAY
385- armature_object.drawMode |= Object.DrawModes.XRAY
386- # armature settings
387- armature.drawType = Armature.OCTAHEDRON
388- armature.envelopes = False
389- armature.vertexGroups = True
390- armature.mirrorEdit = True
391- armature.drawNames=True
392-
393- # edit bones
394- armature.makeEditable()
395- build_armature(armature, mikotoRoot)
396- armature.update()
397-
398- return armature_object
399-
400-
401-class TrianglePlane(object):
402- """
403- mikoto方式ボーンのアンカーウェイト計算用。
404- (不完全)
405- """
406- __slots__=['normal',
407- 'v0', 'v1', 'v2',
408- ]
409- def __init__(self, v0, v1, v2):
410- self.v0=v0
411- self.v1=v1
412- self.v2=v2
413-
414- def isInsideXY(self, p):
415- v0=Vector2(self.v0.x, self.v0.y)
416- v1=Vector2(self.v1.x, self.v1.y)
417- v2=Vector2(self.v2.x, self.v2.y)
418- e01=v1-v0
419- e12=v2-v1
420- e20=v0-v2
421- c0=Vector2.cross(e01, p-v0)
422- c1=Vector2.cross(e12, p-v1)
423- c2=Vector2.cross(e20, p-v2)
424- if c0>=0 and c1>=0 and c2>=0:
425- return True
426- if c0<=0 and c1<=0 and c2<=0:
427- return True
428-
429- def isInsideYZ(self, p):
430- v0=Vector2(self.v0.y, self.v0.z)
431- v1=Vector2(self.v1.y, self.v1.z)
432- v2=Vector2(self.v2.y, self.v2.z)
433- e01=v1-v0
434- e12=v2-v1
435- e20=v0-v2
436- c0=Vector2.cross(e01, p-v0)
437- c1=Vector2.cross(e12, p-v1)
438- c2=Vector2.cross(e20, p-v2)
439- if c0>=0 and c1>=0 and c2>=0:
440- return True
441- if c0<=0 and c1<=0 and c2<=0:
442- return True
443-
444- def isInsideZX(self, p):
445- v0=Vector2(self.v0.z, self.v0.x)
446- v1=Vector2(self.v1.z, self.v1.x)
447- v2=Vector2(self.v2.z, self.v2.x)
448- e01=v1-v0
449- e12=v2-v1
450- e20=v0-v2
451- c0=Vector2.cross(e01, p-v0)
452- c1=Vector2.cross(e12, p-v1)
453- c2=Vector2.cross(e20, p-v2)
454- if c0>=0 and c1>=0 and c2>=0:
455- return True
456- if c0<=0 and c1<=0 and c2<=0:
457- return True
458-
459-
460-class MikotoAnchor(object):
461- """
462- mikoto方式スケルトンのアンカー。
463- """
464- __slots__=[
465- "triangles", "bbox",
466- ]
467- def __init__(self):
468- self.triangles=[]
469- self.bbox=None
470-
471- def push(self, face, vertices):
472- if face.index_count==3:
473- self.triangles.append(TrianglePlane(
474- vertices[face.indices[0]],
475- vertices[face.indices[1]],
476- vertices[face.indices[2]]
477- ))
478- elif face.index_count==4:
479- self.triangles.append(TrianglePlane(
480- vertices[face.indices[0]],
481- vertices[face.indices[1]],
482- vertices[face.indices[2]]
483- ))
484- self.triangles.append(TrianglePlane(
485- vertices[face.indices[2]],
486- vertices[face.indices[3]],
487- vertices[face.indices[0]]
488- ))
489- # bounding box
490- if not self.bbox:
491- self.bbox=BoundingBox(vertices[face.indices[0]])
492- for i in face.indices:
493- self.bbox.expand(vertices[i])
494-
495-
496- def calcWeight(self, v):
497- if not self.bbox.isInside(v):
498- return 0
499-
500- if self.anyXY(v.x, v.y) and self.anyYZ(v.y, v.z) and self.anyZX(v.z, v.x):
501- return 1.0
502- else:
503- return 0
504-
505- def anyXY(self, x, y):
506- for t in self.triangles:
507- if t.isInsideXY(Vector2(x, y)):
508- return True
509- return False
510-
511- def anyYZ(self, y, z):
512- for t in self.triangles:
513- if t.isInsideYZ(Vector2(y, z)):
514- return True
515- return False
516-
517- def anyZX(self, z, x):
518- for t in self.triangles:
519- if t.isInsideZX(Vector2(z, x)):
520- return True
521- return False
522-
523-
524-def create_bone_weight(scene, mqo, armature_object, objects):
525- """
526- create mikoto bone weight.
527- """
528- anchorMap={}
529- # setup mikoto anchors
530- for o in mqo.objects:
531- if o.name.startswith("anchor"):
532- for f in o.faces:
533- name=mqo.materials[f.material_index].name
534- if name.endswith('[]'):
535- basename=name[0:-2]
536- v=o.vertices[f.indices[0]]
537- if(v.x>0):
538- # L
539- name_L=basename+'_L'
540- if not name_L in anchorMap:
541- anchorMap[name_L]=MikotoAnchor()
542- anchorMap[name_L].push(f, o.vertices)
543- elif(v.x<0):
544- # R
545- name_R=basename+'_R'
546- if not name_R in anchorMap:
547- anchorMap[name_R]=MikotoAnchor()
548- anchorMap[name_R].push(f, o.vertices)
549- else:
550- print("no side", v)
551- else:
552- if not name in anchorMap:
553- anchorMap[name]=MikotoAnchor()
554- anchorMap[name].push(f, o.vertices)
555-
556- for o in objects:
557- # add armature modifier
558- mod=o.modifiers.append(Modifier.Types.ARMATURE)
559- mod[Modifier.Settings.OBJECT] = armature_object
560- mod[Modifier.Settings.ENVELOPES] = False
561- o.makeDisplayList()
562- # create vertex group
563- mesh=o.getData(mesh=True)
564- for name in anchorMap.keys():
565- mesh.addVertGroup(name)
566- mesh.update()
567-
568- # assing vertices to vertex group
569- for o in objects:
570- mesh=o.getData(mesh=True)
571- for i, mvert in enumerate(mesh.verts):
572- hasWeight=False
573- for name, anchor in anchorMap.items():
574- weight=anchor.calcWeight(mvert.co)
575- if weight>0:
576- mesh.assignVertsToGroup(
577- name, [i], weight, Mesh.AssignModes.ADD)
578- hasWeight=True
579- if not hasWeight:
580- # debug orphan vertex
581- print('orphan', mvert)
582- mesh.update()
583-
584-
585-def _execute(filepath='', scale=0.1):
586- # read mqo model
587- model=reader.read_from_file(filepath)
588- if not model:
589- bl.message("fail to load %s" % filepath)
590- return
591-
592- # create materials
593- materials, imageMap=__createMaterials(model, os.path.dirname(filepath))
594- if len(materials)==0:
595- materials.append(bl.material.create('default'))
596-
597- # create objects
598- root=bl.object.createEmpty(os.path.basename(filepath))
599- objects=__createObjects(model, root, materials, imageMap, scale)
600-
601- if has_mikoto(model):
602- # create mikoto bone
603- armature_object=create_armature(model)
604- if armature_object:
605- root.makeParent([armature_object])
606-
607- # create bone weight
608- create_bone_weight(model, armature_object, objects)
609-
1+#!BPY
2+# coding: utf-8
3+"""
4+Name: 'Metasequoia(.mqo)...'
5+Blender: 245
6+Group: 'Import'
7+Tooltip: 'Import from Metasequoia file format (.mqo)'
8+"""
9+__author__=['ousttrue']
10+__url__ = ["http://gunload.web.fc2.com/blender/"]
11+__bpydoc__= '''\
12+
13+MQO Importer
14+
15+This script imports a mqo into Blender for editing.
16+
17+20080123: update.
18+20091125: modify for linux.
19+20100310: rewrite.
20+20100311: create armature from mikoto bone.
21+20100505: C extension.
22+20100606: integrate 2.4 and 2.5.
23+20100619: fix multibyte object name.
24+20100626: refactoring.
25+20100724: update for Blender2.53.
26+20100731: add full python module.
27+20101005: update for Blender2.54.
28+20101228: update for Blender2.55.
29+20110429: update for Blender2.57b.
30+20110918: update for Blender2.59.
31+20111002: update for pymeshio-2.1.0
32+'''
33+
34+bl_addon_info = {
35+ 'category': 'Import/Export',
36+ 'name': 'Import: Metasequioa Model Format (.mqo)',
37+ 'author': 'ousttrue',
38+ 'version': (2, 0),
39+ 'blender': (2, 5, 3),
40+ 'location': 'File > Import',
41+ 'description': 'Import from the Metasequioa Model Format (.mqo)',
42+ 'warning': '', # used for warning icon and text in addons panel
43+ 'wiki_url': 'http://sourceforge.jp/projects/meshio/wiki/FrontPage',
44+ 'tracker_url': 'http://sourceforge.jp/ticket/newticket.php?group_id=5081',
45+ }
46+
47+import os
48+import sys
49+from .pymeshio.mqo import reader
50+
51+# for 2.5
52+import bpy
53+
54+# wrapper
55+from . import bl
56+
57+def createMqoMaterial(m):
58+ material = bpy.data.materials.new(m.name.decode("cp932"))
59+ # shader
60+ if m.shader==1:
61+ material.diffuse_shader='FRESNEL'
62+ else:
63+ material.diffuse_shader='LAMBERT'
64+ # diffuse
65+ material.diffuse_color=[m.color.r, m.color.g, m.color.b]
66+ material.diffuse_intensity=m.diffuse
67+ material.alpha=m.color.a
68+ # other
69+ material.ambient = m.ambient
70+ #material.specular = m.specular
71+ material.emit=m.emit
72+ material.use_shadeless=True
73+ return material
74+
75+
76+def has_mikoto(mqo):
77+ return False
78+
79+
80+def __createMaterials(mqo, directory):
81+ """
82+ create blender materials and renturn material list.
83+ """
84+ materials = []
85+ textureMap={}
86+ imageMap={}
87+ if len(mqo.materials)>0:
88+ for material_index, m in enumerate(mqo.materials):
89+ # material
90+ material=createMqoMaterial(m)
91+ materials.append(material)
92+ # texture
93+ texture_name=m.tex.decode("cp932")
94+ if texture_name!=b'':
95+ if texture_name in textureMap:
96+ texture=textureMap[texture_name]
97+ else:
98+ # load texture image
99+ if os.path.isabs(texture_name):
100+ # absolute
101+ path = texture_name
102+ else:
103+ # relative
104+ path = os.path.join(directory, texture_name)
105+ # texture
106+ path=path.replace("\\", "/")
107+ if os.path.exists(path):
108+ print("create texture:", path)
109+ texture, image=bl.texture.create(path)
110+ textureMap[texture_name]=texture
111+ imageMap[material_index]=image
112+ else:
113+ print("%s not exits" % path)
114+ continue
115+ bl.material.addTexture(material, texture)
116+ else:
117+ # default material
118+ pass
119+ return materials, imageMap
120+
121+
122+def __createObjects(mqo, root, materials, imageMap, scale):
123+ """
124+ create blender mesh objects.
125+ """
126+ # tree stack
127+ stack=[root]
128+ objects=[]
129+ for o in mqo.objects:
130+ mesh, mesh_object=bl.mesh.create(o.name.decode("cp932"))
131+
132+ # add hierarchy
133+ stack_depth=len(stack)-1
134+ #print(o.depth, stack_depth)
135+ if o.depth<stack_depth:
136+ for i in range(stack_depth-o.depth):
137+ stack.pop()
138+ bl.object.makeParent(stack[-1], mesh_object)
139+ stack.append(mesh_object)
140+
141+ obj_name=o.name.decode("cp932")
142+ if obj_name.startswith('sdef'):
143+ objects.append(mesh_object)
144+ elif obj_name.startswith('anchor'):
145+ bl.object.setLayerMask(mesh_object, [0, 1])
146+ elif obj_name.startswith('bone'):
147+ bl.object.setLayerMask(mesh_object, [0, 1])
148+
149+ # geometry
150+ vertices=[(v.x * scale, -v.z * scale, v.y * scale) for v in o.vertices]
151+ faces=[]
152+ materialMap={}
153+ for f in o.faces:
154+ face_indices=[]
155+ # flip face
156+ for i in reversed(range(f.index_count)):
157+ face_indices.append(f.getIndex(i))
158+ faces.append(face_indices)
159+ materialMap[f.material_index]=True
160+ bl.mesh.addGeometry(mesh, vertices, faces)
161+
162+ # blender limits 16 materials per mesh
163+ for i, material_index in enumerate(materialMap.keys()):
164+ if i>=16:
165+ # split a mesh ?
166+ print("over 16 materials!")
167+ break
168+ bl.mesh.addMaterial(mesh, materials[material_index])
169+ materialMap[material_index]=i
170+
171+ # set face params
172+ assert(len(o.faces)==len(mesh.faces))
173+ bl.mesh.addUV(mesh)
174+ for i, (f, face) in enumerate(zip(o.faces, mesh.faces)):
175+ uv_array=[]
176+ # ToDo FIX
177+ # flip face
178+ for j in reversed(range(f.index_count)):
179+ uv_array.append((f.getUV(j).x, 1.0-f.getUV(j).y))
180+ bl.mesh.setFaceUV(mesh, i, face, uv_array,
181+ imageMap.get(f.material_index, None))
182+ if f.material_index in materialMap:
183+ bl.face.setMaterial(face, materialMap[f.material_index])
184+ bl.face.setSmooth(face, True)
185+
186+ # mirror modifier
187+ if o.mirror:
188+ bl.modifier.addMirror(mesh_object)
189+
190+ # set smoothing
191+ bl.mesh.setSmooth(mesh, o.smoothing)
192+
193+ # calc normal
194+ bl.mesh.recalcNormals(mesh_object)
195+
196+ return objects
197+
198+
199+###############################################################################
200+# for mqo mikoto bone.
201+###############################################################################
202+class MikotoBone(object):
203+ __slots__=[
204+ 'name',
205+ 'iHead', 'iTail', 'iUp',
206+ 'vHead', 'vTail', 'vUp',
207+ 'parent', 'isFloating',
208+ 'children',
209+ ]
210+ def __init__(self, face=None, vertices=None, materials=None):
211+ self.parent=None
212+ self.isFloating=False
213+ self.children=[]
214+ if not face:
215+ self.name='root'
216+ return
217+
218+ self.name=materials[face.material_index].name.encode('utf-8')
219+
220+ i0=face.getIndex(0)
221+ i1=face.getIndex(1)
222+ i2=face.getIndex(2)
223+ v0=vertices[i0]
224+ v1=vertices[i1]
225+ v2=vertices[i2]
226+ e01=v1-v0
227+ e12=v2-v1
228+ e20=v0-v2
229+ sqNorm0=e01.getSqNorm()
230+ sqNorm1=e12.getSqNorm()
231+ sqNorm2=e20.getSqNorm()
232+ if sqNorm0>sqNorm1:
233+ if sqNorm1>sqNorm2:
234+ # e01 > e12 > e20
235+ self.iHead=i2
236+ self.iTail=i1
237+ self.iUp=i0
238+ else:
239+ if sqNorm0>sqNorm2:
240+ # e01 > e20 > e12
241+ self.iHead=i2
242+ self.iTail=i0
243+ self.iUp=i1
244+ else:
245+ # e20 > e01 > e12
246+ self.iHead=i1
247+ self.iTail=i0
248+ self.iUp=i2
249+ else:
250+ # 0 < 1
251+ if sqNorm1<sqNorm2:
252+ # e20 > e12 > e01
253+ self.iHead=i1
254+ self.iTail=i2
255+ self.iUp=i0
256+ else:
257+ if sqNorm0<sqNorm2:
258+ # e12 > e20 > e01
259+ self.iHead=i0
260+ self.iTail=i2
261+ self.iUp=i1
262+ else:
263+ # e12 > e01 > e20
264+ self.iHead=i0
265+ self.iTail=i1
266+ self.iUp=i2
267+ self.vHead=vertices[self.iHead]
268+ self.vTail=vertices[self.iTail]
269+ self.vUp=vertices[self.iUp]
270+
271+ if self.name.endswith('[]'):
272+ basename=self.name[0:-2]
273+ # expand LR name
274+ if self.vTail.x>0:
275+ self.name="%s_L" % basename
276+ else:
277+ self.name="%s_R" % basename
278+
279+
280+ def setParent(self, parent, floating=False):
281+ if floating:
282+ self.isFloating=True
283+ self.parent=parent
284+ parent.children.append(self)
285+
286+ def printTree(self, indent=''):
287+ print("%s%s" % (indent, self.name))
288+ for child in self.children:
289+ child.printTree(indent+' ')
290+
291+
292+def build_armature(armature, mikotoBone, parent=None):
293+ """
294+ create a armature bone.
295+ """
296+ bone = Armature.Editbone()
297+ bone.name = mikotoBone.name.encode('utf-8')
298+ armature.bones[bone.name] = bone
299+
300+ bone.head = Mathutils.Vector(*mikotoBone.vHead.to_a())
301+ bone.tail = Mathutils.Vector(*mikotoBone.vTail.to_a())
302+ if parent:
303+ bone.parent=parent
304+ if mikotoBone.isFloating:
305+ pass
306+ else:
307+ bone.options=[Armature.CONNECTED]
308+
309+ for child in mikotoBone.children:
310+ build_armature(armature, child, bone)
311+
312+
313+def create_armature(mqo):
314+ """
315+ create armature
316+ """
317+ boneObject=None
318+ for o in mqo.objects:
319+ if o.name.startswith('bone'):
320+ boneObject=o
321+ break
322+ if not boneObject:
323+ return
324+
325+ tailMap={}
326+ for f in boneObject.faces:
327+ if f.index_count!=3:
328+ print("invalid index_count: %d" % f.index_count)
329+ continue
330+ b=MikotoBone(f, boneObject.vertices, mqo.materials)
331+ tailMap[b.iTail]=b
332+
333+ ####################
334+ # build mikoto bone tree
335+ ####################
336+ mikotoRoot=MikotoBone()
337+
338+ for b in tailMap.values():
339+ # each bone has unique parent or is root bone.
340+ if b.iHead in tailMap:
341+ b.setParent(tailMap[b.iHead])
342+ else:
343+ isFloating=False
344+ for e in boneObject.edges:
345+ if b.iHead==e.indices[0]:
346+ # floating bone
347+ if e.indices[1] in tailMap:
348+ b.setParent(tailMap[e.indices[1]], True)
349+ isFloating=True
350+ break
351+ elif b.iHead==e.indices[1]:
352+ # floating bone
353+ if e.indices[0] in tailMap:
354+ b.setParent(tailMap[e.indices[0]], True)
355+ isFloating=True
356+ break
357+ if isFloating:
358+ continue
359+
360+ # no parent bone
361+ b.setParent(mikotoRoot, True)
362+
363+ if len(mikotoRoot.children)==0:
364+ print("no root bone")
365+ return
366+
367+ if len(mikotoRoot.children)==1:
368+ # single root
369+ mikotoRoot=mikotoRoot.children[0]
370+ mikotoRoot.parent=None
371+ else:
372+ mikotoRoot.vHead=Vector3(0, 10, 0)
373+ mikotoRoot.vTail=Vector3(0, 0, 0)
374+
375+ ####################
376+ # create armature
377+ ####################
378+ armature = Armature.New()
379+ # link to object
380+ armature_object = scene.objects.new(armature)
381+ # create action
382+ act = Armature.NLA.NewAction()
383+ act.setActive(armature_object)
384+ # set XRAY
385+ armature_object.drawMode |= Object.DrawModes.XRAY
386+ # armature settings
387+ armature.drawType = Armature.OCTAHEDRON
388+ armature.envelopes = False
389+ armature.vertexGroups = True
390+ armature.mirrorEdit = True
391+ armature.drawNames=True
392+
393+ # edit bones
394+ armature.makeEditable()
395+ build_armature(armature, mikotoRoot)
396+ armature.update()
397+
398+ return armature_object
399+
400+
401+class TrianglePlane(object):
402+ """
403+ mikoto方式ボーンのアンカーウェイト計算用。
404+ (不完全)
405+ """
406+ __slots__=['normal',
407+ 'v0', 'v1', 'v2',
408+ ]
409+ def __init__(self, v0, v1, v2):
410+ self.v0=v0
411+ self.v1=v1
412+ self.v2=v2
413+
414+ def isInsideXY(self, p):
415+ v0=Vector2(self.v0.x, self.v0.y)
416+ v1=Vector2(self.v1.x, self.v1.y)
417+ v2=Vector2(self.v2.x, self.v2.y)
418+ e01=v1-v0
419+ e12=v2-v1
420+ e20=v0-v2
421+ c0=Vector2.cross(e01, p-v0)
422+ c1=Vector2.cross(e12, p-v1)
423+ c2=Vector2.cross(e20, p-v2)
424+ if c0>=0 and c1>=0 and c2>=0:
425+ return True
426+ if c0<=0 and c1<=0 and c2<=0:
427+ return True
428+
429+ def isInsideYZ(self, p):
430+ v0=Vector2(self.v0.y, self.v0.z)
431+ v1=Vector2(self.v1.y, self.v1.z)
432+ v2=Vector2(self.v2.y, self.v2.z)
433+ e01=v1-v0
434+ e12=v2-v1
435+ e20=v0-v2
436+ c0=Vector2.cross(e01, p-v0)
437+ c1=Vector2.cross(e12, p-v1)
438+ c2=Vector2.cross(e20, p-v2)
439+ if c0>=0 and c1>=0 and c2>=0:
440+ return True
441+ if c0<=0 and c1<=0 and c2<=0:
442+ return True
443+
444+ def isInsideZX(self, p):
445+ v0=Vector2(self.v0.z, self.v0.x)
446+ v1=Vector2(self.v1.z, self.v1.x)
447+ v2=Vector2(self.v2.z, self.v2.x)
448+ e01=v1-v0
449+ e12=v2-v1
450+ e20=v0-v2
451+ c0=Vector2.cross(e01, p-v0)
452+ c1=Vector2.cross(e12, p-v1)
453+ c2=Vector2.cross(e20, p-v2)
454+ if c0>=0 and c1>=0 and c2>=0:
455+ return True
456+ if c0<=0 and c1<=0 and c2<=0:
457+ return True
458+
459+
460+class MikotoAnchor(object):
461+ """
462+ mikoto方式スケルトンのアンカー。
463+ """
464+ __slots__=[
465+ "triangles", "bbox",
466+ ]
467+ def __init__(self):
468+ self.triangles=[]
469+ self.bbox=None
470+
471+ def push(self, face, vertices):
472+ if face.index_count==3:
473+ self.triangles.append(TrianglePlane(
474+ vertices[face.indices[0]],
475+ vertices[face.indices[1]],
476+ vertices[face.indices[2]]
477+ ))
478+ elif face.index_count==4:
479+ self.triangles.append(TrianglePlane(
480+ vertices[face.indices[0]],
481+ vertices[face.indices[1]],
482+ vertices[face.indices[2]]
483+ ))
484+ self.triangles.append(TrianglePlane(
485+ vertices[face.indices[2]],
486+ vertices[face.indices[3]],
487+ vertices[face.indices[0]]
488+ ))
489+ # bounding box
490+ if not self.bbox:
491+ self.bbox=BoundingBox(vertices[face.indices[0]])
492+ for i in face.indices:
493+ self.bbox.expand(vertices[i])
494+
495+
496+ def calcWeight(self, v):
497+ if not self.bbox.isInside(v):
498+ return 0
499+
500+ if self.anyXY(v.x, v.y) and self.anyYZ(v.y, v.z) and self.anyZX(v.z, v.x):
501+ return 1.0
502+ else:
503+ return 0
504+
505+ def anyXY(self, x, y):
506+ for t in self.triangles:
507+ if t.isInsideXY(Vector2(x, y)):
508+ return True
509+ return False
510+
511+ def anyYZ(self, y, z):
512+ for t in self.triangles:
513+ if t.isInsideYZ(Vector2(y, z)):
514+ return True
515+ return False
516+
517+ def anyZX(self, z, x):
518+ for t in self.triangles:
519+ if t.isInsideZX(Vector2(z, x)):
520+ return True
521+ return False
522+
523+
524+def create_bone_weight(scene, mqo, armature_object, objects):
525+ """
526+ create mikoto bone weight.
527+ """
528+ anchorMap={}
529+ # setup mikoto anchors
530+ for o in mqo.objects:
531+ if o.name.startswith("anchor"):
532+ for f in o.faces:
533+ name=mqo.materials[f.material_index].name
534+ if name.endswith('[]'):
535+ basename=name[0:-2]
536+ v=o.vertices[f.indices[0]]
537+ if(v.x>0):
538+ # L
539+ name_L=basename+'_L'
540+ if not name_L in anchorMap:
541+ anchorMap[name_L]=MikotoAnchor()
542+ anchorMap[name_L].push(f, o.vertices)
543+ elif(v.x<0):
544+ # R
545+ name_R=basename+'_R'
546+ if not name_R in anchorMap:
547+ anchorMap[name_R]=MikotoAnchor()
548+ anchorMap[name_R].push(f, o.vertices)
549+ else:
550+ print("no side", v)
551+ else:
552+ if not name in anchorMap:
553+ anchorMap[name]=MikotoAnchor()
554+ anchorMap[name].push(f, o.vertices)
555+
556+ for o in objects:
557+ # add armature modifier
558+ mod=o.modifiers.append(Modifier.Types.ARMATURE)
559+ mod[Modifier.Settings.OBJECT] = armature_object
560+ mod[Modifier.Settings.ENVELOPES] = False
561+ o.makeDisplayList()
562+ # create vertex group
563+ mesh=o.getData(mesh=True)
564+ for name in anchorMap.keys():
565+ mesh.addVertGroup(name)
566+ mesh.update()
567+
568+ # assing vertices to vertex group
569+ for o in objects:
570+ mesh=o.getData(mesh=True)
571+ for i, mvert in enumerate(mesh.verts):
572+ hasWeight=False
573+ for name, anchor in anchorMap.items():
574+ weight=anchor.calcWeight(mvert.co)
575+ if weight>0:
576+ mesh.assignVertsToGroup(
577+ name, [i], weight, Mesh.AssignModes.ADD)
578+ hasWeight=True
579+ if not hasWeight:
580+ # debug orphan vertex
581+ print('orphan', mvert)
582+ mesh.update()
583+
584+
585+def _execute(filepath='', scale=0.1):
586+ # read mqo model
587+ model=reader.read_from_file(filepath)
588+ if not model:
589+ bl.message("fail to load %s" % filepath)
590+ return
591+
592+ # create materials
593+ materials, imageMap=__createMaterials(model, os.path.dirname(filepath))
594+ if len(materials)==0:
595+ materials.append(bl.material.create('default'))
596+
597+ # create objects
598+ root=bl.object.createEmpty(os.path.basename(filepath))
599+ objects=__createObjects(model, root, materials, imageMap, scale)
600+
601+ if has_mikoto(model):
602+ # create mikoto bone
603+ armature_object=create_armature(model)
604+ if armature_object:
605+ root.makeParent([armature_object])
606+
607+ # create bone weight
608+ create_bone_weight(model, armature_object, objects)
609+
similarity index 98%
rename from blender25-meshio/import_pmd.py
rename to blender26-meshio/import_pmd.py
index bc81749833307d52e87a121181610bd4024c307c..ac6323e9101bcd85ced043d433c05aa1ca894869
--- a/blender25-meshio/import_pmd.py
+++ b/blender26-meshio/import_pmd.py
@@ -70,7 +70,7 @@ import bpy
7070 import mathutils
7171
7272 # wrapper
73-from . import bl25 as bl
73+from . import bl
7474
7575 xrange=range
7676
@@ -216,10 +216,14 @@ def __importShape(obj, l, vertex_map):
216216 print(msg)
217217 print("invalid index %d/%d" % (index, len(base.indices)))
218218 continue
219- vertex_index=vertex_map[base_index]
220- bl.shapekey.assign(new_shape_key, vertex_index,
221- mesh.vertices[vertex_index].co+
222- bl.createVector(*convert_coord(offset)))
219+ try:
220+ vertex_index=vertex_map[base_index]
221+ bl.shapekey.assign(new_shape_key, vertex_index,
222+ mesh.vertices[vertex_index].co+
223+ bl.createVector(*convert_coord(offset)))
224+ except KeyError as e:
225+ print('base_index: %d' % base_index)
226+ print(e)
223227
224228 # select base shape
225229 bl.object.setActivateShapeKey(obj, 0)
--- a/blender26-meshio/import_pmx.py
+++ b/blender26-meshio/import_pmx.py
@@ -1,8 +1,39 @@
11 # coding: utf-8
2+"""
3+PMXモデルをインポートする。
24
3-def load(operator, context, filepath, **kw):
5+1マテリアル、1オブジェクトで作成する。
6+"""
7+import os
8+from . import bl
9+
10+
11+def __create_a_material(m, name, textures_and_images):
12+ material = bl.material.create(name)
13+ # diffuse
14+ material.diffuse_shader='FRESNEL'
15+ material.diffuse_color=[m.diffuse_color.r, m.diffuse_color.g, m.diffuse_color.b]
16+ material.alpha=m.alpha
17+ # specular
18+ material.specular_shader='TOON'
19+ material.specular_color=[m.specular_color.r, m.specular_color.g, m.specular_color.b]
20+ material.specular_toon_size=int(m.specular_factor)
21+ # ambient
22+ material.mirror_color=[m.ambient_color.r, m.ambient_color.g, m.ambient_color.b]
23+ # todo
24+ # flag
25+ # edge_color
26+ # edge_size
27+ # other
28+ material.preview_render_type='FLAT'
29+ material.use_transparency=True
30+ # texture
31+ texture_index=bl.material.addTexture(material, textures_and_images[m.texture_index][0])
32+ return material
33+
34+def _execute(filepath):
35+ bl.progress_set('load %s' % filepath, 0.0)
436 print(filepath)
5- print(kw)
637
738 from .pymeshio.pmx import reader
839 model=reader.read_from_file(filepath)
@@ -10,6 +41,48 @@ def load(operator, context, filepath, **kw):
1041 print("fail to load %s" % filepath)
1142 return
1243 print(model)
44+ bl.progress_set('loaded', 0.1)
45+
46+ # メッシュをまとめるエンプティオブジェクト
47+ model_name=model.english_name
48+ if len(model_name)==0:
49+ model_name=os.path.basename(filepath)
50+ root=bl.object.createEmpty(model_name)
51+ root[bl.MMD_MB_NAME]=model.name
52+ root[bl.MMD_MB_COMMENT]=model.comment
53+ root[bl.MMD_COMMENT]=model.english_comment
54+
55+ # テクスチャを作る
56+ texture_dir=os.path.dirname(filepath)
57+ textures_and_images=[bl.texture.create(os.path.join(texture_dir, t))
58+ for t in model.textures]
59+ print(textures_and_images)
60+
61+ def get_name(name, fmt, *args):
62+ if len(name.encode("utf-8"))<16:
63+ return name
64+ else:
65+ return fmt.format(*args)
66+ index_generator=(i for i in model.indices)
67+ # 頂点配列。(Left handed y-up) to (Right handed z-up)
68+ vertices=[(pos.x, pos.z, pos.y)
69+ for pos in (v.position for v in model.vertices)]
70+ for i, m in enumerate(model.materials):
71+ # マテリアル毎にメッシュを作成する
72+ print(m.name)
73+ #material=__create_a_material(m, get_name(m.name, "material:{0:02}", i), textures_and_images)
74+ material=__create_a_material(m, m.name, textures_and_images)
75+ mesh, mesh_object=bl.mesh.create("object:{0:02}".format(i))
76+ bl.mesh.addMaterial(mesh, material)
77+ # activate object
78+ bl.object.deselectAll()
79+ bl.object.activate(mesh_object)
80+ bl.object.makeParent(root, mesh_object)
81+ indices=[next(index_generator)
82+ for _ in range(m.vertex_count)]
83+ bl.mesh.addGeometry(mesh, vertices,
84+ [(indices[i], indices[i+1], indices[i+2])
85+ for i in range(0, len(indices), 3)])
1386
1487 return {'FINISHED'}
1588
--- a/pymeshio/pmd/__init__.py
+++ b/pymeshio/pmd/__init__.py
@@ -222,7 +222,7 @@ class Bone(object):
222222 self.ik_index=0xFFFF
223223 self.pos=common.Vector3(0, 0, 0)
224224 self.children=[]
225- self.english_name=''
225+ self.english_name=b''
226226
227227 def __eq__(self, rhs):
228228 return (
@@ -387,7 +387,7 @@ class Morph(object):
387387 self.type=None
388388 self.indices=[]
389389 self.pos_list=[]
390- self.english_name=''
390+ self.english_name=b''
391391 self.vertex_count=0
392392
393393 def append(self, index, x, y, z):
@@ -559,7 +559,7 @@ class Model(object):
559559 'no_parent_bones',
560560 ]
561561 def __init__(self, version=1.0):
562- self.path=''
562+ self.path=b''
563563 self.version=version
564564 self.name=b''
565565 self.comment=b''
Show on old repository browser