4 Name: 'MikuMikuDance model (.pmd)...'
7 Tooltip: 'Import PMD file for MikuMikuDance.'
9 __author__= ["ousttrue"]
15 This script imports a pmd into Blender for editing.
17 0.1 20091126: first implement.
18 0.2 20091209: implement IK.
19 0.3 20091210: implement morph target.
20 0.4 20100305: use english name.
21 0.5 20100408: cleanup not used vertices.
22 0.6 20100416: fix fornt face. texture load fail safe. add progress.
23 0.7 20100506: C extension.
24 0.8 20100521: add shape_key group.
25 1.0 20100530: add invisilbe bone tail(armature layer 2).
26 1.1 20100608: integrate 2.4 and 2.5.
27 1.2 20100616: implement rigid body.
28 1.3 20100619: fix for various models.
29 1.4 20100623: fix constraint name.
30 1.5 20100626: refactoring.
31 1.6 20100629: sphere map.
32 1.7 20100703: implement bone group.
33 1.8 20100710: implement toon texture.
34 1.9 20100718: keep model name, comment.
35 2.0 20100724: update for Blender2.53.
36 2.1 20100731: add full python module.
37 2.2 20101005: update for Blender2.54.
38 2.3 20101228: update for Blender2.55.
41 'category': 'Import/Export',
42 'name': 'Import: MikuMikuDance Model Format (.pmd)',
46 'location': 'File > Import',
47 'description': 'Import from the MikuMikuDance Model Format (.pmd)',
48 'warning': '', # used for warning icon and text in addons panel
49 'wiki_url': 'http://sourceforge.jp/projects/meshio/wiki/FrontPage',
50 'tracker_url': 'http://sourceforge.jp/ticket/newticket.php?group_id=5081',
53 MMD_SHAPE_GROUP_NAME='_MMD_SHAPE'
55 MMD_MB_COMMENT='mb_comment'
57 BASE_SHAPE_NAME='Basis'
58 RIGID_NAME='rigid_name'
59 RIGID_SHAPE_TYPE='rigid_shape_type'
60 RIGID_PROCESS_TYPE='rigid_process_type'
61 RIGID_BONE_NAME='rigid_bone_name'
62 #RIGID_LOCATION='rigid_loation'
63 RIGID_GROUP='ribid_group'
64 RIGID_INTERSECTION_GROUP='rigid_intersection_group'
65 RIGID_WEIGHT='rigid_weight'
66 RIGID_LINEAR_DAMPING='rigid_linear_damping'
67 RIGID_ANGULAR_DAMPING='rigid_angular_damping'
68 RIGID_RESTITUTION='rigid_restitution'
69 RIGID_FRICTION='rigid_friction'
70 CONSTRAINT_NAME='constraint_name'
71 CONSTRAINT_A='const_a'
72 CONSTRAINT_B='const_b'
73 CONSTRAINT_POS_MIN='const_pos_min'
74 CONSTRAINT_POS_MAX='const_pos_max'
75 CONSTRAINT_ROT_MIN='const_rot_min'
76 CONSTRAINT_ROT_MAX='const_rot_max'
77 CONSTRAINT_SPRING_POS='const_spring_pos'
78 CONSTRAINT_SPRING_ROT='const_spring_rot'
79 TOON_TEXTURE_OBJECT='ToonTextures'
82 ###############################################################################
84 ###############################################################################
91 from meshio import pmd, englishmap
92 print('use meshio C module')
95 from pymeshio import englishmap
96 from pymeshio import mmd as pmd
100 return sys.version_info[0]<3
105 from Blender import Mathutils
111 def createPmdMaterial(m, index):
112 material=Blender.Material.New()
114 material.setDiffuseShader(Blender.Material.Shaders.DIFFUSE_TOON)
115 material.setRGBCol([m.diffuse.r, m.diffuse.g, m.diffuse.b])
116 material.setAlpha(m.diffuse.a)
118 material.setSpecShader(Blender.Material.Shaders.SPEC_TOON)
119 material.setSpec(m.shinness*0.1)
120 material.setSpecCol([m.specular.r, m.specular.g, m.specular.b])
122 material.setMirCol([m.ambient.r, m.ambient.g, m.ambient.b])
124 material.enableSSS=True if m.flag==1 else False
126 material.name="m_%02d" % index
129 def poseBoneLimit(n, b):
132 if n.startswith("knee_"):
137 b.limitMax=[180, 0, 0]
138 elif n.startswith("ankle_"):
141 def setSphereMap(material, index, blend_type='MULTIPLY'):
142 slot=material.textures[index]
143 slot.mapto=Blender.Texture.MapTo.NOR
144 slot.mapping=Blender.Texture.Mappings.SPHERE
145 if blend_type=='MULTIPLY':
146 slot.blendmode=Blender.Texture.BlendModes.MULTIPLY
147 elif blend_type=='ADD':
148 slot.blendmode=Blender.Texture.BlendModes.ADD
160 def createPmdMaterial(m, index):
161 material = bpy.data.materials.new("Material")
163 material.diffuse_shader='FRESNEL'
164 material.diffuse_color=([m.diffuse.r, m.diffuse.g, m.diffuse.b])
165 material.alpha=m.diffuse.a
167 material.specular_shader='TOON'
168 material.specular_color=([m.specular.r, m.specular.g, m.specular.b])
169 material.specular_toon_size=int(m.shinness)
171 material.mirror_color=([m.ambient.r, m.ambient.g, m.ambient.b])
173 material.subsurface_scattering.use=True if m.flag==1 else False
175 material.name="m_%02d" % index
176 material.preview_render_type='FLAT'
177 material.use_transparency=True
180 def poseBoneLimit(n, b):
183 if n.startswith("knee_"):
188 b.use_ik_limit_x=True
191 elif n.startswith("ankle_"):
195 def setSphereMap(material, index, blend_type='MULTIPLY'):
196 slot=material.texture_slots[index]
197 slot.texture_coords='NORMAL'
198 slot.mapping='SPHERE'
199 slot.blend_type=blend_type
202 ###############################################################################
204 return bl.createVector(v.x, v.y, v.z)
207 def convert_coord(pos):
209 Left handed y-up to Right handed z-up
211 return (pos.x, pos.z, pos.y)
214 def to_radian(degree):
215 return math.pi * degree / 180
218 def get_bone_name(l, index):
220 return l.bones[0].getName()
222 if index < len(l.bones):
223 name=englishmap.getEnglishBoneName(l.bones[index].getName())
226 return l.bones[index].getName()
227 print('invalid bone index', index)
228 return l.bones[0].getName()
231 def get_group_name(g):
232 group_name=englishmap.getEnglishBoneGroupName(g.getName().strip())
234 group_name=g.getName().strip()
238 def __importToonTextures(io, tex_dir):
239 mesh, meshObject=bl.mesh.create(TOON_TEXTURE_OBJECT)
240 material=bl.material.create(TOON_TEXTURE_OBJECT)
241 bl.mesh.addMaterial(mesh, material)
243 t=io.getToonTexture(i)
244 path=os.path.join(tex_dir, t.getName())
245 texture, image=bl.texture.create(path)
246 bl.material.addTexture(material, texture, False)
247 return meshObject, material
250 def __importShape(obj, l, vertex_map):
251 if len(l.morph_list)==0:
255 bl.object.pinShape(obj, True)
259 for s in l.morph_list:
263 # create vertex group
264 bl.object.addVertexGroup(obj, MMD_SHAPE_GROUP_NAME)
269 bl.object.assignVertexGroup(
270 obj, MMD_SHAPE_GROUP_NAME, vertex_map[i], 0)
276 baseShapeBlock=bl.object.addShapeKey(obj, BASE_SHAPE_NAME)
278 mesh=bl.object.getData(obj)
282 for s in l.morph_list:
287 name=englishmap.getEnglishSkinName(s.getName())
293 for index, offset in zip(s.indices, s.pos_list):
295 vertex_index=vertex_map[base.indices[index]]
296 v=mesh.vertices[vertex_index].co
297 offset=convert_coord(offset)
301 except IndexError as msg:
303 print(index, len(base.indices), len(vertex_map))
304 print(len(mesh.vertices))
305 print(base.indices[index])
309 #print 'this mesh not has shape vertices'
312 # create shapekey block
313 new_shape_key=bl.object.addShapeKey(obj, name)
315 # copy vertex to shape key
319 for mv, v in zip(mesh.vertices, baseShapeBlock.getData()):
327 new_shape_key=bl.object.addShapeKey(obj, name)
329 for index, offset in zip(s.indices, s.pos_list):
331 vertex_index=vertex_map[base.indices[index]]
332 bl.shapekey.assign(new_shape_key, vertex_index,
333 mesh.vertices[vertex_index].co+
334 bl.createVector(*convert_coord(offset)))
335 except IndexError as msg:
337 print(index, len(base.indices), len(vertex_map))
338 print(len(mesh.vertices))
339 print(base.indices[index])
343 #print 'this mesh not has shape vertices'
347 bl.object.setActivateShapeKey(obj, 0)
350 def __build(armature, b, p, parent):
351 name=englishmap.getEnglishBoneName(b.getName())
355 bone=bl.armature.createBone(armature, name)
357 if parent and (b.tail_index==0 or b.type==6 or b.type==7 or b.type==9):
359 bone.head = bl.createVector(*convert_coord(b.pos))
360 bone.tail=bone.head+bl.createVector(0, 1, 0)
362 if bone.name=="center_t":
363 # センターボーンは(0, 1, 0)の方向を向いていないと具合が悪い
364 parent.tail=parent.head+bl.createVector(0, 1, 0)
365 bone.head=parent.tail
366 bone.tail=bone.head+bl.createVector(0, 1, 0)
368 if parent.tail==bone.head:
371 print('diffurence with parent.tail and head', name)
374 bl.bone.setConnected(bone)
376 bl.bone.setLayerMask(bone, [0, 1])
379 bone.head = bl.createVector(*convert_coord(b.pos))
380 bone.tail = bl.createVector(*convert_coord(b.tail))
383 if parent.tail==bone.head:
384 bl.bone.setConnected(bone)
386 if bone.head==bone.tail:
387 bone.tail=bone.head+bl.createVector(0, 1, 0)
390 __build(armature, c, b, bone)
393 def __importArmature(l):
394 armature, armature_object=bl.armature.create()
397 bl.armature.makeEditable(armature_object)
400 __build(armature, b, None, None)
401 bl.armature.update(armature)
405 pose = bl.object.getPose(armature_object)
407 target=l.bones[ik.target]
408 name = englishmap.getEnglishBoneName(target.getName())
410 name=target.getName()
411 p_bone = pose.bones[name]
413 print('not found', name)
415 if len(ik.children) >= 16:
416 print('over MAX_CHAINLEN', ik, len(ik.children))
418 effector_name=englishmap.getEnglishBoneName(
419 l.bones[ik.index].getName())
420 if not effector_name:
421 effector_name=l.bones[ik.index].getName()
423 constraint=bl.armature.createIkConstraint(armature_object,
424 p_bone, effector_name, ik)
426 bl.armature.makeEditable(armature_object)
427 bl.armature.update(armature)
434 for i, g in enumerate(l.bone_group_list):
435 name=get_group_name(g)
436 bl.object.createBoneGroup(armature_object, name, "THEME%02d" % (i+1))
438 # assign bone to group
439 for b_index, g_index in l.bone_display_list:
442 bone_name=englishmap.getEnglishBoneName(b.getName())
444 bone_name=b.getName()
446 g=l.bone_group_list[g_index-1]
447 group_name=get_group_name(g)
450 pose.bones[bone_name].bone_group=pose.bone_groups[group_name]
454 return armature_object
457 def __import16MaerialAndMesh(meshObject, l,
458 material_order, face_map, tex_dir, toon_material):
460 mesh=bl.object.getData(meshObject)
461 ############################################################
463 ############################################################
464 bl.progress_print('create materials')
470 for material_index in material_order:
472 m=l.materials[material_index]
473 mesh_material_map[material_index]=index
477 material=createPmdMaterial(m, material_index)
480 texture_name=m.getTexture()
482 for i, t in enumerate(texture_name.split('*')):
484 texture=textureMap[t]
486 path=os.path.join(tex_dir, t)
487 texture, image=bl.texture.create(path)
488 textureMap[texture_name]=texture
489 imageMap[material_index]=image
490 texture_index=bl.material.addTexture(material, texture)
491 if t.endswith('sph'):
493 setSphereMap(material, texture_index)
494 elif t.endswith('spa'):
496 setSphereMap(material, texture_index, 'ADD')
499 toon_index=bl.material.addTexture(
501 bl.material.getTexture(
503 0 if m.toon_index==0xFF else m.toon_index
507 bl.mesh.addMaterial(mesh, material)
511 ############################################################
513 ############################################################
514 bl.progress_print('create vertices')
517 for v in l.each_vertex():
518 vertices.append(convert_coord(v.pos))
520 ############################################################
522 ############################################################
523 bl.progress_print('create faces')
526 mesh_face_materials=[]
529 for material_index in material_order:
530 face_offset=face_map[material_index]
531 m=l.materials[material_index]
532 material_faces=l.indices[face_offset:face_offset+m.vertex_count]
534 def degenerate(i0, i1, i2):
538 return i0==i1 or i1==i2 or i2==i0
540 for j in xrange(0, len(material_faces), 3):
542 i1=material_faces[j+1]
543 i2=material_faces[j+2]
545 triangle=[i2, i1, i0]
546 if degenerate(*triangle):
548 mesh_face_indices.append(triangle[0:3])
549 mesh_face_materials.append(material_index)
550 used_vertices.add(i0)
551 used_vertices.add(i1)
552 used_vertices.add(i2)
554 ############################################################
555 # create vertices & faces
556 ############################################################
557 bl.mesh.addGeometry(mesh, vertices, mesh_face_indices)
559 ############################################################
561 ############################################################
562 # create vertex group
564 for v in l.each_vertex():
565 vertex_groups[v.bone0]=True
566 vertex_groups[v.bone1]=True
567 for i in vertex_groups.keys():
568 bl.object.addVertexGroup(meshObject, get_bone_name(l, i))
571 bl.mesh.useVertexUV(mesh)
572 for i, v, mvert in zip(xrange(len(l.vertices)),
573 l.each_vertex(), mesh.vertices):
575 bl.vertex.setNormal(mvert, convert_coord(v.normal))
577 w1=float(v.weight0)/100.0
579 bl.object.assignVertexGroup(meshObject, get_bone_name(l, v.bone0),
581 bl.object.assignVertexGroup(meshObject, get_bone_name(l, v.bone1),
584 ############################################################
586 ############################################################
589 for i, (face, material_index) in enumerate(
590 zip(mesh.faces, mesh_face_materials)):
592 index=mesh_material_map[material_index]
593 except KeyError as message:
594 print(message, mesh_material_map, m)
596 bl.face.setMaterial(face, index)
597 material=mesh.materials[index]
599 if bl.material.hasTexture(material):
600 uv_array=[l.getUV(i) for i in bl.face.getIndices(face)]
601 bl.mesh.setFaceUV(mesh, i, face,
603 [(uv.x, 1.0-uv.y) for uv in uv_array],
604 imageMap.get(index, None))
607 bl.face.setSmooth(face, True)
611 ############################################################
612 # clean up not used vertices
613 ############################################################
614 bl.progress_print('clean up vertices not used')
617 for i, v in enumerate(l.each_vertex()):
618 if i in used_vertices:
619 vertex_map[i]=len(vertex_map)
621 remove_vertices.append(i)
623 bl.mesh.vertsDelete(mesh, remove_vertices)
625 bl.progress_print('%s created' % mesh.name)
629 def __importMaterialAndMesh(io, tex_dir, toon_material):
631 @param l[in] mmd.PMDLoader
634 ############################################################
635 # shpaeキーで使われるマテリアル優先的に前に並べる
636 ############################################################
637 # shapeキーで使われる頂点インデックスを集める
638 shape_key_used_vertices=set()
639 if len(io.morph_list)>0:
642 for s in io.morph_list:
649 for index in base.indices:
650 shape_key_used_vertices.add(index)
652 # マテリアルに含まれる頂点がshape_keyに含まれるか否か?
653 def isMaterialUsedInShape(offset, m):
654 for i in xrange(offset, offset+m.vertex_count):
655 if io.indices[i] in shape_key_used_vertices:
658 material_with_shape=set()
660 # 各マテリアルの開始頂点インデックスを記録する
663 for i, m in enumerate(io.materials):
664 face_map[i]=face_count
665 if isMaterialUsedInShape(face_count, m):
666 material_with_shape.add(i)
667 face_count+=m.vertex_count
669 # shapeキーで使われる頂点のあるマテリアル
670 material_with_shape=list(material_with_shape)
671 material_with_shape.sort()
673 # shapeキーに使われていないマテリアル
674 material_without_shape=[]
675 for i in range(len(io.materials)):
676 if not i in material_with_shape:
677 material_without_shape.append(i)
680 def __splitList(l, length):
681 for i in range(0, len(l), length):
684 def __importMeshAndShape(material16, name):
685 mesh, meshObject=bl.mesh.create(name)
688 bl.object.deselectAll()
689 bl.object.activate(meshObject)
691 # shapeキーで使われる順に並べなおしたマテリアル16個分の
693 vertex_map=__import16MaerialAndMesh(
694 meshObject, io, material16, face_map, tex_dir, toon_material)
697 __importShape(meshObject, io, vertex_map)
702 mesh_objects=[__importMeshAndShape(material16, 'with_shape')
703 for material16 in __splitList(material_with_shape, 16)]
705 mesh_objects+=[__importMeshAndShape(material16, 'mesh')
706 for material16 in __splitList(material_without_shape, 16)]
711 def __importConstraints(io):
714 print("create constraint")
715 container=bl.object.createEmpty('Constraints')
717 True, False, False, False, False, False, False, False, False, False,
718 False, False, False, False, False, False, False, False, False, False,
720 material=bl.material.create('constraint')
721 material.diffuse_color=(1, 0, 0)
723 for i, c in enumerate(io.constraints):
724 bpy.ops.mesh.primitive_uv_sphere_add(
728 location=(c.pos.x, c.pos.z, c.pos.y),
731 meshObject=bl.object.getActive()
732 constraintMeshes.append(meshObject)
733 mesh=bl.object.getData(meshObject)
734 bl.mesh.addMaterial(mesh, material)
735 meshObject.name='c_%d' % i
736 #meshObject.draw_transparent=True
737 #meshObject.draw_wire=True
738 meshObject.max_draw_type='SOLID'
740 meshObject.rotation_euler=(-rot.x, -rot.z, -rot.y)
742 meshObject[CONSTRAINT_NAME]=c.getName()
743 meshObject[CONSTRAINT_A]=io.rigidbodies[c.rigidA].getName()
744 meshObject[CONSTRAINT_B]=io.rigidbodies[c.rigidB].getName()
745 meshObject[CONSTRAINT_POS_MIN]=VtoV(c.constraintPosMin)
746 meshObject[CONSTRAINT_POS_MAX]=VtoV(c.constraintPosMax)
747 meshObject[CONSTRAINT_ROT_MIN]=VtoV(c.constraintRotMin)
748 meshObject[CONSTRAINT_ROT_MAX]=VtoV(c.constraintRotMax)
749 meshObject[CONSTRAINT_SPRING_POS]=VtoV(c.springPos)
750 meshObject[CONSTRAINT_SPRING_ROT]=VtoV(c.springRot)
752 for meshObject in reversed(constraintMeshes):
753 bl.object.makeParent(container, meshObject)
758 def __importRigidBodies(io):
761 print("create rigid bodies")
763 container=bl.object.createEmpty('RigidBodies')
765 True, False, False, False, False, False, False, False, False, False,
766 False, False, False, False, False, False, False, False, False, False,
768 material=bl.material.create('rigidBody')
770 for i, rigid in enumerate(io.rigidbodies):
771 if rigid.boneIndex==0xFFFF:
775 bone=io.bones[rigid.boneIndex]
776 pos=bone.pos+rigid.position
778 if rigid.shapeType==pmd.SHAPE_SPHERE:
779 bpy.ops.mesh.primitive_ico_sphere_add(
780 location=(pos.x, pos.z, pos.y),
783 bpy.ops.transform.resize(
784 value=(rigid.w, rigid.w, rigid.w))
785 elif rigid.shapeType==pmd.SHAPE_BOX:
786 bpy.ops.mesh.primitive_cube_add(
787 location=(pos.x, pos.z, pos.y),
790 bpy.ops.transform.resize(
791 value=(rigid.w, rigid.d, rigid.h))
792 elif rigid.shapeType==pmd.SHAPE_CAPSULE:
793 bpy.ops.mesh.primitive_tube_add(
794 location=(pos.x, pos.z, pos.y),
797 bpy.ops.transform.resize(
798 value=(rigid.w, rigid.w, rigid.h))
802 meshObject=bl.object.getActive()
803 mesh=bl.object.getData(meshObject)
804 rigidMeshes.append(meshObject)
805 bl.mesh.addMaterial(mesh, material)
806 meshObject.name='r_%d' % i
807 meshObject[RIGID_NAME]=rigid.getName()
808 #meshObject.draw_transparent=True
809 #meshObject.draw_wire=True
810 meshObject.max_draw_type='WIRE'
812 meshObject.rotation_euler=(-rot.x, -rot.z, -rot.y)
815 meshObject[RIGID_SHAPE_TYPE]=rigid.shapeType
816 meshObject[RIGID_PROCESS_TYPE]=rigid.processType
818 bone_name = englishmap.getEnglishBoneName(bone.getName())
820 bone_name=bone.getName()
821 meshObject[RIGID_BONE_NAME]=bone_name
823 meshObject[RIGID_GROUP]=rigid.group
824 meshObject[RIGID_INTERSECTION_GROUP]=rigid.target
825 meshObject[RIGID_WEIGHT]=rigid.weight
826 meshObject[RIGID_LINEAR_DAMPING]=rigid.linearDamping
827 meshObject[RIGID_ANGULAR_DAMPING]=rigid.angularDamping
828 meshObject[RIGID_RESTITUTION]=rigid.restitution
829 meshObject[RIGID_FRICTION]=rigid.friction
831 for meshObject in reversed(rigidMeshes):
832 bl.object.makeParent(container, meshObject)
837 def _execute(filename):
839 load pmd file to context.
843 bl.progress_set('load %s' % filename, 0.0)
846 if not io.read(filename):
847 bl.message("fail to load %s" % filename)
849 bl.progress_set('loaded', 0.1)
852 model_name=io.getEnglishName()
853 if len(model_name)==0:
854 model_name=io.getName()
855 root=bl.object.createEmpty(model_name)
856 root[MMD_MB_NAME]=io.getName()
857 root[MMD_MB_COMMENT]=io.getComment()
858 root[MMD_COMMENT]=io.getEnglishComment()
861 tex_dir=os.path.dirname(filename)
862 toonTextures, toonMaterial=__importToonTextures(io, tex_dir)
863 bl.object.makeParent(root, toonTextures)
866 mesh_objects=__importMaterialAndMesh(io, tex_dir, toonMaterial)
867 for o in mesh_objects:
868 bl.object.makeParent(root, o)
871 armature_object=__importArmature(io)
873 bl.object.makeParent(root, armature_object)
874 armature = bl.object.getData(armature_object)
876 # add armature modifier
877 for o in mesh_objects:
878 bl.modifier.addArmature(o, armature_object)
881 for n, b in bl.object.getPose(armature_object).bones.items():
884 # import rigid bodies
885 rigidBodies=__importRigidBodies(io)
887 bl.object.makeParent(root, rigidBodies)
890 constraints=__importConstraints(io)
892 bl.object.makeParent(root, constraints)
894 bl.object.activate(root)
899 def execute_24(filename):
900 bl.initialize('pmd_import', bpy.data.scenes.active)
901 _execute(filename.decode(bl.INTERNAL_ENCODING))
904 Blender.Window.FileSelector(
907 Blender.sys.makename(ext='.pmd'))
911 class IMPORT_OT_pmd(bpy.types.Operator):
912 bl_idname = "import_scene.pmd"
913 bl_label = 'Import PMD'
915 # List of operator properties, the attributes will be assigned
916 # to the class instance from the operator settings before calling.
917 filepath = bpy.props.StringProperty()
918 filename = bpy.props.StringProperty()
919 directory = bpy.props.StringProperty()
921 def execute(self, context):
922 bl.initialize('pmd_import', context.scene)
923 _execute(self.properties.filepath)
927 def invoke(self, context, event):
928 wm = context.window_manager
930 wm.fileselect_add(self)
932 wm.add_fileselect(self)
933 return 'RUNNING_MODAL'
936 def menu_func(self, context):
937 self.layout.operator(IMPORT_OT_pmd.bl_idname,
938 text="MikuMikuDance model (.pmd)",
943 bpy.types.INFO_MT_file_import.append(menu_func)
946 bpy.types.INFO_MT_file_import.remove(menu_func)
948 if __name__=="__main__":