def _get_bone_pose_matrix_cleaned(self, bone): offset_m4 = (Matrix.Translation(bone.location) * Quaternion(bone.rotation_quaternion).to_matrix().to_4x4()) pose_matrix_cleaned = bone.pose_matrix * offset_m4.inverted() print(bone.pose_matrix) # pose matrix not update after translate hand return pose_matrix_cleaned
def run(self, objects, set_location_func, set_rotation_func): apply_location_dict = {} apply_rotation_dict = {} receive = True sync = False try: data = self.sock.recv( 1024 ) except: data = None if self.next_sync: apply_location_dict = self.next_location_dict.copy() apply_rotation_dict = self.next_rotation_dict.copy() self.next_location_dict = {} self.next_rotation_dict = {} self.next_sync = False sync = True receive = False trash = data while(receive): data = trash decoded = OSC.decodeOSC(data) ob_name = str(decoded[0], "utf-8") try: if (ob_name.startswith("@")): # Something to play with: # values that begin with a @ are python expressions, # and there is one parameter after the address in the OSC message # if you set something such as # bpy.data.objects"['Cube']".location.x= {V} # into a OSC path for, say, a face shape smile controller # you can move an object by smiling to_evaluate = ob_name[1:] to_evaluate += str(decoded[2]) try: print(exec(to_evaluate)) except Exception as e: print(to_evaluate) print(str(e)) elif (ob_name.startswith("?")): # This one could be used for something such as mapping # "thumbs up" gesture for rendering # Add the following path to a gesture controller OSC path # ?bpy.ops.render.render() to_evaluate = ob_name[1:] try: print(exec(to_evaluate)) except Exception as e: print(to_evaluate) print(str(e)) elif len(decoded) == 3: #one value if ob_name == "NI_mate_sync": if sync: self.next_sync = True else: sync = True apply_location_dict = self.location_dict.copy() apply_rotation_dict = self.rotation_dict.copy() self.location_dict = {} self.rotation_dict = {} self.next_location_dict = {} self.next_rotation_dict = {} else: if sync: self.next_location_dict[ob_name] = Vector([decoded[2], 0, 0]) else: self.location_dict[ob_name] = Vector([decoded[2], 0, 0]) elif len(decoded) == 5: #location if sync: self.next_location_dict[ob_name] = Vector([decoded[2], -decoded[4], decoded[3]]) else: self.location_dict[ob_name] = Vector([decoded[2], -decoded[4], decoded[3]]) elif len(decoded) == 6: #quaternion if sync: self.next_rotation_dict[ob_name] = Quaternion((-decoded[2], decoded[3], -decoded[5], decoded[4])) else: self.rotation_dict[ob_name] = Quaternion((-decoded[2], decoded[3], -decoded[5], decoded[4])) elif len(decoded) == 9: #location & quaternion if sync: self.next_location_dict[ob_name] = Vector([decoded[2], -decoded[4], decoded[3]]) self.next_rotation_dict[ob_name] = Quaternion((-decoded[5], decoded[6], -decoded[8], decoded[7])) else: self.location_dict[ob_name] = Vector([decoded[2], -decoded[4], decoded[3]]) self.rotation_dict[ob_name] = Quaternion((-decoded[5], decoded[6], -decoded[8], decoded[7])) except: print("NI mate Tools error parsing OSC message: " + str(decoded)) pass try: trash = self.sock.recv(1024) except: break if sync: for key, value in apply_location_dict.items(): set_location_func(objects, key, value, self.original_locations) for key, value in apply_rotation_dict.items(): set_rotation_func(objects, key, value, self.original_rotations) self.location_dict = {} self.rotation_dict = {} else: for key, value in self.location_dict.items(): set_location_func(objects, key, value, self.location_dict) for key, value in self.rotation_dict.items(): set_rotation_func(objects, key, value, self.rotation_dict)
def angle_axis_to_quat(angle, axis): w = math.cos(angle / 2.0) xyz = axis.normalized() * math.sin(angle / 2.0) return Quaternion((w, xyz.x, xyz.y, xyz.z))
def pitch_down(self, angle): """Pitch the turtle down about the right axis""" self.dir.rotate(Quaternion(self.right, radians(-angle))) self.dir.normalize()
def recursive_node_traverse(self, blender_object, blender_bone, parent_uuid, parent_coll_matrix_world, armature_uuid=None, dupli_world_matrix=None): node = VExportNode() node.uuid = str(uuid.uuid4()) node.parent_uuid = parent_uuid node.set_blender_data(blender_object, blender_bone) # add to parent if needed if parent_uuid is not None: self.add_children(parent_uuid, node.uuid) else: self.roots.append(node.uuid) # Set blender type if blender_bone is not None: node.blender_type = VExportNode.BONE self.nodes[armature_uuid].bones[blender_bone.name] = node.uuid node.use_deform = blender_bone.id_data.data.bones[ blender_bone.name].use_deform elif blender_object.type == "ARMATURE": node.blender_type = VExportNode.ARMATURE elif blender_object.type == "CAMERA": node.blender_type = VExportNode.CAMERA elif blender_object.type == "LIGHT": node.blender_type = VExportNode.LIGHT elif blender_object.instance_type == "COLLECTION": node.blender_type = VExportNode.COLLECTION else: node.blender_type = VExportNode.OBJECT # For meshes with armature modifier (parent is armature), keep armature uuid if node.blender_type == VExportNode.OBJECT: modifiers = {m.type: m for m in blender_object.modifiers} if "ARMATURE" in modifiers and modifiers[ "ARMATURE"].object is not None: if parent_uuid is None or not self.nodes[ parent_uuid].blender_type == VExportNode.ARMATURE: # correct workflow is to parent skinned mesh to armature, but ... # all users don't use correct workflow print( "WARNING: Armature must be the parent of skinned mesh") print( "Armature is selected by its name, but may be false in case of instances" ) # Search an armature by name, and use the first found # This will be done after all objects are setup node.armature_needed = modifiers["ARMATURE"].object.name else: node.armature = parent_uuid # For bones, store uuid of armature if blender_bone is not None: node.armature = armature_uuid # for bone/bone parenting, store parent, this will help armature tree management if parent_uuid is not None and self.nodes[ parent_uuid].blender_type == VExportNode.BONE and node.blender_type == VExportNode.BONE: node.parent_bone_uuid = parent_uuid # Objects parented to bone if parent_uuid is not None and self.nodes[ parent_uuid].blender_type == VExportNode.BONE and node.blender_type != VExportNode.BONE: node.parent_bone_uuid = parent_uuid # World Matrix # Store World Matrix for objects if dupli_world_matrix is not None: node.matrix_world = dupli_world_matrix elif node.blender_type in [ VExportNode.OBJECT, VExportNode.COLLECTION, VExportNode.ARMATURE, VExportNode.CAMERA, VExportNode.LIGHT ]: # Matrix World of object is expressed based on collection instance objects are # So real world matrix is collection world_matrix @ "world_matrix" of object node.matrix_world = parent_coll_matrix_world @ blender_object.matrix_world.copy( ) if node.blender_type == VExportNode.CAMERA and self.export_settings[ gltf2_blender_export_keys.CAMERAS]: correction = Quaternion((2**0.5 / 2, -2**0.5 / 2, 0.0, 0.0)) node.matrix_world @= correction.to_matrix().to_4x4() elif node.blender_type == VExportNode.LIGHT and self.export_settings[ gltf2_blender_export_keys.LIGHTS]: correction = Quaternion((2**0.5 / 2, -2**0.5 / 2, 0.0, 0.0)) node.matrix_world @= correction.to_matrix().to_4x4() elif node.blender_type == VExportNode.BONE: node.matrix_world = self.nodes[ node.armature].matrix_world @ blender_bone.matrix axis_basis_change = Matrix.Identity(4) if self.export_settings[gltf2_blender_export_keys.YUP]: axis_basis_change = Matrix( ((1.0, 0.0, 0.0, 0.0), (0.0, 0.0, 1.0, 0.0), (0.0, -1.0, 0.0, 0.0), (0.0, 0.0, 0.0, 1.0))) node.matrix_world = node.matrix_world @ axis_basis_change # Force empty ? # For duplis, if instancer is not display, we should create an empty if blender_object.is_instancer is True and blender_object.show_instancer_for_render is False: node.force_as_empty = True # Storing this node self.add_node(node) ###### Manage children ###### # standard children if blender_bone is None and blender_object.is_instancer is False: for child_object in blender_object.children: if child_object.parent_bone: # Object parented to bones # Will be manage later continue else: # Classic parenting self.recursive_node_traverse(child_object, None, node.uuid, parent_coll_matrix_world) # Collections if blender_object.instance_type == 'COLLECTION' and blender_object.instance_collection: for dupli_object in blender_object.instance_collection.objects: if dupli_object.parent is not None: continue self.recursive_node_traverse(dupli_object, None, node.uuid, node.matrix_world) # Armature : children are bones with no parent if blender_object.type == "ARMATURE" and blender_bone is None: for b in [ b for b in blender_object.pose.bones if b.parent is None ]: self.recursive_node_traverse(blender_object, b, node.uuid, parent_coll_matrix_world, node.uuid) # Bones if blender_object.type == "ARMATURE" and blender_bone is not None: for b in blender_bone.children: self.recursive_node_traverse(blender_object, b, node.uuid, parent_coll_matrix_world, armature_uuid) # Object parented to bone if blender_bone is not None: for child_object in [ c for c in blender_object.children if c.parent_bone is not None and c.parent_bone == blender_bone.name ]: self.recursive_node_traverse(child_object, None, node.uuid, parent_coll_matrix_world) # Duplis if blender_object.is_instancer is True and blender_object.instance_type != 'COLLECTION': depsgraph = bpy.context.evaluated_depsgraph_get() for (dupl, mat) in [(dup.object.original, dup.matrix_world.copy()) for dup in depsgraph.object_instances if dup.parent and id(dup.parent.original) == id(blender_object)]: self.recursive_node_traverse(dupl, None, node.uuid, parent_coll_matrix_world, dupli_world_matrix=mat)
BILLBOARDMODE_ALL = '7' # used in Mesh constructor, defined in BABYLON.PhysicsImpostor SPHERE_IMPOSTER = 1 BOX_IMPOSTER = 2 #PLANE_IMPOSTER = 3 MESH_IMPOSTER = 4 CAPSULE_IMPOSTER = 5 CONE_IMPOSTER = 6 CYLINDER_IMPOSTER = 7 PARTICLE_IMPOSTER = 8 SHAPE_KEY_GROUPS_ALLOWED = False ZERO_V = Vector((0, 0, 0)) ZERO_Q = Quaternion((1, 0, 0, 0)) #=============================================================================== class Mesh(FCurveAnimatable): def __init__(self, bpyMesh, scene, exporter): self.scene = scene self.name = bpyMesh.name Logger.log('processing begun of mesh: ' + self.name) self.define_animations( bpyMesh, True, True, True) #Should animations be done when forcedParent self.isVisible = bpyMesh.visible_get() self.isPickable = not bpyMesh.hide_select self.isEnabled = not bpyMesh.hide_render
def _add_bone_to_scene(self, scene_node, armature): # Let's get all the data collection out of the way joint_index = scene_node.Attribute('JOINTINDEX', int) joint_binding_data = self.mesh_binding_data[ 'JointBindings'][joint_index] inv_bind_matrix = joint_binding_data['InvBindMatrix'] inv_bind_matrix = Matrix([inv_bind_matrix[:4], inv_bind_matrix[4:8], inv_bind_matrix[8:12], inv_bind_matrix[12:]]) inv_bind_matrix.transpose() bind_trans = joint_binding_data['BindTranslate'] bind_rot = joint_binding_data['BindRotate'] bind_sca = joint_binding_data['BindScale'] # Assign the bind matrix so we can do easy lookup of it later for # applying animations. # Ironically, the inverse bind matrix is strored uninverted, and the # bind matrix is stored inverted... self.inv_bind_matrices[scene_node.Name] = inv_bind_matrix # Let's create the bone now # All changes to Bones have to be in EDIT mode or _bad things happen_ with edit_object(armature) as data: bone = data.edit_bones.new(scene_node.Name) bone.use_inherit_rotation = True bone.use_inherit_scale = True self.scn.objects[scene_node.Name]['bind_data'] = ( Vector(bind_trans[:3]), Quaternion((bind_rot[3], bind_rot[0], bind_rot[1], bind_rot[2])), Vector(bind_sca[:3])) """ self.bind_matrices[scene_node.Name] = (Vector(bind_trans[:3]), Quaternion((bind_rot[3], bind_rot[0], bind_rot[1], bind_rot[2])), Vector(bind_sca[:3])) """ if scene_node.parent.Type == 'JOINT': bone.matrix = self.inv_bind_matrices[scene_node.parent.Name] bone.tail = inv_bind_matrix.inverted().to_translation() if bone.length == 0: bone.tail = bone.head + Vector([0, 10 ** (-4), 0]) if scene_node.parent.Type == 'JOINT': bone.parent = armature.data.edit_bones[scene_node.parent.Name] bone.use_connect = True # NMS defines some bones used in animations with 0 transform, eg. # Toy Cube. # This causes bone creation to fail, we need to move the tail # slightly. # Note that MMD Tools would have to deal with this too. while scene_node: if scene_node.Transform['Trans'] != (0.0, 0.0, 0.0): break bone.tail += Vector([0, 0, 10 ** (-4)]) scene_node = scene_node.parent
class HierarchyPivot(Struct): name = "" parentID = -1 position = Vector((0.0, 0.0, 0.0)) eulerAngles = Vector((0.0, 0.0, 0.0)) rotation = Quaternion((1.0, 0.0, 0.0, 0.0))
# Animate center of the sphere. center = Vector((0.0, 0.0, 0.0)) startcenter = Vector((0.0, -4.0, 0.0)) stopcenter = Vector((0.0, 4.0, 0.0)) # Rotate cubes around the surface of the sphere. pt = Vector((0.0, 0.0, 0.0)) rotpt = Vector((0.0, 0.0, 0.0)) # Change the axis of rotation for the point. baseaxis = Vector((0.0, 1.0, 0.0)) axis = Vector((0.0, 0.0, 0.0)) # Slerp between two rotations for each cube. startrot = Quaternion((0.0, 1.0, 0.0), pi) stoprot = Quaternion((1.0, 0.0, 0.0), pi * 1.5) currot = Quaternion() for i in range(0, latitude, 1): iprc = i * invlatitude phi = pi * (i + 1) * invlatitude sinphi = sin(phi) cosphi = cos(phi) rad = 0.01 + sz * abs(sinphi) * 0.99 pt.z = cosphi * diameter for j in range(0, longitude, 1): jprc = j * invlongitude
def convert_quat(q): return Quaternion([q[3], q[0], q[1], q[2]])
def load_nodes(self, offset, export_order, parent=None): self.mdl.seek(MDL_OFFSET + offset) type_flags = self.mdl.get_uint16() node_number = self.mdl.get_uint16() name_index = self.mdl.get_uint16() self.mdl.skip(2) # padding off_root = self.mdl.get_uint32() off_parent = self.mdl.get_uint32() position = [self.mdl.get_float() for _ in range(3)] orientation = [self.mdl.get_float() for _ in range(4)] children_arr = self.get_array_def() controller_arr = self.get_array_def() controller_data_arr = self.get_array_def() name = self.names[name_index] node_type = self.get_node_type(type_flags) node = self.new_node(name, node_type) self.node_by_number[node_number] = node if parent: node.parent = parent node.from_root = parent.from_root node.node_number = node_number node.export_order = export_order node.position = position node.orientation = orientation node.from_root = node.from_root @ Matrix.Translation(Vector(position)) @ Quaternion(orientation).to_matrix().to_4x4() if offset == self.off_anim_root: self.model.animroot = name if type_flags & NODE_LIGHT: flare_radius = self.mdl.get_float() unknown_arr = self.get_array_def() flare_size_arr = self.get_array_def() flare_position_arr = self.get_array_def() flare_color_shift_arr = self.get_array_def() flare_tex_name_arr = self.get_array_def() light_priority = self.mdl.get_int32() ambient_only = self.mdl.get_uint32() dynamic_type = self.mdl.get_uint32() affect_dynamic = self.mdl.get_uint32() shadow = self.mdl.get_uint32() flare = self.mdl.get_uint32() fading_light = self.mdl.get_uint32() node.shadow = shadow node.lightpriority = light_priority node.ambientonly = ambient_only node.dynamictype = dynamic_type node.affectdynamic = affect_dynamic node.fadinglight = fading_light node.lensflares = flare node.flareradius = flare_radius node.flare_list = FlareList() if type_flags & NODE_EMITTER: dead_space = self.mdl.get_float() blast_radius = self.mdl.get_float() blast_length = self.mdl.get_float() num_branches = self.mdl.get_uint32() ctrl_point_smoothing = self.mdl.get_float() x_grid = self.mdl.get_uint32() y_grid = self.mdl.get_uint32() spawn_type = self.mdl.get_uint32() update = self.mdl.get_c_string_up_to(32) emitter_render = self.mdl.get_c_string_up_to(32) blend = self.mdl.get_c_string_up_to(32) texture = self.mdl.get_c_string_up_to(32) chunk_name = self.mdl.get_c_string_up_to(16) twosided_tex = self.mdl.get_uint32() loop = self.mdl.get_uint32() render_order = self.mdl.get_uint16() frame_blending = self.mdl.get_uint8() depth_texture_name = self.mdl.get_c_string_up_to(32) self.mdl.skip(1) # padding flags = self.mdl.get_uint32() # object data node.deadspace = dead_space node.blastradius = blast_radius node.blastlength = blast_length node.num_branches = num_branches node.controlptsmoothing = ctrl_point_smoothing node.xgrid = x_grid node.ygrid = y_grid node.spawntype = spawn_type node.update = update node.emitter_render = emitter_render node.blend = blend node.texture = texture node.chunk_name = chunk_name node.twosidedtex = twosided_tex != 0 node.loop = loop != 0 node.renderorder = render_order node.frame_blending = frame_blending != 0 node.depth_texture_name = depth_texture_name if len(depth_texture_name) > 0 and depth_texture_name.lower() != "null" else defines.NULL # flags node.p2p = flags & EMITTER_FLAG_P2P != 0 node.p2p_sel = flags & EMITTER_FLAG_P2P_SEL != 0 node.affected_by_wind = flags & EMITTER_FLAG_AFFECTED_WIND != 0 node.tinted = flags & EMITTER_FLAG_TINTED != 0 node.bounce = flags & EMITTER_FLAG_BOUNCE != 0 node.random = flags & EMITTER_FLAG_RANDOM != 0 node.inherit = flags & EMITTER_FLAG_INHERIT != 0 node.inheritvel = flags & EMITTER_FLAG_INHERIT_VEL != 0 node.inherit_local = flags & EMITTER_FLAG_INHERIT_LOCAL != 0 node.splat = flags & EMITTER_FLAG_SPLAT != 0 node.inherit_part = flags & EMITTER_FLAG_INHERIT_PART != 0 node.depth_texture = flags & EMITTER_FLAG_DEPTH_TEXTURE != 0 if type_flags & NODE_REFERENCE: ref_model = self.mdl.get_c_string_up_to(32) reattachable = self.mdl.get_uint32() node.refmodel = ref_model node.reattachable = reattachable if type_flags & NODE_MESH: fn_ptr1 = self.mdl.get_uint32() fn_ptr2 = self.mdl.get_uint32() face_arr = self.get_array_def() bouding_box = [self.mdl.get_float() for _ in range(6)] radius = self.mdl.get_float() average = [self.mdl.get_float() for _ in range(3)] diffuse = [self.mdl.get_float() for _ in range(3)] ambient = [self.mdl.get_float() for _ in range(3)] transparency_hint = self.mdl.get_uint32() bitmap = self.mdl.get_c_string_up_to(32) bitmap2 = self.mdl.get_c_string_up_to(32) bitmap3 = self.mdl.get_c_string_up_to(12) bitmap4 = self.mdl.get_c_string_up_to(12) index_count_arr = self.get_array_def() index_offset_arr = self.get_array_def() inv_counter_arr = self.get_array_def() self.mdl.skip(3 * 4) # unknown self.mdl.skip(8) # saber unknown animate_uv = self.mdl.get_uint32() uv_dir_x = self.mdl.get_float() uv_dir_y = self.mdl.get_float() uv_jitter = self.mdl.get_float() uv_jitter_speed = self.mdl.get_float() mdx_data_size = self.mdl.get_uint32() mdx_data_bitmap = self.mdl.get_uint32() off_mdx_verts = self.mdl.get_uint32() off_mdx_normals = self.mdl.get_uint32() off_mdx_colors = self.mdl.get_uint32() off_mdx_uv1 = self.mdl.get_uint32() off_mdx_uv2 = self.mdl.get_uint32() off_mdx_uv3 = self.mdl.get_uint32() off_mdx_uv4 = self.mdl.get_uint32() off_mdx_tan_space1 = self.mdl.get_uint32() off_mdx_tan_space2 = self.mdl.get_uint32() off_mdx_tan_space3 = self.mdl.get_uint32() off_mdx_tan_space4 = self.mdl.get_uint32() num_verts = self.mdl.get_uint16() num_textures = self.mdl.get_uint16() has_lightmap = self.mdl.get_uint8() rotate_texture = self.mdl.get_uint8() background_geometry = self.mdl.get_uint8() shadow = self.mdl.get_uint8() beaming = self.mdl.get_uint8() render = self.mdl.get_uint8() if self.tsl: dirt_enabled = self.mdl.get_uint8() self.mdl.skip(1) # padding dirt_texture = self.mdl.get_uint16() dirt_coord_space = self.mdl.get_uint16() hide_in_holograms = self.mdl.get_uint8() self.mdl.skip(1) # padding self.mdl.skip(2) # padding total_area = self.mdl.get_float() self.mdl.skip(4) # padding mdx_offset = self.mdl.get_uint32() if not self.xbox: off_vert_arr = self.mdl.get_uint32() node.render = render node.shadow = shadow node.lightmapped = has_lightmap node.beaming = beaming node.tangentspace = 1 if mdx_data_bitmap & MDX_FLAG_TANGENT1 else 0 node.rotatetexture = rotate_texture node.background_geometry = background_geometry node.animateuv = animate_uv node.uvdirectionx = uv_dir_x node.uvdirectiony = uv_dir_y node.uvjitter = uv_jitter node.uvjitterspeed = uv_jitter_speed node.transparencyhint = transparency_hint node.ambient = ambient node.diffuse = diffuse node.center = average if len(bitmap) > 0 and bitmap.lower() != "null": node.bitmap = bitmap if len(bitmap2) > 0 and bitmap2.lower() != "null": node.bitmap2 = bitmap2 if self.tsl: node.dirt_enabled = dirt_enabled node.dirt_texture = dirt_texture node.dirt_worldspace = dirt_coord_space node.hologram_donotdraw = hide_in_holograms if type_flags & NODE_SKIN: unknown_arr = self.get_array_def() off_mdx_bone_weights = self.mdl.get_uint32() off_mdx_bone_indices = self.mdl.get_uint32() off_bonemap = self.mdl.get_uint32() num_bonemap = self.mdl.get_uint32() qbone_arr = self.get_array_def() tbone_arr = self.get_array_def() garbage_arr = self.get_array_def() bone_indices = [self.mdl.get_uint16() for _ in range(16)] self.mdl.skip(4) # padding if type_flags & NODE_DANGLY: constraint_arr = self.get_array_def() displacement = self.mdl.get_float() tightness = self.mdl.get_float() period = self.mdl.get_float() off_vert_data = self.mdl.get_uint32() node.displacement = displacement node.period = period node.tightness = tightness if type_flags & NODE_AABB: off_root_aabb = self.mdl.get_uint32() self.load_aabb(off_root_aabb) if type_flags & NODE_SABER: off_saber_verts = self.mdl.get_uint32() off_saber_uv = self.mdl.get_uint32() off_saber_normals = self.mdl.get_uint32() inv_count1 = self.mdl.get_uint32() inv_count2 = self.mdl.get_uint32() if controller_arr.count > 0: controllers = self.load_controllers(controller_arr, controller_data_arr) if type_flags & NODE_MESH: node.alpha = controllers[CTRL_MESH_ALPHA][0][1] if CTRL_MESH_ALPHA in controllers else 1.0 node.scale = controllers[CTRL_MESH_SCALE][0][1] if CTRL_MESH_SCALE in controllers else 1.0 node.selfillumcolor = controllers[CTRL_MESH_SELFILLUMCOLOR][0][1:] if CTRL_MESH_SELFILLUMCOLOR in controllers else [0.0] * 3 elif type_flags & NODE_LIGHT: node.radius = controllers[CTRL_LIGHT_RADIUS][0][1] if CTRL_LIGHT_RADIUS in controllers else 1.0 node.multiplier = controllers[CTRL_LIGHT_MULTIPLIER][0][1] if CTRL_LIGHT_MULTIPLIER in controllers else 1.0 node.color = controllers[CTRL_LIGHT_COLOR][0][1:] if CTRL_LIGHT_COLOR in controllers else [1.0] * 3 elif type_flags & NODE_EMITTER: for val, key, dim in EMITTER_CONTROLLER_KEYS: if val not in controllers: continue if dim == 1: setattr(node, key, controllers[val][0][1]) else: setattr(node, key, controllers[val][0][1:dim+1]) if type_flags & NODE_LIGHT: self.mdl.seek(MDL_OFFSET + flare_size_arr.offset) node.flare_list.sizes = [self.mdl.get_float() for _ in range(flare_size_arr.count)] self.mdl.seek(MDL_OFFSET + flare_position_arr.offset) node.flare_list.positions = [self.mdl.get_float() for _ in range(flare_position_arr.count)] self.mdl.seek(MDL_OFFSET + flare_color_shift_arr.offset) for _ in range(flare_color_shift_arr.count): color_shift = [self.mdl.get_float() for _ in range(3)] node.flare_list.colorshifts.append(color_shift) self.mdl.seek(MDL_OFFSET + flare_tex_name_arr.offset) tex_name_offsets = [self.mdl.get_uint32() for _ in range(flare_tex_name_arr.count)] for tex_name_offset in tex_name_offsets: self.mdl.seek(MDL_OFFSET + tex_name_offset) node.flare_list.textures.append(self.mdl.get_c_string()) if type_flags & NODE_SKIN: if num_bonemap > 0: self.mdl.seek(MDL_OFFSET + off_bonemap) if self.xbox: bonemap = [self.mdl.get_uint16() for _ in range(num_bonemap)] else: bonemap = [int(self.mdl.get_float()) for _ in range(num_bonemap)] else: bonemap = [] node_by_bone = dict() for node_idx, bone_idx in enumerate(bonemap): if bone_idx == -1: continue node_by_bone[bone_idx] = node_idx if type_flags & NODE_MESH: node.facelist = FaceList() if type_flags & NODE_SABER: for face in SABER_FACES: node.facelist.vertices.append(face) node.facelist.uv.append(face) node.facelist.materials.append(0) elif face_arr.count > 0: self.mdl.seek(MDL_OFFSET + face_arr.offset) for _ in range(face_arr.count): normal = [self.mdl.get_float() for _ in range(3)] plane_distance = self.mdl.get_float() material_id = self.mdl.get_uint32() adjacent_faces = [self.mdl.get_uint16() for _ in range(3)] vert_indices = [self.mdl.get_uint16() for _ in range(3)] node.facelist.vertices.append(tuple(vert_indices)) node.facelist.uv.append(tuple(vert_indices)) node.facelist.materials.append(material_id) if index_count_arr.count > 0: self.mdl.seek(MDL_OFFSET + index_count_arr.offset) num_indices = self.mdl.get_uint32() if index_offset_arr.count > 0: self.mdl.seek(MDL_OFFSET + index_offset_arr.offset) off_indices = self.mdl.get_uint32() node.verts = [] node.uv1 = [] node.uv2 = [] node.weights = [] if type_flags & NODE_SABER: saber_verts = [] self.mdl.seek(MDL_OFFSET + off_saber_verts) for i in range(NUM_SABER_VERTS): saber_verts.append([self.mdl.get_float() for _ in range(3)]) saber_tverts = [] self.mdl.seek(MDL_OFFSET + off_saber_uv) for i in range(NUM_SABER_VERTS): saber_tverts.append([self.mdl.get_float() for _ in range(2)]) saber_normals = [] self.mdl.seek(MDL_OFFSET + off_saber_normals) for i in range(NUM_SABER_VERTS): saber_normals.append([self.mdl.get_float() for _ in range(3)]) for i in range(8): node.verts.append(saber_verts[i]) node.normals.append(saber_normals[i]) node.uv1.append(saber_tverts[i]) for i in range(88, 96): node.verts.append(saber_verts[i]) node.normals.append(saber_normals[i]) node.uv1.append(saber_tverts[i]) elif mdx_data_size > 0: for i in range(num_verts): self.mdx.seek(mdx_offset + i * mdx_data_size + off_mdx_verts) node.verts.append(tuple([self.mdx.get_float() for _ in range(3)])) if mdx_data_bitmap & MDX_FLAG_NORMAL: self.mdx.seek(mdx_offset + i * mdx_data_size + off_mdx_normals) if self.xbox: comp = self.mdx.get_uint32() node.normals.append(self.decompress_vector_xbox(comp)) else: node.normals.append(tuple([self.mdx.get_float() for _ in range(3)])) if mdx_data_bitmap & MDX_FLAG_UV1: self.mdx.seek(mdx_offset + i * mdx_data_size + off_mdx_uv1) node.uv1.append(tuple([self.mdx.get_float() for _ in range(2)])) if mdx_data_bitmap & MDX_FLAG_UV2: self.mdx.seek(mdx_offset + i * mdx_data_size + off_mdx_uv2) node.uv2.append(tuple([self.mdx.get_float() for _ in range(2)])) if type_flags & NODE_SKIN: self.mdx.seek(mdx_offset + i * mdx_data_size + off_mdx_bone_weights) bone_weights = [self.mdx.get_float() for _ in range(4)] self.mdx.seek(mdx_offset + i * mdx_data_size + off_mdx_bone_indices) if self.xbox: bone_indices = [self.mdx.get_uint16() for _ in range(4)] else: bone_indices = [int(self.mdx.get_float()) for _ in range(4)] vert_weights = [] for i in range(4): bone_idx = bone_indices[i] if bone_idx == -1: continue node_idx = node_by_bone[bone_idx] node_name = self.node_names[node_idx] vert_weights.append([node_name, bone_weights[i]]) node.weights.append(vert_weights) if type_flags & NODE_DANGLY: self.mdl.seek(MDL_OFFSET + constraint_arr.offset) node.constraints = [self.mdl.get_float() for _ in range(constraint_arr.count)] self.mdl.seek(MDL_OFFSET + children_arr.offset) child_offsets = [self.mdl.get_uint32() for _ in range(children_arr.count)] for child_idx, off_child in enumerate(child_offsets): child = self.load_nodes(off_child, child_idx, node) node.children.append(child) return node
def create_object(mu, muobj, parent): if muobj in mu.imported_objects: # the object has already been processed (probably an armature) return None mu.imported_objects.add(muobj) xform = muobj.transform component_data = [] for component in muobj.components: if type(component) in type_handlers: data = type_handlers[type(component)](mu, muobj, component, xform.name) if data: component_data.append(data) else: print(f"unhandled component {component}") if muobj.transform.name == 'SkirtArmature': print(dir(muobj)) if hasattr(muobj, "bone") and not component_data and not muobj.force_import: return None if hasattr(muobj, "armature_obj") or len(component_data) != 1: #empty or multiple components obj = None if hasattr(muobj, "armature_obj"): obj = muobj.armature_obj set_transform(obj, muobj.transform) mu.collection.objects.link(obj) if not obj: #if a mesh is present, use it for the main object for component in component_data: if component[0] == "mesh": component_data.remove(component) component = (None,) + component[1:] obj = create_component_object(mu.collection, component, xform.name, xform) break if not obj: obj = create_data_object(mu.collection, xform.name, None, xform) for component in component_data: cobj = create_component_object(mu.collection, component, xform.name, None) cobj.parent = obj else: component = component_data[0] component = (None,) + component[1:] obj = create_component_object(mu.collection, component, xform.name, xform) if obj.name not in mu.collection.objects: mu.collection.objects.link(obj) if not obj.data: if xform.name[:5] == "node_": #print(name, xform.name[:5]) obj.empty_display_type = 'SINGLE_ARROW' #print(obj.empty_display_type) # Blender's empties use the +Z axis for single-arrow # display, so that is the most natural orientation for # nodes in blender. # However, KSP uses the transform's +Z (Unity) axis which # is Blender's +Y, so rotate -90 degrees around local X to # go from KSP to Blender #print(obj.rotation_quaternion) rot = Quaternion((0.5**0.5, -(0.5**0.5), 0, 0)) obj.rotation_quaternion @= rot #print(obj.rotation_quaternion) muobj.bobj = obj if hasattr(muobj, "bone") and hasattr(muobj, "armature"): set_transform(obj, None) parent_to_bone(obj, muobj.armature.armature_obj, muobj.bone) else: obj.parent = parent if hasattr(muobj, "tag_and_layer"): obj.muproperties.tag = muobj.tag_and_layer.tag obj.muproperties.layer = muobj.tag_and_layer.layer for child in muobj.children: create_object(mu, child, obj) if hasattr(muobj, "animation"): for clip in muobj.animation.clips: create_action(mu, muobj.path, clip) return obj
def set_convert_functions(gltf): if bpy.app.debug_value != 100: # Unit conversion factor in (Blender units) per meter u = 1.0 / bpy.context.scene.unit_settings.scale_length # glTF Y-Up space --> Blender Z-up space # X,Y,Z --> X,-Z,Y def convert_loc(x): return u * Vector([x[0], -x[2], x[1]]) def convert_quat(q): return Quaternion([q[3], q[0], -q[2], q[1]]) def convert_scale(s): return Vector([s[0], s[2], s[1]]) def convert_matrix(m): return Matrix([ [m[0], -m[8], m[4], m[12] * u], [-m[2], m[10], -m[6], -m[14] * u], [m[1], -m[9], m[5], m[13] * u], [m[3] / u, -m[11] / u, m[7] / u, m[15]], ]) # Batch versions operate in place on a numpy array def convert_locs_batch(locs): # x,y,z -> x,-z,y locs[:, [1, 2]] = locs[:, [2, 1]] locs[:, 1] *= -1 # Unit conversion if u != 1: locs *= u def convert_normals_batch(ns): ns[:, [1, 2]] = ns[:, [2, 1]] ns[:, 1] *= -1 # Correction for cameras and lights. # glTF: right = +X, forward = -Z, up = +Y # glTF after Yup2Zup: right = +X, forward = +Y, up = +Z # Blender: right = +X, forward = -Z, up = +Y # Need to carry Blender --> glTF after Yup2Zup gltf.camera_correction = Quaternion( (2**0.5 / 2, 2**0.5 / 2, 0.0, 0.0)) else: def convert_loc(x): return Vector(x) def convert_quat(q): return Quaternion([q[3], q[0], q[1], q[2]]) def convert_scale(s): return Vector(s) def convert_matrix(m): return Matrix([m[0::4], m[1::4], m[2::4], m[3::4]]) def convert_locs_batch(_locs): return def convert_normals_batch(_ns): return # Same convention, no correction needed. gltf.camera_correction = None gltf.loc_gltf_to_blender = convert_loc gltf.locs_batch_gltf_to_blender = convert_locs_batch gltf.quaternion_gltf_to_blender = convert_quat gltf.normals_batch_gltf_to_blender = convert_normals_batch gltf.scale_gltf_to_blender = convert_scale gltf.matrix_gltf_to_blender = convert_matrix
def do_import(path, DELETE_TOP_BONE=True): # get scene scn = bpy.context.scene if scn == None: return "No scene to import to!" # open the file try: file = open(path, 'r') except IOError: return "Failed to open the file!" try: if not path.endswith(".cn6"): raise IOError except IOError: return "Must be an cn6 file!" # Load Armature try: lines = getNextLine(file).split() if len(lines) != 1 or lines[0] != "skeleton": raise ValueError except ValueError: return "File invalid!" # Before adding any meshes or armatures go into Object mode. if bpy.ops.object.mode_set.poll(): bpy.ops.object.mode_set(mode='OBJECT') armature = bpy.data.armatures.new("Armature") armOb = bpy.data.objects.new("ArmatureObject", armature) armature.draw_type = 'STICK' scn.objects.link(armOb) scn.objects.active = armOb # read bones boneNames = [] bpy.ops.object.editmode_toggle() bpy.types.EditBone.rot_matrix = bpy.props.FloatVectorProperty( name="Rot Matrix", size=9) currentLine = "" boneCount = 0 boneNameDict = [] parentBoneIds = [] positions = [] quaternions = [] while (not currentLine.startswith('meshes')): currentLine = getNextLine(file) if (not currentLine.startswith('meshes')): lines = shlex.split(currentLine) boneNameDict.append(lines[1]) parentBoneIds.append(int(lines[2])) positions.append( [float(lines[3]), float(lines[4]), float(lines[5])]) quaternions.append([ float(lines[6]), float(lines[7]), float(lines[8]), float(lines[9]) ]) boneCount = boneCount + 1 print(boneNameDict) for i in range(boneCount): # read name fullName = boneNameDict[i] boneNames.append(fullName) bone = armature.edit_bones.new(fullName) # read parent if parentBoneIds[i] >= 0: parentBoneName = boneNameDict[ parentBoneIds[i]] #getNextLine(file)[1:-1] bone.parent = armature.bones.data.edit_bones[parentBoneName] pos = positions[i] quat = quaternions[i] # Granny Rotation Quaternions are stored X,Y,Z,W but Blender uses W,X,Y,Z quaternion = Quaternion((quat[3], quat[0], quat[1], quat[2])) rotMatrix = quaternion.to_matrix() rotMatrix.transpose( ) # Need to transpose to get same behaviour as 2.49 script print("Bone Data") print(fullName) print(pos) print(rotMatrix) boneLength = 3 # set position and orientation if bone.parent: bone_parent_matrix = Matrix([[ bone.parent.rot_matrix[0], bone.parent.rot_matrix[1], bone.parent.rot_matrix[2] ], [ bone.parent.rot_matrix[3], bone.parent.rot_matrix[4], bone.parent.rot_matrix[5] ], [ bone.parent.rot_matrix[6], bone.parent.rot_matrix[7], bone.parent.rot_matrix[8] ]]) bone.head = Vector(pos) * bone_parent_matrix + bone.parent.head bone.tail = bone.head + Vector([boneLength, 0, 0]) tempM = rotMatrix * bone_parent_matrix bone.rot_matrix = [ tempM[0][0], tempM[0][1], tempM[0][2], tempM[1][0], tempM[1][1], tempM[1][2], tempM[2][0], tempM[2][1], tempM[2][2] ] bone.matrix = Matrix([[ -bone.rot_matrix[3], bone.rot_matrix[0], bone.rot_matrix[6], bone.head[0] ], [ -bone.rot_matrix[4], bone.rot_matrix[1], bone.rot_matrix[7], bone.head[1] ], [ -bone.rot_matrix[5], bone.rot_matrix[2], bone.rot_matrix[8], bone.head[2] ], [0, 0, 0, 1]]) else: bone.head = Vector(pos) bone.tail = bone.head + Vector([boneLength, 0, 0]) bone.rot_matrix = [ rotMatrix[0][0], rotMatrix[0][1], rotMatrix[0][2], rotMatrix[1][0], rotMatrix[1][1], rotMatrix[1][2], rotMatrix[2][0], rotMatrix[2][1], rotMatrix[2][2] ] bone.matrix = Matrix([[ -bone.rot_matrix[3], bone.rot_matrix[0], bone.rot_matrix[6], bone.head[0] ], [ -bone.rot_matrix[4], bone.rot_matrix[1], bone.rot_matrix[7], bone.head[1] ], [ -bone.rot_matrix[5], bone.rot_matrix[2], bone.rot_matrix[8], bone.head[2] ], [0, 0, 0, 1]]) # Roll fix for all bones for bone in armature.bones.data.edit_bones: roll = bone.roll bone.roll = roll - radians(90.0) # read the number of meshes try: lines = currentLine if not lines.startswith('meshes:'): raise ValueError numMeshes = int(lines.replace('meshes:', '')) if numMeshes < 0: raise ValueError except ValueError: return "Number of meshes is invalid!" # read meshes boneIds = [[], [], [], [], [], [], [], []] boneWeights = [[], [], [], [], [], [], [], []] meshVertexGroups = {} vCount = 0 meshes = [] meshObjects = [] print('Num Meshes') print(numMeshes) for i in range(numMeshes): while (not currentLine.startswith('mesh:')): currentLine = getNextLine(file) lines = currentLine.split(':') meshName = lines[1][1:-1] + '#M' meshes.append(bpy.data.meshes.new(meshName)) # read materials materialNames = [] while (not currentLine.startswith('vertices')): currentLine = getNextLine(file) if (not currentLine.startswith('materials') and not currentLine.startswith('vertices')): materialNames.append(currentLine[1:-1]) print("materialNames") print(materialNames) # read vertices coords = [] normals = [] tangents = [] binormals = [] uvs = [] uvs2 = [] uvs3 = [] numVerts = 0 normalsTangentsBinormals = [] originalTangentsBinormals = {} nonMatchingNormalTangentBinormal = True while (not currentLine.startswith('triangles')): currentLine = getNextLine(file) if (not currentLine.startswith('vertices') and not currentLine.startswith('triangles')): lines = currentLine.split() if len(lines) != 34: raise ValueError coords.append( [float(lines[0]), float(lines[1]), float(lines[2])]) normals.append( [float(lines[3]), float(lines[4]), float(lines[5])]) tangents.append( [float(lines[6]), float(lines[7]), float(lines[8])]) binormals.append( [float(lines[9]), float(lines[10]), float(lines[11])]) if (numVerts < 10): #print("Normal/Tangent/Binormal Check") #print(abs(float(lines[3]) - float(lines[6]))) #print(abs(float(lines[4]) - float(lines[7]))) #print(abs(float(lines[5]) - float(lines[8]))) if (abs(float(lines[3]) - float(lines[6])) < 0.000001 and abs(float(lines[4]) - float(lines[7])) < 0.000001 and abs(float(lines[5]) - float(lines[8])) < 0.000001 and abs(float(lines[3]) - float(lines[9])) < 0.000001 and abs(float(lines[4]) - float(lines[10])) < 0.000001 and abs(float(lines[5]) - float(lines[11])) < 0.000001): nonMatchingNormalTangentBinormal = False else: nonMatchingNormalTangentBinormal = True uvs.append([float(lines[12]), 1 - float(lines[13])]) uvs2.append([float(lines[14]), 1 - float(lines[15])]) uvs3.append([float(lines[16]), 1 - float(lines[17])]) normalsTangentsBinormals.append([ float(lines[3]), float(lines[4]), float(lines[5]), float(lines[6]), float(lines[7]), float(lines[8]), float(lines[9]), float(lines[10]), float(lines[11]) ]) boneIds[0].append(int(lines[18])) boneIds[1].append(int(lines[19])) boneIds[2].append(int(lines[20])) boneIds[3].append(int(lines[21])) boneIds[4].append(int(lines[22])) boneIds[5].append(int(lines[23])) boneIds[6].append(int(lines[24])) boneIds[7].append(int(lines[25])) boneWeights[0].append(float(lines[26])) boneWeights[1].append(float(lines[27])) boneWeights[2].append(float(lines[28])) boneWeights[3].append(float(lines[29])) boneWeights[4].append(float(lines[30])) boneWeights[5].append(float(lines[31])) boneWeights[6].append(float(lines[32])) boneWeights[7].append(float(lines[33])) meshVertexGroups[ vCount] = meshName # uses the long mesh name - may be > 21 chars numVerts += 1 #print ('nonMatchingNormalTangentBinormal:%s' % nonMatchingNormalTangentBinormal) meshes[i].vertices.add(len(coords)) meshes[i].vertices.foreach_set("co", unpack_list(coords)) meshOb = bpy.data.objects.new(meshName, meshes[i]) for materialName in materialNames: material = bpy.data.materials.new(materialName) meshOb.data.materials.append(material) if (nonMatchingNormalTangentBinormal): meshOb.vertex_groups.new("VERTEX_KEYS") keyVertexGroup = meshOb.vertex_groups.get("VERTEX_KEYS") for v, vertex in enumerate(meshes[i].vertices): encoded_weight = (v / 2000000) keyVertexGroup.add([v], encoded_weight, 'ADD') #print ("encoded_weight {}".format(encoded_weight)) #print ("vertex.bevel_weight {}".format(vertex.groups[keyVertexGroup.index].weight)) originalTangentsBinormals[str(v)] = normalsTangentsBinormals[v] meshes[i]['originalTangentsBinormals'] = originalTangentsBinormals # read triangles faces = [] while (not currentLine.startswith('mesh:') and not currentLine.startswith('end')): # read the triangle currentLine = getNextLine(file) if (not currentLine.startswith('mesh:') and not currentLine.startswith('end')): lines = currentLine.split() if len(lines) != 4: # Fourth element is material index raise ValueError v1 = int(lines[0]) v2 = int(lines[1]) v3 = int(lines[2]) mi = int(lines[3]) if v1 < numVerts and v2 < numVerts and v3 < numVerts and mi < len( materialNames): faces.append([v1, v2, v3, mi]) # Create Meshes and import Normals mesh = meshes[i] mesh.loops.add(len(faces) * 3) mesh.polygons.add(len(faces)) loops_vert_idx = [] faces_loop_start = [] faces_loop_total = [] faces_material_index = [] lidx = 0 for f in faces: vidx = [f[0], f[1], f[2]] nbr_vidx = len(vidx) loops_vert_idx.extend(vidx) faces_loop_start.append(lidx) faces_loop_total.append(nbr_vidx) faces_material_index.append(f[3]) lidx += nbr_vidx mesh.loops.foreach_set("vertex_index", loops_vert_idx) mesh.polygons.foreach_set("loop_start", faces_loop_start) mesh.polygons.foreach_set("loop_total", faces_loop_total) mesh.polygons.foreach_set("material_index", faces_material_index) mesh.create_normals_split() mesh.uv_textures.new('UV1') mesh.uv_textures.new('UV2') mesh.uv_textures.new('UV3') for l in mesh.loops: l.normal[:] = normals[l.vertex_index] mesh.uv_layers[0].data[l.index].uv = uvs[l.vertex_index] mesh.uv_layers[1].data[l.index].uv = uvs2[l.vertex_index] mesh.uv_layers[2].data[l.index].uv = uvs3[l.vertex_index] mesh.validate(clean_customdata=False) clnors = array.array('f', [0.0] * (len(mesh.loops) * 3)) mesh.loops.foreach_get("normal", clnors) mesh.polygons.foreach_set("use_smooth", [True] * len(mesh.polygons)) mesh.normals_split_custom_set(tuple(zip(*(iter(clnors), ) * 3))) mesh.use_auto_smooth = True mesh.show_edge_sharp = True #mesh.free_normals_split() ####NORMALS - End meshObjects.append(meshOb) scn.objects.link(meshObjects[i]) for mesh in meshes: mesh.update() # Create Vertex Groups vi = 0 for meshOb in meshObjects: mesh = meshOb.data for mvi, vertex in enumerate(mesh.vertices): for bi in range(boneCount): for j in range(8): if bi == boneIds[j][vi]: name = boneNames[bi] if not meshOb.vertex_groups.get(name): meshOb.vertex_groups.new(name) grp = meshOb.vertex_groups.get(name) normalizedWeight = boneWeights[j][vi] / 255 grp.add([mvi], normalizedWeight, 'ADD') #print('Vertex: %d; Index: %d; Bone: %s; Weight: %f; ' % (mvi, j, name, normalizedWeight)) vi = vi + 1 # Give mesh object an armature modifier, using vertex groups but not envelopes mod = meshOb.modifiers.new('mod_' + mesh.name, 'ARMATURE') mod.object = armOb mod.use_bone_envelopes = False mod.use_vertex_groups = True # Parent Mesh Object to Armature Object meshOb.parent = armOb meshOb.parent_type = 'ARMATURE' if DELETE_TOP_BONE: # Adjust object names, remove top bone for Civ V bone = armature.bones.data.edit_bones[boneNames[0]] while not bone.parent is None: bone = bone.parent print('Found World Bone: %s' % bone.name) name = bone.name armOb.name = name # Delete top bone unless that would leave zero bones if (len(armature.bones.data.edit_bones) > 1): bpy.ops.object.select_pattern(pattern=name) bpy.ops.armature.delete() bpy.ops.object.editmode_toggle() bpy.ops.object.editmode_toggle() bpy.ops.object.editmode_toggle() return ""
def matrix_quaternion(self, q): return Quaternion([q[0], q[1], q[2], q[3]])
def read_bones(self, context, filepath, root): skeleton_node = root.find("Skeleton") if (skeleton_node == None): return None, None bones = [] bones_tag = [] flags_list = [] # LimitRotation and Unk0 have their special meanings, can be deduced if needed when exporting flags_restricted = set(["LimitRotation", "Unk0"]) drawable_name = root.find("Name").text.split(".")[0] bones_node = skeleton_node.find("Bones") armature = context.object bpy.ops.object.mode_set(mode='EDIT') for bones_item in bones_node: name_item = bones_item.find("Name") tag_item = bones_item.find("Tag") parentindex_item = bones_item.find("ParentIndex") flags_item = bones_item.find("Flags") translation_item = bones_item.find("Translation") rotation_item = bones_item.find("Rotation") scale_item = bones_item.find("Scale") quaternion = Quaternion() quaternion.w = float(rotation_item.attrib["w"]) quaternion.x = float(rotation_item.attrib["x"]) quaternion.y = float(rotation_item.attrib["y"]) quaternion.z = float(rotation_item.attrib["z"]) mat_rot = quaternion.to_matrix().to_4x4() trans = Vector() trans.x = float(translation_item.attrib["x"]) trans.y = float(translation_item.attrib["y"]) trans.z = float(translation_item.attrib["z"]) mat_loc = Matrix.Translation(trans) scale = Vector() scale.x = float(scale_item.attrib["x"]) scale.y = float(scale_item.attrib["y"]) scale.z = float(scale_item.attrib["z"]) mat_sca = Matrix.Scale(1, 4, scale) edit_bone = armature.data.edit_bones.new(name_item.text) # edit_bone.bone_id = int(bone_tag.attrib["value"]) if parentindex_item.attrib["value"] != "-1": edit_bone.parent = armature.data.edit_bones[int( parentindex_item.attrib["value"])] # https://github.com/LendoK/Blender_GTA_V_model_importer/blob/master/importer.py edit_bone.head = (0, 0, 0) edit_bone.tail = (0, 0.05, 0) edit_bone.matrix = mat_loc @ mat_rot @ mat_sca if edit_bone.parent != None: edit_bone.matrix = edit_bone.parent.matrix @ edit_bone.matrix if (flags_item != None and flags_item.text != None): flags = flags_item.text.strip().split(", ") flags_list.append(flags) # build a bones lookup table bones.append(name_item.text) bones_tag.append(int(tag_item.get('value'))) bpy.ops.object.mode_set(mode='POSE') for i in range(len(bones)): armature.pose.bones[i].bone.bone_properties.tag = bones_tag[i] for _flag in flags_list[i]: if (_flag in flags_restricted): continue flag = armature.pose.bones[i].bone.bone_properties.flags.add() flag.name = _flag bpy.ops.object.mode_set(mode='OBJECT') return bones, drawable_name
def importWoWOBJ(objectFile, givenParent=None): baseDir, fileName = os.path.split(objectFile) print('Parsing OBJ: ' + fileName) ### OBJ wide material_libs = set() mtlfile = "" verts = [] normals = [] uv = [] meshes = [] ### Per group class OBJMesh: def __init__(self): self.usemtl = "" self.name = "" self.faces = [] curMesh = OBJMesh() meshIndex = -1 with open(objectFile, 'rb') as f: for line in f: line_split = line.split() if not line_split: continue line_start = line_split[0] if line_start == b'mtllib': mtlfile = line_split[1] elif line_start == b'v': verts.append([float(v) for v in line_split[1:]]) elif line_start == b'vn': normals.append([float(v) for v in line_split[1:]]) elif line_start == b'vt': uv.append([float(v) for v in line_split[1:]]) elif line_start == b'f': line_split = line_split[1:] meshes[meshIndex].faces.append( (int(line_split[0].split(b'/')[0]), int(line_split[1].split(b'/')[0]), int(line_split[2].split(b'/')[0]))) elif line_start == b'g': meshIndex += 1 meshes.append(OBJMesh()) meshes[meshIndex].name = line_split[1].decode("utf-8") elif line_start == b'usemtl': meshes[meshIndex].usemtl = line_split[1].decode("utf-8") ## Materials file (.mtl) materials = dict() matname = "" matfile = "" with open(os.path.join(baseDir, mtlfile.decode("utf-8")), 'r') as f: for line in f: line_split = line.split() if not line_split: continue line_start = line_split[0] if line_start == 'newmtl': matname = line_split[1] elif line_start == 'map_Kd': matfile = line_split[1] materials[matname] = os.path.join(baseDir, matfile) if bpy.ops.object.select_all.poll(): bpy.ops.object.select_all(action='DESELECT') # TODO: Better handling for dupes? objname = os.path.basename(objectFile) if objname in bpy.data.objects: objindex = 1 newname = objname while (newname in bpy.data.objects): newname = objname + '.' + str(objindex).rjust(3, '0') objindex += 1 newmesh = bpy.data.meshes.new(objname) obj = bpy.data.objects.new(objname, newmesh) ## Textures # TODO: Must be a better way to do this! materialmapping = dict() for matname, texturelocation in materials.items(): # Import material only once if (matname not in bpy.data.materials): mat = bpy.data.materials.new(name=matname) mat.use_nodes = True mat.blend_method = 'CLIP' principled = PrincipledBSDFWrapper(mat, is_readonly=False) principled.specular = 0.0 principled.base_color_texture.image = load_image(texturelocation) mat.node_tree.links.new( mat.node_tree.nodes['Image Texture'].outputs[1], mat.node_tree.nodes['Principled BSDF'].inputs[18]) obj.data.materials.append(bpy.data.materials[matname]) # TODO: Must be a better way to do this! materialmapping[matname] = len(obj.data.materials) - 1 ## Meshes bm = bmesh.new() i = 0 for v in verts: vert = bm.verts.new(v) vert.normal = normals[i] i = i + 1 bm.verts.ensure_lookup_table() bm.verts.index_update() for mesh in meshes: exampleFaceSet = False for face in mesh.faces: try: ## TODO: Must be a better way to do this, this is already much faster than doing material every face, but still. if exampleFaceSet == False: bm.faces.new((bm.verts[face[0] - 1], bm.verts[face[1] - 1], bm.verts[face[2] - 1])) bm.faces.ensure_lookup_table() bm.faces[-1].material_index = materialmapping[mesh.usemtl] bm.faces[-1].smooth = True exampleFace = bm.faces[-1] exampleFaceSet = True else: ## Use example face if set to speed up material copy! bm.faces.new((bm.verts[face[0] - 1], bm.verts[face[1] - 1], bm.verts[face[2] - 1]), exampleFace) except ValueError: ## TODO: Duplicate faces happen for some reason pass uv_layer = bm.loops.layers.uv.new() for face in bm.faces: for loop in face.loops: loop[uv_layer].uv = uv[loop.vert.index] bm.to_mesh(newmesh) bm.free() ## Rotate object the right way obj.rotation_euler = [0, 0, 0] obj.rotation_euler.x = radians(90) bpy.context.scene.collection.objects.link(obj) obj.select_set(True) ## WoW coordinate system max_size = 51200 / 3 map_size = max_size * 2 adt_size = map_size / 64 ## Import doodads and/or WMOs csvPath = objectFile.replace('.obj', '_ModelPlacementInformation.csv') if os.path.exists(csvPath): with open(csvPath) as csvFile: reader = csv.DictReader(csvFile, delimiter=';') if 'Type' in reader.fieldnames: importType = 'ADT' wmoparent = bpy.data.objects.new("WMOs", None) wmoparent.parent = obj wmoparent.name = "WMOs" wmoparent.rotation_euler = [0, 0, 0] wmoparent.rotation_euler.x = radians(-90) bpy.context.scene.collection.objects.link(wmoparent) doodadparent = bpy.data.objects.new("Doodads", None) doodadparent.parent = obj doodadparent.name = "Doodads" doodadparent.rotation_euler = [0, 0, 0] doodadparent.rotation_euler.x = radians(-90) bpy.context.scene.collection.objects.link(doodadparent) else: importType = 'WMO' if not givenParent: print('WMO import without given parent, creating..') givenParent = bpy.data.objects.new("WMO parent", None) givenParent.parent = obj givenParent.name = "Doodads" givenParent.rotation_euler = [0, 0, 0] givenParent.rotation_euler.x = radians(-90) bpy.context.scene.collection.objects.link(givenParent) for row in reader: if importType == 'ADT': if 'importedModelIDs' in bpy.context.scene: tempModelIDList = bpy.context.scene['importedModelIDs'] else: tempModelIDList = [] if row['ModelId'] in tempModelIDList: print('Skipping already imported model ' + row['ModelId']) continue else: tempModelIDList.append(row['ModelId']) # ADT CSV if row['Type'] == 'wmo': print('ADT WMO import: ' + row['ModelFile']) # Make WMO parent that holds WMO and doodads parent = bpy.data.objects.new( row['ModelFile'] + " parent", None) parent.parent = wmoparent parent.location = (17066 - float(row['PositionX']), (17066 - float(row['PositionZ'])) * -1, float(row['PositionY'])) parent.rotation_euler = [0, 0, 0] parent.rotation_euler.x += radians( float(row['RotationZ'])) parent.rotation_euler.y += radians( float(row['RotationX'])) parent.rotation_euler.z = radians( (-90 + float(row['RotationY']))) if row['ScaleFactor']: parent.scale = (float(row['ScaleFactor']), float(row['ScaleFactor']), float(row['ScaleFactor'])) bpy.context.scene.collection.objects.link(parent) ## Only import OBJ if model is not yet in scene, otherwise copy existing if row['ModelFile'] not in bpy.data.objects: importedFile = importWoWOBJ( os.path.join(baseDir, row['ModelFile']), parent) else: ## Don't copy WMOs with doodads! if os.path.exists( os.path.join( baseDir, row['ModelFile'].replace( '.obj', '_ModelPlacementInformation.csv')) ): importedFile = importWoWOBJ( os.path.join(baseDir, row['ModelFile']), parent) else: originalObject = bpy.data.objects[ row['ModelFile']] importedFile = originalObject.copy() importedFile.data = originalObject.data.copy() bpy.context.scene.collection.objects.link( importedFile) importedFile.parent = parent elif row['Type'] == 'm2': print('ADT M2 import: ' + row['ModelFile']) ## Only import OBJ if model is not yet in scene, otherwise copy existing if row['ModelFile'] not in bpy.data.objects: importedFile = importWoWOBJ( os.path.join(baseDir, row['ModelFile'])) else: originalObject = bpy.data.objects[row['ModelFile']] importedFile = originalObject.copy() importedFile.rotation_euler = [0, 0, 0] importedFile.rotation_euler.x = radians(90) bpy.context.scene.collection.objects.link( importedFile) importedFile.parent = doodadparent importedFile.location.x = (17066 - float(row['PositionX'])) importedFile.location.y = ( 17066 - float(row['PositionZ'])) * -1 importedFile.location.z = float(row['PositionY']) importedFile.rotation_euler.x += radians( float(row['RotationZ'])) importedFile.rotation_euler.y += radians( float(row['RotationX'])) importedFile.rotation_euler.z = radians( 90 + float(row['RotationY'])) if row['ScaleFactor']: importedFile.scale = (float(row['ScaleFactor']), float(row['ScaleFactor']), float(row['ScaleFactor'])) bpy.context.scene['importedModelIDs'] = tempModelIDList else: # WMO CSV print('WMO M2 import: ' + row['ModelFile']) if row['ModelFile'] not in bpy.data.objects: importedFile = importWoWOBJ( os.path.join(baseDir, row['ModelFile'])) else: originalObject = bpy.data.objects[row['ModelFile']] importedFile = originalObject.copy() bpy.context.scene.collection.objects.link(importedFile) importedFile.location = (float(row['PositionX']) * -1, float(row['PositionY']) * -1, float(row['PositionZ'])) importedFile.rotation_euler = [0, 0, 0] rotQuat = Quaternion( (float(row['RotationW']), float(row['RotationX']), float(row['RotationY']), float(row['RotationZ']))) rotEul = rotQuat.to_euler() rotEul.x += radians(90) rotEul.z += radians(180) importedFile.rotation_euler = rotEul importedFile.parent = givenParent or obj if row['ScaleFactor']: importedFile.scale = (float(row['ScaleFactor']), float(row['ScaleFactor']), float(row['ScaleFactor'])) return obj #objectFile = "D:\\models\\world\\maps\\azeroth\\azeroth_32_32.obj" #objectFile = "D:\\models\\world\\maps\\kultiras\\kultiras_32_29.obj" #objectFile = "D:\\models\\world\\wmo\\kultiras\\human\\8hu_kultiras_seabattlement01.obj" #importWoWOBJ(objectFile)
def getValue(self): return Quaternion(self.value)
def __gather_children(blender_object, export_settings): children = [] # standard children for child_object in blender_object.children: if child_object.parent_bone: # this is handled further down, # as the object should be a child of the specific bone, # not the Armature object continue node = gather_node(child_object, export_settings) if node is not None: children.append(node) # blender dupli objects if bpy.app.version < (2, 80, 0): if blender_object.dupli_type == 'GROUP' and blender_object.dupli_group: for dupli_object in blender_object.dupli_group.objects: node = gather_node(dupli_object, export_settings) if node is not None: children.append(node) else: if blender_object.instance_type == 'COLLECTION' and blender_object.instance_collection: for dupli_object in blender_object.instance_collection.objects: node = gather_node(dupli_object, export_settings) if node is not None: children.append(node) # blender bones if blender_object.type == "ARMATURE": root_joints = [] for blender_bone in blender_object.pose.bones: if not blender_bone.parent: joint = gltf2_blender_gather_joints.gather_joint(blender_bone, export_settings) children.append(joint) root_joints.append(joint) # handle objects directly parented to bones direct_bone_children = [child for child in blender_object.children if child.parent_bone] def find_parent_joint(joints, name): for joint in joints: if joint.name == name: return joint parent_joint = find_parent_joint(joint.children, name) if parent_joint: return parent_joint return None for child in direct_bone_children: # find parent joint parent_joint = find_parent_joint(root_joints, child.parent_bone) if not parent_joint: continue child_node = gather_node(child, export_settings) if child_node is None: continue blender_bone = blender_object.pose.bones[parent_joint.name] # fix rotation if export_settings[gltf2_blender_export_keys.YUP]: rot = child_node.rotation if rot is None: rot = [0, 0, 0, 1] rot_quat = Quaternion(rot) axis_basis_change = Matrix( ((1.0, 0.0, 0.0, 0.0), (0.0, 0.0, -1.0, 0.0), (0.0, 1.0, 0.0, 0.0), (0.0, 0.0, 0.0, 1.0))) mat = gltf2_blender_math.multiply(axis_basis_change, child.matrix_basis) mat = gltf2_blender_math.multiply(child.matrix_parent_inverse, mat) _, rot_quat, _ = mat.decompose() child_node.rotation = [rot_quat[1], rot_quat[2], rot_quat[3], rot_quat[0]] # fix translation (in blender bone's tail is the origin for children) trans, _, _ = child.matrix_local.decompose() if trans is None: trans = [0, 0, 0] # bones go down their local y axis bone_tail = [0, blender_bone.length, 0] child_node.translation = [trans[idx] + bone_tail[idx] for idx in range(3)] parent_joint.children.append(child_node) return children
def getDefaultValue(cls): return Quaternion((1, 0, 0, 0))
def convert_quaternion(q): """Converts a glTF quaternion to Blender a quaternion.""" # xyzw -> wxyz return Quaternion([q[3], q[0], q[1], q[2]])
def doodads(self, object1, mesh1, dmin, dmax): """function to generate the doodads""" global dVerts global dPolygons i = 0 # on parcoure cette boucle pour ajouter des doodads a toutes les polygons # english translation: this loops adds doodads to all polygons while (i < len(object1.data.polygons)): if object1.data.polygons[i].select is False: continue doods_nbr = random.randint(dmin, dmax) j = 0 while (j <= doods_nbr): origin_dood = randVertex(object1.data.polygons[i].vertices[0], object1.data.polygons[i].vertices[1], object1.data.polygons[i].vertices[2], object1.data.polygons[i].vertices[3], Verts) type_dood = random.randint(0, len(self.DISC_doodads) - 1) polygons_add = [] verts_add = [] # First we have to apply scaling and rotation to the mesh bpy.ops.object.select_pattern(pattern=self.DISC_doodads[type_dood], extend=False) bpy.context.view_layer.objects.active = bpy.data.objects[ self.DISC_doodads[type_dood]] bpy.ops.object.transform_apply(location=False, rotation=True, scale=True) for polygon in bpy.data.objects[ self.DISC_doodads[type_dood]].data.polygons: polygons_add.append(polygon.vertices) for vertex in bpy.data.objects[ self.DISC_doodads[type_dood]].data.vertices: verts_add.append(vertex.co.copy()) normal_original_polygon = object1.data.polygons[i].normal nor_def = Vector((0.0, 0.0, 1.0)) qr = nor_def.rotation_difference( normal_original_polygon.normalized()) if (test_v2_near_v1(nor_def, -normal_original_polygon)): qr = Quaternion((0.0, 0.0, 0.0, 0.0)) # qr = angle_between_nor(nor_def, normal_original_polygon) for vertex in verts_add: vertex.rotate(qr) vertex += origin_dood findex = len(dVerts) for polygon in polygons_add: dPolygons.append([ polygon[0] + findex, polygon[1] + findex, polygon[2] + findex, polygon[3] + findex ]) i_dood_type.append( bpy.data.objects[self.DISC_doodads[type_dood]].name) for vertex in verts_add: dVerts.append(vertex) j += 1 i += 5
def roll_left(self, angle): """Roll the turtle left about the direction it is facing""" self.right.rotate(Quaternion(self.dir, radians(-angle))) self.right.normalize()
def perpendicular_direction(self): q0 = Quaternion(Vector((42, 1.618034, 2.71828)), 1.5707963) q1 = Quaternion(Vector((1.41421, 2, 1.73205)), -1.5707963) v = q1 * q0 * self return Direction(self.cross(v))
def create(gltf, scene_idx): """Scene creation.""" if scene_idx is not None: pyscene = gltf.data.scenes[scene_idx] list_nodes = pyscene.nodes # Create a new scene only if not already exists in .blend file # TODO : put in current scene instead ? if pyscene.name not in [scene.name for scene in bpy.data.scenes]: # TODO: There is a bug in 2.8 alpha that break CLEAR_KEEP_TRANSFORM # if we are creating a new scene scene = bpy.context.scene scene.render.engine = "BLENDER_EEVEE" gltf.blender_scene = scene.name else: gltf.blender_scene = pyscene.name # Switch to newly created main scene bpy.context.window.scene = bpy.data.scenes[gltf.blender_scene] else: # No scene in glTF file, create all objects in current scene scene = bpy.context.scene scene.render.engine = "BLENDER_EEVEE" gltf.blender_scene = scene.name list_nodes = BlenderScene.get_root_nodes(gltf) # Create Yup2Zup empty obj_rotation = bpy.data.objects.new("Yup2Zup", None) obj_rotation.rotation_mode = 'QUATERNION' obj_rotation.rotation_quaternion = Quaternion( (sqrt(2) / 2, sqrt(2) / 2, 0.0, 0.0)) bpy.data.scenes[gltf.blender_scene].collection.objects.link( obj_rotation) if list_nodes is not None: for node_idx in list_nodes: BlenderNode.create(gltf, node_idx, None) # None => No parent # Now that all mesh / bones are created, create vertex groups on mesh if gltf.data.skins: for skin_id, skin in enumerate(gltf.data.skins): if hasattr(skin, "node_ids"): BlenderSkin.create_vertex_groups(gltf, skin_id) for skin_id, skin in enumerate(gltf.data.skins): if hasattr(skin, "node_ids"): BlenderSkin.assign_vertex_groups(gltf, skin_id) for skin_id, skin in enumerate(gltf.data.skins): if hasattr(skin, "node_ids"): BlenderSkin.create_armature_modifiers(gltf, skin_id) if gltf.data.animations: gltf.animation_managed = [] for anim_idx, anim in enumerate(gltf.data.animations): gltf.current_animation_names = {} if list_nodes is not None: for node_idx in list_nodes: BlenderAnimation.anim(gltf, anim_idx, node_idx) for an in gltf.current_animation_names.values(): gltf.animation_managed.append(an) # Parent root node to rotation object if list_nodes is not None: exclude_nodes = [] for node_idx in list_nodes: if gltf.data.nodes[node_idx].is_joint: # Do not change parent if root node is already parented (can be the case for skinned mesh) if not bpy.data.objects[gltf.data.nodes[node_idx]. blender_armature_name].parent: bpy.data.objects[ gltf.data.nodes[node_idx]. blender_armature_name].parent = obj_rotation else: exclude_nodes.append(node_idx) else: # Do not change parent if root node is already parented (can be the case for skinned mesh) if not bpy.data.objects[ gltf.data.nodes[node_idx].blender_object].parent: bpy.data.objects[gltf.data.nodes[node_idx]. blender_object].parent = obj_rotation else: exclude_nodes.append(node_idx) if gltf.animation_object is False: for node_idx in list_nodes: if node_idx in exclude_nodes: continue # for root node that are parented by the process # for example skinned meshes for obj_ in bpy.context.scene.objects: obj_.select_set(False) if gltf.data.nodes[node_idx].is_joint: bpy.data.objects[gltf.data.nodes[ node_idx].blender_armature_name].select_set(True) bpy.context.view_layer.objects.active = bpy.data.objects[ gltf.data.nodes[node_idx].blender_armature_name] else: bpy.data.objects[gltf.data.nodes[node_idx]. blender_object].select_set(True) bpy.context.view_layer.objects.active = bpy.data.objects[ gltf.data.nodes[node_idx].blender_object] bpy.ops.object.parent_clear(type='CLEAR_KEEP_TRANSFORM') # remove object bpy.context.scene.collection.objects.unlink(obj_rotation) bpy.data.objects.remove(obj_rotation)
def __init__(self): self.translation = Vector((0, 0, 0)) self.rotation = Quaternion() self.scale = 1
def loadAnimation(self, path): data = self.teaSerializer.read(path) selectedObj = bpy.context.active_object #Walk up object tree to amature walkObj = selectedObj while not walkObj.name.endswith('-amt') and walkObj.parent: walkObj = walkObj.parent if not walkObj.name.endswith('-amt'): self.log.warning("Amature object nof found for: {}".format( obj.name)) return armatureObj = walkObj obj = walkObj.children[0] bones = armatureObj.pose.bones bpy.context.scene.frame_set(1) for frame in data: bpy.context.scene.objects.active = obj if frame.translation: translation = frame.translation obj.location = (translation.x, translation.y, translation.z) obj.keyframe_insert(data_path='location') if frame.rotation: rotation = frame.rotation obj.rotation_quaternion = (rotation.w, rotation.x, rotation.y, rotation.z) obj.keyframe_insert(data_path='rotation_quaternion') #bpy.ops.anim.keyframe_insert(type='LocRotScale', confirm_success=False) bpy.context.scene.objects.active = armatureObj bpy.ops.object.mode_set(mode='POSE') if len(bones) != len(frame.groups): #raise InconsistentStateException("Bones in amature must match animation groups, existing {} new {}".format(len(bones), len(frame['groups']))) log.warning( "Bones in amature must match animation groups, existing {} new {}" .format(len(bones), len(frame.groups))) #break # This seems to be required to handle mismatched data maxBone = min(len(bones), len(frame.groups)) for groupIndex in range(maxBone - 1, -1, -1): # group index = bone index group = frame.groups[groupIndex] bone = bones[groupIndex] location = Vector( (group.translate.x, group.translate.y, group.translate.z)) #self.log.info("moving bone to %s" % str(group.translate)) #bone.location = location rotation = Quaternion((group.Quaternion.w, group.Quaternion.x, group.Quaternion.y, group.Quaternion.z)) #self.log.info("rotating bone to %s" % str(rotation)) bone.rotation_quaternion = rotation scale = Vector((group.zoom.x, group.zoom.y, group.zoom.z)) #self.log.info("scaling bone to %s" % str(group.zoom)) #bone.scale = scale bone.keyframe_insert(data_path="location") bone.keyframe_insert(data_path="rotation_quaternion") bone.keyframe_insert(data_path="scale") #bpy.ops.anim.keyframe_insert(type='LocRotScale', confirm_success=False) # FIXME translation and zoom are both 0,0,0 # TODO keyframes of rotation are glitchy. probably cause im not doing it right. keyframe has to be set via bpy.ops.anim.keyframe_insert but i dont know how to select the bone. bpy.ops.object.mode_set(mode='OBJECT') bpy.context.scene.frame_set(bpy.context.scene.frame_current + frame.duration) #self.log.info("Loaded Frame") bpy.context.scene.frame_end = bpy.context.scene.frame_current
def quaternion(self, q): return Quaternion([q[3], q[0], q[1], q[2]])
def _read_quaternion(self, f): x, y, z, w = unpack('4f', f) return Quaternion((w, x, y, z))
def createAnnotationRotateGiz(group, annotationGen, objIndex): # Set Basis Matrix idx = 0 rotateGizScale = 0.5 for anno in annotationGen.annotations: basisMatrix = Matrix.Translation(Vector((0, 0, 0))) basisMatrix.translation = Vector(anno.gizLoc) obj = bpy.context.selected_objects[objIndex] mat = obj.matrix_world objrot = mat.to_quaternion() lineweight = 2 baseAlpha = 0.15 #Translate Op Gizmos #X annotationRotateX = group.gizmos.new("GIZMO_GT_move_3d") annotationRotateX.use_draw_modal = True opX = annotationRotateX.target_set_operator( "measureit_arch.rotate_annotation") opX.constrainAxis = (True, False, False) opX.objIndex = objIndex opX.idx = idx XbasisMatrix = basisMatrix.to_3x3() rot = Quaternion(Vector((0, 1, 0)), radians(90)) XbasisMatrix.rotate(rot) XbasisMatrix.rotate(objrot) XbasisMatrix.resize_4x4() XbasisMatrix.translation = Vector(anno.gizLoc) annotationRotateX.matrix_basis = XbasisMatrix annotationRotateX.scale_basis = rotateGizScale annotationRotateX.line_width = lineweight annotationRotateX.color = 0.96, 0.2, 0.31 annotationRotateX.alpha = baseAlpha annotationRotateX.color_highlight = 0.96, 0.2, 0.31 annotationRotateX.alpha_highlight = 1 #Y annotationRotateY = group.gizmos.new("GIZMO_GT_move_3d") annotationRotateY.use_draw_modal = True opY = annotationRotateY.target_set_operator( "measureit_arch.rotate_annotation") opY.constrainAxis = (False, True, False) opY.objIndex = objIndex opY.idx = idx YbasisMatrix = basisMatrix.to_3x3() rot = Quaternion(Vector((1, 0, 0)), radians(-90)) YbasisMatrix.rotate(rot) YbasisMatrix.rotate(objrot) YbasisMatrix.resize_4x4() YbasisMatrix.translation = Vector(anno.gizLoc) annotationRotateY.matrix_basis = YbasisMatrix annotationRotateY.scale_basis = rotateGizScale annotationRotateY.line_width = lineweight annotationRotateY.color = 0.54, 0.86, 0 annotationRotateY.alpha = baseAlpha annotationRotateY.color_highlight = 0.54, 0.86, 0 annotationRotateY.alpha_highlight = 1 #Z annotationRotateZ = group.gizmos.new("GIZMO_GT_move_3d") annotationRotateZ.use_draw_modal = True opZ = annotationRotateZ.target_set_operator( "measureit_arch.rotate_annotation") opZ.constrainAxis = (False, False, True) opZ.objIndex = objIndex opZ.idx = idx ZbasisMatrix = basisMatrix.to_3x3() ZbasisMatrix.rotate(objrot) ZbasisMatrix.resize_4x4() ZbasisMatrix.translation = Vector(anno.gizLoc) annotationRotateZ.matrix_basis = ZbasisMatrix annotationRotateZ.scale_basis = rotateGizScale annotationRotateZ.line_width = lineweight annotationRotateZ.color = 0.15, 0.56, 1 annotationRotateZ.alpha = baseAlpha annotationRotateZ.color_highlight = 0.15, 0.56, 1 annotationRotateZ.alpha_highlight = 1 #add to group group.X_widget = annotationRotateX group.Y_widget = annotationRotateY group.Z_widget = annotationRotateZ idx += 1