import io import numpy as np from PIL import Image as PIL_Image # gltflibのImageと被るので別名にする。 import struct import uuid from gltflib import ( GLTF, GLTFModel, Asset, Scene, Node, Mesh, Primitive, Attributes, Buffer, BufferView, Image, Texture, TextureInfo, Material, Sampler, Accessor, AccessorType, BufferTarget, ComponentType, GLBResource, PBRMetallicRoughness) # 共通バイナリ情報 # 頂点データ(POSITION) vertices = [ [ 1.0, 1.0, -1.0,], [ 1.0, 1.0, -1.0,], [ 1.0, 1.0, -1.0,], [ 1.0, -1.0, -1.0,], [ 1.0, -1.0, -1.0,], [ 1.0, -1.0, -1.0,], [ 1.0, 1.0, 1.0,], [ 1.0, 1.0, 1.0,], [ 1.0, 1.0, 1.0,], [ 1.0, -1.0, 1.0,], [ 1.0, -1.0, 1.0,], [ 1.0, -1.0, 1.0,], [-1.0, 1.0, -1.0,], [-1.0, 1.0, -1.0,], [-1.0, 1.0, -1.0,], [-1.0, -1.0, -1.0,], [-1.0, -1.0, -1.0,], [-1.0, -1.0, -1.0,], [-1.0, 1.0, 1.0,], [-1.0, 1.0, 1.0,], [-1.0, 1.0, 1.0,], [-1.0, -1.0, 1.0,], [-1.0, -1.0, 1.0,], [-1.0, -1.0, 1.0,], ] vertex_bytearray = bytearray() for vertex in vertices: for value in vertex: vertex_bytearray.extend(struct.pack('f', value)) vertex_bytelen = len(vertex_bytearray) mins = [min([vertex[i] for vertex in vertices]) for i in range(3)] maxs = [max([vertex[i] for vertex in vertices]) for i in range(3)] # 法線データ(NORMAL) normals = [ [ 0.0, 0.0, -1.0,], [ 0.0, 1.0, -0.0,], [ 1.0, 0.0, -0.0,], [ 0.0, -1.0, -0.0,], [ 0.0, 0.0, -1.0,], [ 1.0, 0.0, -0.0,], [ 0.0, 0.0, 1.0,], [ 0.0, 1.0, -0.0,], [ 1.0, 0.0, -0.0,], [ 0.0, -1.0, -0.0,], [ 0.0, 0.0, 1.0,], [ 1.0, 0.0, -0.0,], [-1.0, 0.0, -0.0,], [ 0.0, 0.0, -1.0,], [ 0.0, 1.0, -0.0,], [-1.0, 0.0, -0.0,], [ 0.0, -1.0, -0.0,], [ 0.0, 0.0, -1.0,], [-1.0, 0.0, -0.0,], [ 0.0, 0.0, 1.0,], [ 0.0, 1.0, -0.0,], [-1.0, 0.0, -0.0,], [ 0.0, -1.0, -0.0,], [ 0.0, 0.0, 1.0,], ] normal_bytearray = bytearray() for normal in normals: for value in normal: normal_bytearray.extend(struct.pack('f', value)) normal_bytelen = len(normal_bytearray) # テクスチャ座標(TEXCOORD_0) texcoord_0s = [ [0.9, 0.1],[0.9, 0.0],[1.0, 0.1], [0.9, 1.0],[0.9, 0.9],[1.0, 0.9], [0.9, 0.1],[0.9, 0.1],[0.9, 0.1], [0.9, 0.9],[0.9, 0.9],[0.9, 0.9], [0.0, 0.1],[0.1, 0.1],[0.1, 0.0], [0.0, 0.9],[0.1, 1.0],[0.1, 0.9], [0.1, 0.1],[0.1, 0.1],[0.1, 0.1], [0.1, 0.9],[0.1, 0.9],[0.1, 0.9], ] texcoord_0_bytearray = bytearray() for texcoord_0 in texcoord_0s: for value in texcoord_0: texcoord_0_bytearray.extend(struct.pack('f', value)) texcoord_0_bytelen = len(texcoord_0_bytearray) # 頂点インデックス vertex_indices = [ 1, 14, 20, 1, 20, 7, 10, 6, 19, 10, 19, 23, 21, 18, 12, 21, 12, 15, 16, 3, 9, 16, 9, 22, 5, 2, 8, 5, 8, 11, 17, 13, 0, 17, 0, 4 ] vertex_index_bytearray = bytearray() for value in vertex_indices: vertex_index_bytearray.extend(struct.pack('H', value)) vertex_index_bytelen = len(vertex_index_bytearray) def create_picture_box_model(img_bytearray): # 画像の取得 img = PIL_Image.open(img_bytearray).convert('RGB') # 辺の長さが8で割れる数値になるように調整 img = img.resize((8 * (img.size[0] // 8), 8 * (img.size[1] // 8))) temp_img = PIL_Image.new('RGB', (int(img.size[0] * 1.25), int(img.size[1] * 1.25)), 'white') temp_img.paste(img, (int(temp_img.size[0] / 10), int(temp_img.size[1] / 10))) img = temp_img.copy() for offset_x in range(0, int(temp_img.size[0] / 10)): img.paste(img.crop((int(temp_img.size[0] / 10)+1, 0, int(temp_img.size[0] / 10)+2, img.size[1])), (offset_x, 0)) for offset_x in range(int(temp_img.size[0] / 10 * 9), img.size[0]): img.paste(img.crop((int(temp_img.size[0] / 10 * 9) - 2, 0, int(temp_img.size[0] / 10 *9) - 1, img.size[1])), (offset_x, 0)) for offset_y in range(0, int(temp_img.size[1] / 10)): img.paste(img.crop((0, int(temp_img.size[1] / 10) + 1, img.size[0], int(temp_img.size[1] / 10) + 2)), (0, offset_y)) for offset_y in range(int(temp_img.size[1] / 10 * 9), img.size[1]): img.paste(img.crop((0, int(temp_img.size[1] / 10 * 9) - 2, img.size[0], int(temp_img.size[1] / 10 * 9) - 1)), (0, offset_y)) img_bytearray = io.BytesIO() img.save(img_bytearray, format="JPEG", quality=95) img_bytearray = img_bytearray.getvalue() img_bytelen = len(img_bytearray) # 3Dモデルのスケールの計算 scale_factor = np.power(img.size[0] * img.size[1], 0.5) scale = (img.size[0] / scale_factor, img.size[1] / scale_factor, 0.1) # バイナリデータ部分の結合 bytearray_list = [ vertex_bytearray, normal_bytearray, texcoord_0_bytearray, vertex_index_bytearray, img_bytearray, ] bytelen_list = [ vertex_bytelen, normal_bytelen, texcoord_0_bytelen, vertex_index_bytelen, img_bytelen, ] bytelen_cumsum_list = list(np.cumsum(bytelen_list)) bytelen_cumsum_list = list(map(lambda x: int(x), bytelen_cumsum_list)) all_bytearray = bytearray() for temp_bytearray in bytearray_list: all_bytearray.extend(temp_bytearray) offset_list = [0] + bytelen_cumsum_list # 最初のオフセットは0 offset_list.pop() # 末尾を削除 # リソースの作成 resources = [GLBResource(data=all_bytearray)] # 各種設定 # アセット asset=Asset() # バッファ buffers = [Buffer(byteLength=len(all_bytearray))] # バッファビュー bufferViews = [ BufferView(buffer=0, byteOffset=offset_list[0], byteLength=bytelen_list[0], target=BufferTarget.ARRAY_BUFFER.value), BufferView(buffer=0, byteOffset=offset_list[1], byteLength=bytelen_list[1], target=BufferTarget.ARRAY_BUFFER.value), BufferView(buffer=0, byteOffset=offset_list[2], byteLength=bytelen_list[2], target=BufferTarget.ARRAY_BUFFER.value), BufferView(buffer=0, byteOffset=offset_list[3], byteLength=bytelen_list[3], target=BufferTarget.ELEMENT_ARRAY_BUFFER.value), BufferView(buffer=0, byteOffset=offset_list[4], byteLength=bytelen_list[4], target=None), ] # アクセサー accessors = [ Accessor(bufferView=0, componentType=ComponentType.FLOAT.value, count=len(vertices), type=AccessorType.VEC3.value, max=maxs, min=mins), Accessor(bufferView=1, componentType=ComponentType.FLOAT.value, count=len(normals), type=AccessorType.VEC3.value, max=None, min=None), Accessor(bufferView=2, componentType=ComponentType.FLOAT.value, count=len(texcoord_0s), type=AccessorType.VEC2.value, max=None, min=None), Accessor(bufferView=3, componentType=ComponentType.UNSIGNED_SHORT.value, count=len(vertex_indices), type=AccessorType.SCALAR.value, max=None, min=None) ] # イメージ images=[ Image(mimeType='image/jpeg', bufferView=4), ] # サンプラー samplers = [Sampler(magFilter=9728, minFilter=9984)] # magFilter:最近傍フィルタリング、minFilter:ミップマップ+最近傍フィルタリング # テクスチャ textures = [ Texture(name='Image',sampler=0,source=0), ] # マテリアル materials = [ Material( pbrMetallicRoughness=PBRMetallicRoughness( baseColorTexture=TextureInfo(index=0), metallicFactor=0, roughnessFactor=0.5 ), name='Material0', alphaMode='BLEND', doubleSided=False ), ] # メッシュ meshes = [ Mesh(name='Image', primitives=[Primitive(attributes=Attributes(POSITION=0, NORMAL=1,TEXCOORD_0=2),indices=3, material=0)]), ] # ノード nodes = [ Node(mesh=0,rotation=None, scale=scale), ] # シーン scene = 0 scenes = [Scene(name='Scene', nodes=[0])] model = GLTFModel( asset=asset, buffers=buffers, bufferViews=bufferViews, accessors=accessors, images=images, samplers=samplers, textures=textures, materials=materials, meshes=meshes, nodes=nodes, scene=scene, scenes=scenes ) gltf = GLTF(model=model, resources=resources) tmp_filename = uuid.uuid4().hex model_path = f'../tmp/{tmp_filename}.glb' gltf.export(model_path) return model_path