1133 lines
46 KiB
Python
1133 lines
46 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
###########################################################################
|
|
# Glest Model / Texture / UV / Animation Importer and Exporter
|
|
# for the Game Glest that u can find http://www.glest.org
|
|
# copyright 2005 By Andreas Becker (seltsamuel@yahoo.de)
|
|
#
|
|
# 2011/05/25: v0.1 alpha1
|
|
# modified by William Zheng for Blender 2.57(loveheaven_zhengwei@hotmail.com)
|
|
#
|
|
# corrected by MrPostiga for Blender 2.58
|
|
#
|
|
# extended by Yggdrasil
|
|
#
|
|
# modified by James Sherratt for Blender 2.90
|
|
#
|
|
# Started Date: 07 June 2005 Put Public 20 June 2005
|
|
# Distributed under the GNU PUBLIC LICENSE
|
|
# """
|
|
# Here an explanation of the V4 Format found at www.glest.org
|
|
# ================================
|
|
# 1. DATA TYPES
|
|
# ================================
|
|
# G3D files use the following data types:
|
|
# uint8: 8 bit unsigned integer
|
|
# uint16: 16 bit unsigned integer
|
|
# uint32: 32 bit unsigned integer
|
|
# float32: 32 bit floating point
|
|
# ================================
|
|
# 2. OVERALL STRUCTURE
|
|
# ================================
|
|
# - File header
|
|
# - Model header
|
|
# - Mesh header
|
|
# - Texture names
|
|
# - Mesh data
|
|
# ================================
|
|
# 2. FILE HEADER
|
|
# ================================
|
|
# Code:
|
|
# struct FileHeader{
|
|
# uint8 id[3];
|
|
# uint8 version;
|
|
# };
|
|
# This header is shared among all the versions of G3D, it identifies this file as a G3D model and provides information of the version.
|
|
# id: must be "G3D"
|
|
# version: must be 4, in binary (not '4')
|
|
# ================================
|
|
# 3. MODEL HEADER
|
|
# ================================
|
|
# Code:
|
|
# struct ModelHeader{
|
|
# uint16 meshCount;
|
|
# uint8 type;
|
|
# };
|
|
# meshCount: number of meshes in this model
|
|
# type: must be 0
|
|
# ================================
|
|
# 4. MESH HEADER
|
|
# ================================
|
|
# There is a mesh header for each mesh, there must be "meshCount" headers in a file but they are not consecutive, texture names and mesh data are stored in between.
|
|
# Code:
|
|
# struct MeshHeader{
|
|
# uint8 name[64];
|
|
# uint32 frameCount;
|
|
# uint32 vertexCount;
|
|
# uint32 indexCount;
|
|
# float32 diffuseColor[3];
|
|
# float32 specularColor[3];
|
|
# float32 specularPower;
|
|
# float32 opacity;
|
|
# uint32 properties;
|
|
# uint32 textures;
|
|
# };
|
|
# name: name of the mesh
|
|
# frameCount: number of keyframes in this mesh
|
|
# vertexCount: number of vertices in each frame
|
|
# indexCount: number of indices in this mesh (the number of triangles is indexCount/3)
|
|
# diffuseColor: RGB diffuse color
|
|
# specularColor: RGB specular color (currently unused)
|
|
# specularPower: specular power (currently unused)
|
|
# properties: property flags
|
|
# Code:
|
|
# enum MeshPropertyFlag{
|
|
# mpfCustomColor= 1,
|
|
# mpfTwoSided= 2,
|
|
# mpfNoSelect= 4
|
|
# };
|
|
# mpfTwoSided: meshes in this mesh are rendered by both sides, if this flag is not present only "counter clockwise" faces are rendered
|
|
# mpfCustomColor: alpha in this model is replaced by a custom color, usually the player color
|
|
# textures: texture flags
|
|
# Code:
|
|
# enum MeshTexture{
|
|
# diffuse = 1,
|
|
# specular = 2,
|
|
# normal = 4
|
|
# };
|
|
# ================================
|
|
# 4. TEXTURE NAMES
|
|
# ================================
|
|
# A list of uint8[64] texture name values. One for each texture in the mesh. If there are no textures in the mesh no texture names are present.
|
|
# ================================
|
|
# 5. MESH DATA
|
|
# ================================
|
|
# After each mesh header and texture names the mesh data is placed.
|
|
# vertices: frameCount * vertexCount * 3, float32 values representing the x, y, z vertex coords for all frames
|
|
# normals: frameCount * vertexCount * 3, float32 values representing the x, y, z normal coords for all frames
|
|
# texture coords: vertexCount * 2, float32 values representing the s, t tex coords for all frames (only present if the mesh has 1 texture at least)
|
|
# indices: indexCount, uint32 values representing the indices
|
|
###########################################################################
|
|
|
|
import types
|
|
import string
|
|
import struct
|
|
import sys
|
|
from math import radians
|
|
from mathutils import Matrix
|
|
import subprocess
|
|
from os.path import dirname, abspath
|
|
from os import path
|
|
import os
|
|
from types import *
|
|
import bmesh
|
|
from bpy_extras.io_utils import ImportHelper, ExportHelper
|
|
from bpy_extras.image_utils import load_image
|
|
from bpy.props import StringProperty
|
|
import bpy
|
|
bl_info = {
|
|
"name": "G3D Mesh Import/Export",
|
|
"description": "Import/Export .g3d file (Glest 3D)",
|
|
"author": "various, see head of script",
|
|
"version": (0, 12, 1),
|
|
"blender": (2, 90, 0),
|
|
"location": "File > Import-Export",
|
|
"warning": "always keep .blend files",
|
|
"wiki_url": "http://glest.wikia.com/wiki/G3D_support",
|
|
"tracker_url": "https://forum.megaglest.org/index.php?topic=6596",
|
|
"category": "Import-Export"
|
|
}
|
|
###########################################################################
|
|
# Importing Structures needed (must later verify if i need them really all)
|
|
###########################################################################
|
|
|
|
###########################################################################
|
|
# Variables that are better Global to handle
|
|
###########################################################################
|
|
imported = [] # List of all imported Objects
|
|
toexport = [] # List of Objects to export (actually only meshes)
|
|
sceneID = None # Points to the active Blender Scene
|
|
|
|
|
|
def unpack_list(list_of_tuples):
|
|
l = []
|
|
for t in list_of_tuples:
|
|
l.extend(t)
|
|
return l
|
|
|
|
|
|
###########################################################################
|
|
# Declaring Structures of G3D Format
|
|
###########################################################################
|
|
|
|
|
|
class G3DHeader: # Read first 4 Bytes of file should be G3D + Versionnumber
|
|
binary_format = "<3cB"
|
|
|
|
def __init__(self, fileID):
|
|
temp = fileID.read(struct.calcsize(self.binary_format))
|
|
data = struct.unpack(self.binary_format, temp)
|
|
self.id = str(data[0] + data[1] + data[2], "utf-8")
|
|
self.version = data[3]
|
|
|
|
|
|
class G3DModelHeaderv3: # Read Modelheader in V3 there is only the number of Meshes in file
|
|
binary_format = "<I"
|
|
|
|
def __init__(self, fileID):
|
|
temp = fileID.read(struct.calcsize(self.binary_format))
|
|
data = struct.unpack(self.binary_format, temp)
|
|
self.meshcount = data[0]
|
|
|
|
|
|
# Read Modelheader: Number of Meshes and Meshtype (must be 0)
|
|
class G3DModelHeaderv4:
|
|
binary_format = "<HB"
|
|
|
|
def __init__(self, fileID):
|
|
temp = fileID.read(struct.calcsize(self.binary_format))
|
|
data = struct.unpack(self.binary_format, temp)
|
|
self.meshcount = data[0]
|
|
self.mtype = data[1]
|
|
|
|
|
|
class G3DMeshHeaderv3: # Read Meshheader
|
|
binary_format = "<7I64c"
|
|
|
|
def __init__(self, fileID):
|
|
temp = fileID.read(struct.calcsize(self.binary_format))
|
|
data = struct.unpack(self.binary_format, temp)
|
|
self.framecount = data[0] # Framecount = Number of Animationsteps
|
|
# Number of Normal Frames actualli equal to Framecount
|
|
self.normalframecount = data[1]
|
|
# Number of Frames of Texturecoordinates seems everytime to be 1
|
|
self.texturecoordframecount = data[2]
|
|
# Number of Frames of Colors seems everytime to be 1
|
|
self.colorframecount = data[3]
|
|
self.vertexcount = data[4] # Number of Vertices in each Frame
|
|
# Number of Indices in Mesh (Triangles = Indexcount/3)
|
|
self.indexcount = data[5]
|
|
self.properties = data[6] # Property flags
|
|
if self.properties & 1: # PropertyBit is Mesh Textured ?
|
|
self.hastexture = False
|
|
self.diffusetexture = None
|
|
else:
|
|
self.diffusetexture = "".join(
|
|
[str(x, "ascii") for x in data[7:-1] if x[0] < 127])
|
|
self.hastexture = True
|
|
if self.properties & 2: # PropertyBit is Mesh TwoSided ?
|
|
self.istwosided = True
|
|
else:
|
|
self.istwosided = False
|
|
if self.properties & 4: # PropertyBit is Mesh Alpha Channel custom Color in Game ?
|
|
self.customalpha = True
|
|
else:
|
|
self.customalpha = False
|
|
|
|
|
|
class G3DMeshHeaderv4: # Read Meshheader
|
|
binary_format = "<64c3I8f2I"
|
|
texname_format = "<64c"
|
|
|
|
def _readtexname(self, fileID):
|
|
temp = fileID.read(struct.calcsize(self.texname_format))
|
|
data = struct.unpack(self.texname_format, temp)
|
|
return "".join([str(x, "ascii") for x in data[0:-1] if x[0] < 127])
|
|
|
|
def __init__(self, fileID):
|
|
temp = fileID.read(struct.calcsize(self.binary_format))
|
|
data = struct.unpack(self.binary_format, temp)
|
|
# Name of Mesh every Char is a String on his own
|
|
self.meshname = "".join(
|
|
[str(x, "ascii") for x in data[0:64] if x[0] < 127])
|
|
self.framecount = data[64] # Framecount = Number of Animationsteps
|
|
self.vertexcount = data[65] # Number of Vertices in each Frame
|
|
# Number of Indices in Mesh (Triangles = Indexcount/3)
|
|
self.indexcount = data[66]
|
|
self.diffusecolor = data[67:70] # RGB diffuse color
|
|
self.specularcolor = data[70:73] # RGB specular color (unused)
|
|
self.specularpower = data[73] # Specular power (unused)
|
|
self.opacity = data[74] # Opacity
|
|
self.properties = data[75] # Property flags
|
|
self.textures = data[76] # Texture flags
|
|
|
|
self.customalpha = bool(self.properties & 1)
|
|
self.istwosided = bool(self.properties & 2)
|
|
self.noselect = bool(self.properties & 4)
|
|
self.glow = bool(self.properties & 8)
|
|
self.onlySelect = bool(self.properties & 16)
|
|
|
|
self.hastexture = False
|
|
self.diffusetexture = None
|
|
self.speculartexture = None
|
|
self.normaltexture = None
|
|
if self.textures: # PropertyBit is Mesh Textured ?
|
|
if self.textures & 1: # diffuse
|
|
self.diffusetexture = self._readtexname(fileID)
|
|
if self.textures & 2: # specular
|
|
self.speculartexture = self._readtexname(fileID)
|
|
if self.textures & 4: # normal
|
|
self.normaltexture = self._readtexname(fileID)
|
|
|
|
self.hastexture = True
|
|
# read all texture slots, otherwise it's read as data
|
|
tex = self.textures >> 3
|
|
while tex:
|
|
tex &= tex - 1 # set rightmost 1-bit to 0
|
|
# discard texture name, as we don't know what to do with it
|
|
fileID.seek(struct.calcsize(self.texname_format))
|
|
print("warning: ignored texture in undefined texture slot")
|
|
|
|
|
|
class G3DMeshdataV3: # Calculate and read the Mesh Datapack
|
|
def __init__(self, fileID, header):
|
|
# Calculation of the Meshdatasize to load because its variable
|
|
# Animationframes * Vertices per Animation * 3 (Each Point are 3 Float X Y Z Coordinates)
|
|
vertex_format = "<%if" % int(
|
|
header.framecount * header.vertexcount * 3)
|
|
# The same for Normals
|
|
normals_format = "<%if" % int(
|
|
header.normalframecount * header.vertexcount * 3)
|
|
# Same here but Textures are 2D so only 2 Floats needed for Position inside Texture Bitmap
|
|
texturecoords_format = "<%if" % int(
|
|
header.texturecoordframecount * header.vertexcount * 2)
|
|
# Colors in format RGBA
|
|
colors_format = "<%if" % int(header.colorframecount * 4)
|
|
# Indices
|
|
indices_format = "<%iI" % int(header.indexcount)
|
|
# Load the Meshdata as calculated above
|
|
self.vertices = struct.unpack(
|
|
vertex_format, fileID.read(struct.calcsize(vertex_format)))
|
|
self.normals = struct.unpack(
|
|
normals_format, fileID.read(struct.calcsize(normals_format)))
|
|
self.texturecoords = struct.unpack(
|
|
texturecoords_format,
|
|
fileID.read(struct.calcsize(texturecoords_format)))
|
|
self.colors = struct.unpack(
|
|
colors_format, fileID.read(struct.calcsize(colors_format)))
|
|
self.indices = struct.unpack(
|
|
indices_format, fileID.read(struct.calcsize(indices_format)))
|
|
|
|
|
|
class G3DMeshdataV4: # Calculate and read the Mesh Datapack
|
|
def __init__(self, fileID, header):
|
|
# Calculation of the Meshdatasize to load because its variable
|
|
# Animationframes * Points (Vertex) per Animation * 3 (Each Point are 3 Float X Y Z Coordinates)
|
|
vertex_format = "<%if" % int(
|
|
header.framecount * header.vertexcount * 3)
|
|
# The same for Normals
|
|
normals_format = "<%if" % int(
|
|
header.framecount * header.vertexcount * 3)
|
|
# Same here but Textures are 2D so only 2 Floats needed for Position inside Texture Bitmap
|
|
texturecoords_format = "<%if" % int(header.vertexcount * 2)
|
|
# Indices
|
|
indices_format = "<%iI" % int(header.indexcount)
|
|
# Load the Meshdata as calculated above
|
|
self.vertices = struct.unpack(
|
|
vertex_format, fileID.read(struct.calcsize(vertex_format)))
|
|
self.normals = struct.unpack(
|
|
normals_format, fileID.read(struct.calcsize(normals_format)))
|
|
if header.hastexture:
|
|
self.texturecoords = struct.unpack(
|
|
texturecoords_format,
|
|
fileID.read(struct.calcsize(texturecoords_format)))
|
|
self.indices = struct.unpack(
|
|
indices_format, fileID.read(struct.calcsize(indices_format)))
|
|
|
|
|
|
# Create a Mesh inside Blender
|
|
|
|
|
|
def createMesh(filename, header, data, toblender, operator):
|
|
def tex_path(tex_filename):
|
|
tex_file = os.path.join(dirname(abspath(filename)), tex_filename).replace('\00', '')
|
|
if os.path.exists(tex_file):
|
|
return tex_file
|
|
else:
|
|
operator.report({'WARNING'},
|
|
"Bad texture filename found in g3d: {}".format(tex_file))
|
|
tex_file = tex_file[:tex_file.rfind(".")]
|
|
for fmt in ('png', 'jpeg', 'tga', 'bmp'):
|
|
new_filename = "{}.{}".format(tex_file, fmt)
|
|
print(new_filename)
|
|
if os.path.exists(new_filename):
|
|
return new_filename
|
|
raise IOError("Could not find image texture file.")
|
|
|
|
mesh = bpy.data.meshes.new(header.meshname) # New Mesh
|
|
# New Object for the new Mesh
|
|
meshobj = bpy.data.objects.new(header.meshname + 'Object', mesh)
|
|
# scene = bpy.context.scene
|
|
# scene.objects.link(meshobj)
|
|
# scene.update()
|
|
col = bpy.data.collections.get("Collection")
|
|
col.objects.link(meshobj)
|
|
uvcoords = []
|
|
img_diffuse = None
|
|
img_specular = None
|
|
img_normal = None
|
|
if header.hastexture: # Load Texture when assigned
|
|
try:
|
|
texturefile = tex_path(header.diffusetexture)
|
|
img_diffuse = bpy.data.images.load(texturefile)
|
|
for x in range(0, len(data.texturecoords), 2): # Prepare the UV
|
|
uvcoords.append(
|
|
[data.texturecoords[x], data.texturecoords[x + 1]])
|
|
|
|
if header.isv4:
|
|
if header.speculartexture:
|
|
texturefile = tex_path(header.speculartexture)
|
|
img_specular = bpy.data.images.load(texturefile)
|
|
if header.normaltexture:
|
|
texturefile = tex_path(header.normaltexture)
|
|
img_normal = bpy.data.images.load(texturefile)
|
|
except:
|
|
import traceback
|
|
traceback.print_exc()
|
|
|
|
header.hastexture = False
|
|
operator.report({'WARNING'},
|
|
"Couldn't load texture. See console for details.")
|
|
|
|
vertsCO = []
|
|
vertsNormal = []
|
|
# Get the Vertices and Normals into empty Mesh
|
|
for x in range(0, header.vertexcount * 3, 3):
|
|
vertsCO.extend([(data.vertices[x], data.vertices[x + 1],
|
|
data.vertices[x + 2])])
|
|
vertsNormal.extend([(data.normals[x], data.normals[x + 1],
|
|
data.normals[x + 2])])
|
|
# vertsCO.extend([(data.vertices[x+(header.framecount-1)*header.vertexcount*3],data.vertices[x+(header.framecount-1)*header.vertexcount*3+1],data.vertices[x+(header.framecount-1)*header.vertexcount*3+2])])
|
|
# vertsNormal.extend([(data.normals[x+(header.framecount-1)*header.vertexcount*3],data.normals[x+(header.framecount-1)*header.vertexcount*3+1],data.normals[x+(header.framecount-1)*header.vertexcount*3+2])])
|
|
# mesh.vertices.add(len(vertsCO))
|
|
|
|
faces = []
|
|
faceuv = []
|
|
for i in range(0, len(data.indices), 3): # Build Faces into Mesh
|
|
faces.append(
|
|
[data.indices[i], data.indices[i + 1], data.indices[i + 2]])
|
|
if header.hastexture:
|
|
uv = []
|
|
u0 = uvcoords[data.indices[i]][0]
|
|
v0 = uvcoords[data.indices[i]][1]
|
|
uv.append([u0, v0])
|
|
u1 = uvcoords[data.indices[i + 1]][0]
|
|
v1 = uvcoords[data.indices[i + 1]][1]
|
|
uv.append([u1, v1])
|
|
u2 = uvcoords[data.indices[i + 2]][0]
|
|
v2 = uvcoords[data.indices[i + 2]][1]
|
|
uv.append([u2, v2])
|
|
faceuv.append([uv, 0, 0, 0])
|
|
else:
|
|
uv = []
|
|
uv.append([0, 0])
|
|
uv.append([0, 0])
|
|
uv.append([0, 0])
|
|
faceuv.append([uv, 0, 0, 0])
|
|
mesh.from_pydata(vertsCO, [], faces)
|
|
mesh.vertices.foreach_set("co", unpack_list(vertsCO))
|
|
mesh.vertices.foreach_set("normal", unpack_list(vertsNormal))
|
|
# mesh.tessfaces.add(len(faces) // 4)
|
|
# mesh.tessfaces.foreach_set("vertices_raw", faces)
|
|
# mesh.tessfaces.foreach_set("use_smooth", [True] * len(mesh.tessfaces))
|
|
mesh.polygons.foreach_set(
|
|
"use_smooth", (True,)*len(mesh.polygons.data.polygons))
|
|
mesh.g3d_customColor = header.customalpha
|
|
mesh.show_double_sided = header.istwosided
|
|
if header.isv4:
|
|
mesh.g3d_noSelect = header.noselect
|
|
mesh.g3d_glow = header.glow
|
|
mesh.g3d_onlySelect = header.onlySelect
|
|
else:
|
|
mesh.g3d_noSelect = False
|
|
mesh.glow = False
|
|
#mesh.g3d_onlySelect = False
|
|
|
|
# ===================================================================================================
|
|
# Material Setup
|
|
# ===================================================================================================
|
|
|
|
def addtexslot(matdata, index, name, img):
|
|
texture = bpy.data.textures.new(name=name, type='IMAGE')
|
|
texture.image = img
|
|
slot = matdata.texture_slots.create(index)
|
|
slot.texture = texture
|
|
slot.texture_coords = 'UV'
|
|
|
|
if header.hastexture:
|
|
materialname = "pskmat"
|
|
materials = []
|
|
matdata = bpy.data.materials.new(materialname + '1')
|
|
matdata.use_backface_culling = not header.istwosided
|
|
# Show alpha.
|
|
matdata.blend_method = 'BLEND'
|
|
# Shader node tree.
|
|
matdata.use_nodes = True
|
|
node_tree = matdata.node_tree
|
|
shader_node = node_tree.nodes['Principled BSDF']
|
|
# Diffuse image
|
|
diff_img_node = node_tree.nodes.new('ShaderNodeTexImage')
|
|
node_tree.links.new(
|
|
diff_img_node.outputs['Color'], shader_node.inputs['Base Color'])
|
|
node_tree.links.new(
|
|
diff_img_node.outputs['Alpha'], shader_node.inputs['Alpha'])
|
|
diff_img_node.image = img_diffuse
|
|
|
|
# addtexslot(matdata, 0, 'diffusetexture', img_diffuse)
|
|
if img_specular:
|
|
spec_img_node = node_tree.nodes.new('ShaderNodeTexImage')
|
|
node_tree.links.new(
|
|
spec_img_node.outputs['Alpha'], diff_node.inputs['Specular'])
|
|
spec_img_node.image = img_specular
|
|
if img_normal:
|
|
norm_node = node_tree.nodes.new('ShaderNodeNormalMap')
|
|
node_tree.links.new(
|
|
norm_node.outputs['Normal'], diff_node.inputs['Normal'])
|
|
norm_img_node = node_tree.nodes.new('ShaderNodeTexImage')
|
|
node_tree.links.new(
|
|
norm_img_node.outputs['Color'], norm_node.inputs['Color'])
|
|
norm_img_node.image = img_normal
|
|
|
|
if header.isv4:
|
|
shader_node.inputs['Subsurface Color'].default_value = header.diffusecolor + (1,)
|
|
shader_node.inputs['Emission'].default_value = header.specularcolor + (1,)
|
|
shader_node.inputs['Alpha'].default_value = header.opacity
|
|
materials.append(matdata)
|
|
|
|
for material in materials:
|
|
# add material to the mesh list of materials
|
|
mesh.materials.append(material)
|
|
|
|
countm = 0
|
|
psktexname = "psk" + str(countm)
|
|
mesh.uv_layers.new(name=psktexname)
|
|
# mesh.tessface_uv_textures.new(name=psktexname)
|
|
if (len(faceuv) > 0):
|
|
uv_dat = mesh.uv_layers[psktexname].data
|
|
for i in range(len(faceuv)):
|
|
for j in range(3):
|
|
uv_dat[i*3+j].uv = faceuv[i][0][j]
|
|
# for countm in range(len(mesh.tessface_uv_textures)):
|
|
# uvtex = mesh.tessface_uv_textures[countm] # add one uv texture
|
|
# for i, face in enumerate(mesh.tessfaces):
|
|
# blender_tface = uvtex.data[i] # face
|
|
# mfaceuv = faceuv[i]
|
|
# if countm == faceuv[i][1]:
|
|
# face.material_index = faceuv[i][1]
|
|
# blender_tface.uv1 = mfaceuv[0][0] # uv = (0,0)
|
|
# blender_tface.uv2 = mfaceuv[0][1] # uv = (0,0)
|
|
# blender_tface.uv3 = mfaceuv[0][2] # uv = (0,0)
|
|
# blender_tface.image = img_diffuse
|
|
# else:
|
|
# blender_tface.uv1 = [0, 0]
|
|
# blender_tface.uv2 = [0, 0]
|
|
# blender_tface.uv3 = [0, 0]
|
|
imported.append(meshobj) # Add to Imported Objects
|
|
sk = meshobj.shape_key_add()
|
|
for x in range(
|
|
1, header.framecount): # Put in Vertex Positions for Keyanimation
|
|
sk = meshobj.shape_key_add()
|
|
for i in range(0, header.vertexcount * 3, 3):
|
|
sk.data[i // 3].co[0] = data.vertices[x * header.vertexcount * 3 +
|
|
i]
|
|
sk.data[i // 3].co[1] = data.vertices[x * header.vertexcount * 3 +
|
|
i + 1]
|
|
sk.data[i // 3].co[2] = data.vertices[x * header.vertexcount * 3 +
|
|
i + 2]
|
|
|
|
# activate one shapekey per frame
|
|
for i in range(1, header.framecount):
|
|
shape = mesh.shape_keys.key_blocks[i]
|
|
shape.value = 0.0
|
|
shape.keyframe_insert("value", frame=i)
|
|
shape.value = 1.0
|
|
shape.keyframe_insert("value", frame=(i + 1))
|
|
shape.value = 0.0
|
|
shape.keyframe_insert("value", frame=(i + 2))
|
|
|
|
meshobj.active_shape_key_index = 0
|
|
|
|
if toblender:
|
|
# rotate from glest to blender orientation
|
|
# mesh.transform( Matrix( ((1,0,0,0),(0,0,-1,0),(0,1,0,0),(0,0,0,1)) ) )
|
|
# doesn't work, maybe because of shape keys
|
|
# use object transformation instead
|
|
meshobj.rotation_euler = (radians(90), 0, 0)
|
|
|
|
# update polygon structures from tessfaces
|
|
mesh.update()
|
|
mesh.update_tag()
|
|
|
|
# remove duplicates
|
|
bm = bmesh.new()
|
|
bm.from_mesh(mesh)
|
|
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001)
|
|
bm.to_mesh(mesh)
|
|
bm.free()
|
|
|
|
return
|
|
|
|
|
|
###########################################################################
|
|
# Import
|
|
###########################################################################
|
|
|
|
|
|
def G3DLoader(filepath, toblender, operator): # Main Import Routine
|
|
global imported, sceneID
|
|
print("\nNow Importing File: " + filepath)
|
|
fileID = open(filepath, "rb")
|
|
header = G3DHeader(fileID)
|
|
print("\nHeader ID : " + header.id)
|
|
print("Version : " + str(header.version))
|
|
if header.id != "G3D":
|
|
print("ERROR: This is Not a G3D Model File")
|
|
operator.report({'ERROR'}, "This is Not a G3D Model File")
|
|
fileID.close
|
|
return
|
|
if header.version not in (3, 4):
|
|
print("ERROR: The Version of this G3D File is not Supported")
|
|
operator.report({'ERROR'},
|
|
"The Version of this G3D File is not Supported")
|
|
fileID.close
|
|
return
|
|
# in_editmode = Blender.Window.EditMode() #Must leave Editmode when active
|
|
# if in_editmode: Blender.Window.EditMode(0)
|
|
sceneID = bpy.context.scene # Get active Scene
|
|
# scenecontext=sceneID.getRenderingContext() #To Access the Start/Endframe its so hidden i searched till i got angry :-)
|
|
# Generate the Base Filename without Path + extension
|
|
basename = os.path.basename(filepath).split('.')[0]
|
|
imported = []
|
|
maxframe = 0
|
|
if header.version == 3:
|
|
modelheader = G3DModelHeaderv3(fileID)
|
|
print("Number of Meshes : " + str(modelheader.meshcount))
|
|
for x in range(modelheader.meshcount):
|
|
meshheader = G3DMeshHeaderv3(fileID)
|
|
meshheader.isv4 = False
|
|
print("\nMesh Number : " + str(x + 1))
|
|
print("framecount : " + str(meshheader.framecount))
|
|
print("normalframecount : " +
|
|
str(meshheader.normalframecount))
|
|
print("texturecoordframecount: " +
|
|
str(meshheader.texturecoordframecount))
|
|
print("colorframecount : " + str(meshheader.colorframecount))
|
|
print("pointcount : " + str(meshheader.vertexcount))
|
|
print("indexcount : " + str(meshheader.indexcount))
|
|
print("texturename : " + str(meshheader.diffusetexture))
|
|
print("hastexture : " + str(meshheader.hastexture))
|
|
print("istwosided : " + str(meshheader.istwosided))
|
|
print("customalpha : " + str(meshheader.customalpha))
|
|
# Generate Meshname because V3 has none
|
|
meshheader.meshname = basename + str(x + 1)
|
|
if meshheader.framecount > maxframe:
|
|
maxframe = meshheader.framecount # Evaluate the maximal animationsteps
|
|
meshdata = G3DMeshdataV3(fileID, meshheader)
|
|
createMesh(filepath, meshheader, meshdata, toblender, operator)
|
|
fileID.close
|
|
bpy.context.scene.frame_start = 1
|
|
bpy.context.scene.frame_end = maxframe
|
|
bpy.context.scene.frame_current = 1
|
|
|
|
anchor = bpy.data.objects.new('Empty', None)
|
|
col = bpy.data.collections.get("Collection")
|
|
col.objects.link(anchor)
|
|
anchor.select_set(True)
|
|
|
|
for ob in imported:
|
|
ob.parent = anchor
|
|
bpy.context.view_layer.update()
|
|
|
|
return
|
|
|
|
if header.version == 4:
|
|
modelheader = G3DModelHeaderv4(fileID)
|
|
print("Number of Meshes : " + str(modelheader.meshcount))
|
|
for x in range(modelheader.meshcount):
|
|
meshheader = G3DMeshHeaderv4(fileID)
|
|
meshheader.isv4 = True
|
|
print("\nMesh Number : " + str(x + 1))
|
|
print("meshname : " + str(meshheader.meshname))
|
|
print("framecount : " + str(meshheader.framecount))
|
|
print("vertexcount : " + str(meshheader.vertexcount))
|
|
print("indexcount : " + str(meshheader.indexcount))
|
|
print("diffusecolor : %1.6f %1.6f %1.6f" %
|
|
meshheader.diffusecolor)
|
|
print("specularcolor : %1.6f %1.6f %1.6f" %
|
|
meshheader.specularcolor)
|
|
print("specularpower : %1.6f" % meshheader.specularpower)
|
|
print("opacity : %1.6f" % meshheader.opacity)
|
|
print("properties : " + str(meshheader.properties))
|
|
print("textures : " + str(meshheader.textures))
|
|
print("texturename : " + str(meshheader.diffusetexture))
|
|
print("istwosided : " + str(meshheader.istwosided))
|
|
if len(meshheader.meshname) == 0: # When no Meshname in File Generate one
|
|
meshheader.meshname = basename + str(x + 1)
|
|
if meshheader.framecount > maxframe:
|
|
maxframe = meshheader.framecount # Evaluate the maximal animationsteps
|
|
meshdata = G3DMeshdataV4(fileID, meshheader)
|
|
createMesh(filepath, meshheader, meshdata, toblender, operator)
|
|
fileID.close
|
|
|
|
bpy.context.scene.frame_start = 1
|
|
bpy.context.scene.frame_end = maxframe
|
|
bpy.context.scene.frame_current = 1
|
|
anchor = bpy.data.objects.new('Empty', None)
|
|
col = bpy.data.collections.get("Collection")
|
|
col.objects.link(anchor)
|
|
anchor.select_set(True)
|
|
for ob in imported:
|
|
ob.parent = anchor
|
|
bpy.context.view_layer.update()
|
|
|
|
def tex_all_view3d_area():
|
|
'''Gets the 3d viewport. It is relative to whatever area the python script runs in.
|
|
Returns None if there's no area.'''
|
|
for screen in bpy.data.screens:
|
|
for ar in screen.areas.values():
|
|
if ar.type == 'VIEW_3D':
|
|
tex_view3d(ar)
|
|
|
|
|
|
def tex_view3d(area):
|
|
'''Sets the viewport colour to '(unshaded) texture'.'''
|
|
# Check there is a 3d view.
|
|
if area is not None:
|
|
area.spaces[0].shading.type = 'SOLID'
|
|
area.spaces[0].shading.color_type = 'TEXTURE'
|
|
area.spaces[0].shading.show_specular_highlight = False
|
|
|
|
tex_all_view3d_area()
|
|
print(
|
|
"Created a empty Object as 'Grip' where all imported Objects are parented to"
|
|
)
|
|
print(
|
|
"To move the complete Meshes only select this empty Object and move it"
|
|
)
|
|
print("All Done, have a good Day :-)\n\n")
|
|
return
|
|
|
|
|
|
def G3DSaver(filepath, context, toglest, operator):
|
|
print("\nNow Exporting File: " + filepath)
|
|
|
|
objs = context.selected_objects
|
|
if len(objs) == 0:
|
|
objs = bpy.data.objects
|
|
|
|
# get real meshcount as len(bpy.data.meshes) holds also old meshes
|
|
meshCount = 0
|
|
for obj in objs:
|
|
if obj.type == 'MESH':
|
|
meshCount += 1
|
|
if obj.mode != 'OBJECT': # we want to be in object mode
|
|
print("ERROR: mesh not in object mode")
|
|
operator.report({'ERROR'}, "mesh not in object mode")
|
|
return -1
|
|
|
|
if meshCount == 0:
|
|
print("ERROR: no meshes found")
|
|
operator.report({'ERROR'}, "no meshes found")
|
|
return -1
|
|
|
|
fileID = open(filepath, "wb")
|
|
# G3DHeader v4
|
|
fileID.write(struct.pack("<3cB", b'G', b'3', b'D', 4))
|
|
# G3DModelHeaderv4
|
|
fileID.write(struct.pack("<HB", meshCount, 0))
|
|
# meshes
|
|
# for mesh in bpy.data.meshes:
|
|
for obj in objs:
|
|
context.view_layer.objects.active = obj
|
|
if obj.type != 'MESH':
|
|
continue
|
|
mesh = obj.data
|
|
diffuseColor = [1.0, 1.0, 1.0]
|
|
specularColor = [0.9, 0.9, 0.9]
|
|
opacity = 1.0
|
|
textures = 0
|
|
texnames = []
|
|
if len(mesh.materials) > 0:
|
|
try:
|
|
# we have a texture, hopefully
|
|
material = mesh.materials[0].node_tree.nodes['Principled BSDF']
|
|
diff_tex = material.inputs['Base Color'].links[0].from_node
|
|
# only look for other textures when we have diffuse
|
|
# if slot and slot.texture.type == 'IMAGE' and len(mesh.uv_textures) > 0:
|
|
diffuseColor = material.inputs['Subsurface Color'].default_value[:3]
|
|
specularColor = material.inputs['Emission'].default_value[:3]
|
|
opacity = material.inputs['Alpha'].default_value
|
|
textures = 1
|
|
texnames.append(bpy.path.basename(diff_tex.image.filepath))
|
|
# specular and normal
|
|
except IndexError:
|
|
print("Could not find diffuse texture.")
|
|
else:
|
|
try:
|
|
spec_tex = material.inputs['Specular'].links[0].from_node
|
|
texnames.append(bpy.path.basename(spec_tex.image.filepath))
|
|
textures |= 1 << 1
|
|
except IndexError:
|
|
print("No specular texture found.")
|
|
try:
|
|
norm_tex = material.inputs['Normal'].links[0].from_node.inputs['Color'].links[0].from_node
|
|
texnames.append(bpy.path.basename(norm_tex.image.filepath))
|
|
textures |= 1 << 2
|
|
except IndexError:
|
|
print("No normal texture found.")
|
|
|
|
# else:
|
|
# print(
|
|
# "WARNING: first texture slot in first material isn't of type IMAGE or it's not unwrapped, texture ignored"
|
|
# )
|
|
# operator.report({
|
|
# 'WARNING'
|
|
# }, "first texture slot in first material isn't of type IMAGE or it's not unwrapped, texture ignored"
|
|
# )
|
|
# continue without texture
|
|
|
|
meshname = mesh.name
|
|
frameCount = context.scene.frame_end - context.scene.frame_start + 1
|
|
realFaceCount = 0 # real face count (triangles)
|
|
indices = [] # list of indices
|
|
newverts = [] # list of vertex indices which need to be duplicated
|
|
uvlist = [] # list of texcoords
|
|
# tesselate n-polygons to triangles & quads
|
|
bpy.ops.object.editmode_toggle()
|
|
bpy.ops.mesh.select_all(action='SELECT')
|
|
bpy.ops.mesh.quads_convert_to_tris()
|
|
bpy.ops.object.editmode_toggle()
|
|
|
|
# We have to update the mesh reference after any mesh modification (e.g. quads_to_tris()).
|
|
mesh = obj.data
|
|
# mesh.update(calc_tessface=True)
|
|
if textures:
|
|
# uvtex = mesh.tessface_uv_textures[0]
|
|
uvlist[:] = [[0] * 2 for i in range(len(mesh.vertices))]
|
|
# blender allows to have multiple texcoords per vertex,
|
|
# in g3d format every vertex can only have one texcoord
|
|
# -> duplicate vertex
|
|
# the dictionary/map vdict collects all the stuff
|
|
# index to "unique" vertices from blender
|
|
# -> tuple( list of texcoords, list of indices to the duplicated vertex )
|
|
vdict = dict()
|
|
nextIndex = len(mesh.vertices)
|
|
uvdata = mesh.uv_layers[0].data
|
|
uvindex = 0
|
|
|
|
# for face in mesh.tessfaces:
|
|
for face in mesh.polygons:
|
|
# when a vertex is duplicated it gets a new index, so the
|
|
# triple of indices describing the face is different too
|
|
faceindices = []
|
|
realFaceCount += 1
|
|
# uvdata = uvtex.data[face.index]
|
|
# for i in range(3):
|
|
for vindex in face.vertices:
|
|
# closure, got rid of copy&paste, still looking weird
|
|
def getTexCoords():
|
|
nonlocal nextIndex, vdict, uvlist, newverts, vindex
|
|
# vindex = face.vertices[i]
|
|
if vindex not in vdict: # new vertex -> add it
|
|
vdict[vindex] = [uvdata[uvindex].uv], [vindex]
|
|
# that's a (s,t)-pair
|
|
uvlist[vindex] = uvdata[uvindex].uv
|
|
else:
|
|
found = False
|
|
idx = 0
|
|
for ele in vdict[vindex][0]:
|
|
if uvdata[uvindex].uv[0] == ele[0] and uvdata[uvindex].uv[1] == ele[1]:
|
|
found = True
|
|
break
|
|
idx += 1
|
|
if found: # same vertex and texcoord before
|
|
# it could be a different index now, the index of a new
|
|
# duplicated vertex
|
|
# vindex = vdict[vindex][1][ vdict[vindex][0].index(uvdata.uv[i]) ]
|
|
vindex = vdict[vindex][1][idx]
|
|
else: # same vertex as before but with different texcoord -> duplicate
|
|
vdict[vindex][0].append(uvdata[uvindex].uv)
|
|
vdict[vindex][1].append(nextIndex)
|
|
|
|
# duplicate vertex because it takes part in different faces
|
|
# with different texcoords
|
|
newverts.append(vindex)
|
|
uvlist.append(uvdata[uvindex].uv)
|
|
# new index for the duplicated vertex
|
|
vindex = nextIndex
|
|
nextIndex += 1
|
|
|
|
faceindices.append(vindex)
|
|
|
|
getTexCoords()
|
|
uvindex += 1
|
|
indices.extend(faceindices)
|
|
|
|
if len(face.vertices) == 4:
|
|
faceindices = []
|
|
realFaceCount += 1
|
|
for i in [0, 2, 3]:
|
|
getTexCoords()
|
|
indices.extend(faceindices)
|
|
else:
|
|
for face in mesh.polygons:
|
|
realFaceCount += 1
|
|
indices.extend(face.vertices[0:3])
|
|
if len(face.vertices) == 4:
|
|
realFaceCount += 1
|
|
# new face because quad got split
|
|
indices.append(face.vertices[0])
|
|
indices.append(face.vertices[2])
|
|
indices.append(face.vertices[3])
|
|
|
|
# abort when no triangles as it crashs g3dviewer
|
|
if realFaceCount == 0:
|
|
print("ERROR: no triangles found")
|
|
operator.report({'ERROR'}, "no triangles found")
|
|
fileID.close()
|
|
return -1
|
|
indexCount = realFaceCount * 3
|
|
vertexCount = len(mesh.vertices) + len(newverts)
|
|
specularPower = 9.999999 # unused, same as old exporter
|
|
properties = 0
|
|
if mesh.g3d_customColor:
|
|
properties |= 1
|
|
if mesh.show_double_sided:
|
|
properties |= 2
|
|
if mesh.g3d_noSelect:
|
|
properties |= 4
|
|
if mesh.g3d_glow:
|
|
properties |= 8
|
|
if mesh.g3d_onlySelect:
|
|
properties |= 16
|
|
|
|
#MeshData
|
|
vertices = []
|
|
normals = []
|
|
fcurrent = context.scene.frame_current
|
|
depsgraph = bpy.context.evaluated_depsgraph_get()
|
|
|
|
for i in range(context.scene.frame_start, context.scene.frame_end+1):
|
|
context.scene.frame_set(i)
|
|
#FIXME: not sure what's better: PREVIEW or RENDER settings
|
|
bm = bmesh.new()
|
|
bm.from_object(obj, depsgraph=depsgraph)
|
|
bm.transform(obj.matrix_world) # apply object-mode transformations
|
|
|
|
if toglest:
|
|
# rotate from blender to glest orientation
|
|
bm.transform( Matrix( ((1,0,0,0),(0,0,1,0),(0,-1,0,0),(0,0,0,1)) ) )
|
|
# transform normals too
|
|
bm.normal_update()
|
|
|
|
for vertex in bm.verts:
|
|
vertices.extend(vertex.co)
|
|
normals.extend(vertex.normal)
|
|
|
|
# duplicate vertices and corresponding normals, for every frame
|
|
for nv in newverts:
|
|
verts = [v for v in bm.verts]
|
|
vertices.extend(verts[nv].co)
|
|
normals.extend(verts[nv].normal)
|
|
|
|
context.scene.frame_set(fcurrent)
|
|
|
|
if mesh.g3d_onlySelect:
|
|
opacity = 1.0
|
|
|
|
# MeshHeader
|
|
fileID.write(
|
|
struct.pack("<64s3I8f2I", bytes(meshname, "ascii"), frameCount,
|
|
vertexCount, indexCount, diffuseColor[0],
|
|
diffuseColor[1], diffuseColor[2], specularColor[0],
|
|
specularColor[1], specularColor[2], specularPower,
|
|
opacity, properties, textures))
|
|
# Texture names
|
|
if textures: # only when we have textures
|
|
for i in range(len(texnames)):
|
|
fileID.write(struct.pack("<64s", bytes(texnames[i], "ascii")))
|
|
|
|
# see G3DMeshdataV4
|
|
vertex_format = "<%if" % int(frameCount * vertexCount * 3)
|
|
normals_format = "<%if" % int(frameCount * vertexCount * 3)
|
|
texturecoords_format = "<%if" % int(vertexCount * 2)
|
|
indices_format = "<%iI" % int(indexCount)
|
|
|
|
fileID.write(struct.pack(vertex_format, *vertices))
|
|
fileID.write(struct.pack(normals_format, *normals))
|
|
|
|
# texcoords
|
|
if textures: # only when we have textures
|
|
texcoords = []
|
|
for uv in uvlist:
|
|
texcoords.extend(uv)
|
|
fileID.write(struct.pack(texturecoords_format, *texcoords))
|
|
|
|
fileID.write(struct.pack(indices_format, *indices))
|
|
|
|
fileID.close()
|
|
return 0
|
|
|
|
|
|
# ---=== Register ===
|
|
class G3DPanel(bpy.types.Panel):
|
|
# bl_idname = "OBJECT_PT_G3DPanel"
|
|
bl_label = "G3D properties"
|
|
bl_space_type = 'PROPERTIES'
|
|
bl_region_type = 'WINDOW'
|
|
bl_context = "data"
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return (context.object is not None and context.object.type == 'MESH')
|
|
|
|
def draw(self, context):
|
|
self.layout.prop(context.object.data, "g3d_customColor")
|
|
self.layout.prop(context.object.data,
|
|
"show_double_sided",
|
|
text="double sided")
|
|
self.layout.prop(context.object.data, "g3d_noSelect")
|
|
self.layout.prop(context.object.data, "g3d_onlySelect")
|
|
self.layout.prop(context.object.data, "g3d_glow")
|
|
|
|
|
|
class ImportG3D(bpy.types.Operator, ImportHelper):
|
|
'''Load a G3D file'''
|
|
bl_idname = "importg3d.g3d"
|
|
bl_label = "Import G3D"
|
|
|
|
filename_ext = ".g3d"
|
|
filter_glob = StringProperty(default="*.g3d", options={'HIDDEN'})
|
|
|
|
toblender = bpy.props.BoolProperty(
|
|
name="rotate to Blender orientation",
|
|
description="Rotate meshes from Glest to Blender orientation",
|
|
default=True)
|
|
|
|
def execute(self, context):
|
|
try:
|
|
G3DLoader(self.filepath, self.toblender, self)
|
|
except:
|
|
import traceback
|
|
traceback.print_exc()
|
|
|
|
return {'CANCELLED'}
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class ExportG3D(bpy.types.Operator, ExportHelper):
|
|
'''Save a G3D file'''
|
|
bl_idname = "exportg3d.g3d"
|
|
bl_label = "Export G3D"
|
|
|
|
filename_ext = ".g3d"
|
|
filter_glob = StringProperty(default="*.g3d", options={'HIDDEN'})
|
|
|
|
# export options
|
|
showg3d = bpy.props.BoolProperty(
|
|
name="show G3D afterwards",
|
|
description=("Run g3dviewer to show G3D after export. "
|
|
"g3dviewer needs to be in the scripts directory, "
|
|
"otherwise the associated program of .g3d is run."),
|
|
default=False)
|
|
toglest = bpy.props.BoolProperty(
|
|
name="rotate to glest orientation",
|
|
description="Rotate meshes from Blender to Glest orientation",
|
|
default=True)
|
|
|
|
def execute(self, context):
|
|
try:
|
|
res = G3DSaver(self.filepath, context, self.toglest, self)
|
|
if res == 0 and self.showg3d:
|
|
print("opening g3dviewer with " + self.filepath)
|
|
scriptsdir = bpy.utils.script_path_user()
|
|
dname = os.path.dirname(self.filepath)
|
|
found = False
|
|
for f in os.listdir(scriptsdir):
|
|
if "g3dviewer" in f:
|
|
f = os.path.join(scriptsdir, f)
|
|
if os.path.isfile(f) and os.access(f, os.X_OK):
|
|
cmd = [f, self.filepath]
|
|
print(cmd)
|
|
subprocess.Popen(cmd, cwd=dname)
|
|
found = True
|
|
|
|
# try default associated program
|
|
if not found:
|
|
if os.name == 'posix':
|
|
# xdg-open is only a shell script which delegates the job to a
|
|
# desktop specific program, e.g. if DE=kde than kde-open
|
|
# needs DE environment variable set, otherwise it just throws it
|
|
# at the browser, which is not very helpful
|
|
print("running xdg-open " + self.filepath)
|
|
subprocess.Popen(['xdg-open', self.filepath],
|
|
cwd=dname)
|
|
elif os.name == 'mac':
|
|
subprocess.Popen(['open', self.filepath], cwd=dname)
|
|
elif os.name == 'nt':
|
|
# os.startfile(self.filepath) # no way to change dir
|
|
subprocess.Popen(['cmd', '/C', 'start', self.filepath],
|
|
cwd=dname)
|
|
|
|
except:
|
|
import traceback
|
|
traceback.print_exc()
|
|
|
|
return {'CANCELLED'}
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
def menu_func_import(self, context):
|
|
self.layout.operator(ImportG3D.bl_idname, text="Glest 3D File (.g3d)")
|
|
|
|
|
|
def menu_func_export(self, context):
|
|
self.layout.operator(ExportG3D.bl_idname, text="Glest 3D File (.g3d)")
|
|
|
|
|
|
def register():
|
|
# custom mesh properties
|
|
bpy.types.Mesh.g3d_customColor = bpy.props.BoolProperty(
|
|
name="team color",
|
|
description="replace alpha channel of texture with team color")
|
|
bpy.types.Mesh.g3d_noSelect = bpy.props.BoolProperty(
|
|
name="non-selectable", description="click on mesh doesn't select unit")
|
|
bpy.types.Mesh.g3d_onlySelect = bpy.props.BoolProperty(
|
|
name="only-selectable",
|
|
description="this mesh is not visible, only selectable")
|
|
bpy.types.Mesh.g3d_glow = bpy.props.BoolProperty(
|
|
name="glow", description="let objects glow like particles")
|
|
bpy.types.Mesh.show_double_sided = bpy.props.BoolProperty(
|
|
name="double_sided", description="render the object double sided")
|
|
|
|
for classes in (G3DPanel, ImportG3D, ExportG3D):
|
|
bpy.utils.register_class(classes)
|
|
|
|
bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
|
|
bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
|
|
|
|
|
|
def unregister():
|
|
for classes in (G3DPanel, ImportG3D, ExportG3D):
|
|
bpy.utils.register_class(classes)
|
|
|
|
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
|
|
bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
register()
|
|
# main()
|
|
|
|
# for obj in bpy.data.objects:
|
|
# if obj.type == 'MESH':
|
|
# obj.select = True
|
|
# bpy.ops.object.delete()
|
|
# G3DLoader("import.g3d", True, None)
|
|
|
|
# for obj in bpy.context.selected_objects:
|
|
# obj.select = False # deselect everything, so we get it all
|
|
# G3DSaver("test.g3d", bpy.context)
|