import io import numpy as np from PIL import Image as PIL_Image # Renaming to avoid conflict with Image from gltflib 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) # Common configuration information # Parts that are independent of the binary section's offset and size can be predefined. # Asset asset=Asset() # Image images=[ Image(mimeType='image/jpeg', bufferView=4), Image(mimeType='image/jpeg',bufferView=5), Image(mimeType='image/jpeg',bufferView=6), Image(mimeType='image/jpeg',bufferView=7), Image(mimeType='image/jpeg',bufferView=8), Image(mimeType='image/jpeg',bufferView=9), ] # Sampler samplers = [Sampler(magFilter=9728, minFilter=9984)] # magFilter: Nearest filtering, minFilter: Mipmap + Nearest filtering # Texture textures = [ Texture(name='Front',sampler=0,source=0), Texture(name='Back',sampler=0,source=1), Texture(name='Left',sampler=0,source=2), Texture(name='Right',sampler=0,source=3), Texture(name='Top',sampler=0,source=4), Texture(name='Bottom',sampler=0,source=5), ] # Material materials = [ Material( pbrMetallicRoughness=PBRMetallicRoughness( baseColorTexture=TextureInfo(index=0), metallicFactor=0, roughnessFactor=0.5 ), name='Material0', alphaMode='OPAQUE', doubleSided=False ), Material( pbrMetallicRoughness=PBRMetallicRoughness( baseColorTexture=TextureInfo(index=1), metallicFactor=0, roughnessFactor=0.5 ), name='Material1', alphaMode='OPAQUE', doubleSided=False ), Material( pbrMetallicRoughness=PBRMetallicRoughness( baseColorTexture=TextureInfo(index=2), metallicFactor=0, roughnessFactor=0.5 ), name='Material2', alphaMode='OPAQUE', doubleSided=True ), Material( pbrMetallicRoughness=PBRMetallicRoughness( baseColorTexture=TextureInfo(index=3), metallicFactor=0, roughnessFactor=0.5 ), name='Material3', alphaMode='OPAQUE', doubleSided=True ), Material( pbrMetallicRoughness=PBRMetallicRoughness( baseColorTexture=TextureInfo(index=4), metallicFactor=0, roughnessFactor=0.5 ), name='Material4', alphaMode='OPAQUE', doubleSided=True ), Material( pbrMetallicRoughness=PBRMetallicRoughness( baseColorTexture=TextureInfo(index=5), metallicFactor=0, roughnessFactor=0.5 ), name='Material5', alphaMode='OPAQUE', doubleSided=True ), ] # Mesh meshes = [ Mesh(name='Front', primitives=[Primitive(attributes=Attributes(POSITION=0, NORMAL=1,TEXCOORD_0=2), indices=3, material=0)]), Mesh(name='Back', primitives=[Primitive(attributes=Attributes(POSITION=0, NORMAL=1,TEXCOORD_0=2), indices=3, material=1)]), Mesh(name='Left', primitives=[Primitive(attributes=Attributes(POSITION=0, NORMAL=1,TEXCOORD_0=2), indices=3, material=2)]), Mesh(name='Right', primitives=[Primitive(attributes=Attributes(POSITION=0, NORMAL=1,TEXCOORD_0=2), indices=3, material=3)]), Mesh(name='Top', primitives=[Primitive(attributes=Attributes(POSITION=0, NORMAL=1,TEXCOORD_0=2), indices=3, material=4)]), Mesh(name='Bottom', primitives=[Primitive(attributes=Attributes(POSITION=0, NORMAL=1,TEXCOORD_0=2), indices=3, material=5)]), ] def create_card_model(front_img_bytearray, back_img_bytearray, option_dict): is_thick = True if option_dict['厚み'] == '有' else False # Front image front_img = PIL_Image.open(front_img_bytearray).convert('RGB') front_bytearray = io.BytesIO() front_img.save(front_bytearray, format="JPEG", quality=95) # Force JPEG format for saving front_bytearray = front_bytearray.getvalue() front_bytelen = len(front_bytearray) # Back image back_img = PIL_Image.open(back_img_bytearray).convert('RGB') back_bytearray = io.BytesIO() back_img.save(back_bytearray, format="JPEG", quality=95) # Force JPEG format for saving back_bytearray = back_bytearray.getvalue() back_bytelen = len(back_bytearray) # Create side image (extend the color of the edges of the back side) back_img_array = np.array(back_img) left_side_img = PIL_Image.fromarray(np.tile(back_img_array[:, [0], :], [1, 32, 1])) right_side_img = PIL_Image.fromarray(np.tile(back_img_array[:, [back_img_array.shape[1] - 1], :], [1, 32, 1])) top_side_img = PIL_Image.fromarray(np.tile(back_img_array[[0], :, :], [32, 1, 1])) bottom_side_img = PIL_Image.fromarray(np.tile(back_img_array[[back_img_array.shape[0] - 1], :, :], [32, 1, 1])) left_side_bytearray = io.BytesIO() left_side_img.save(left_side_bytearray, format="JPEG", quality=95) # Force JPEG format for saving left_side_bytearray = left_side_bytearray.getvalue() left_side_bytelen = len(left_side_bytearray) right_side_bytearray = io.BytesIO() right_side_img.save(right_side_bytearray, format="JPEG", quality=95) # Force JPEG format for saving right_side_bytearray = right_side_bytearray.getvalue() right_side_bytelen = len(right_side_bytearray) top_side_bytearray = io.BytesIO() top_side_img.save(top_side_bytearray, format="JPEG", quality=95) # Force JPEG format for saving top_side_bytearray = top_side_bytearray.getvalue() top_side_bytelen = len(top_side_bytearray) bottom_side_bytearray = io.BytesIO() bottom_side_img.save(bottom_side_bytearray, format="JPEG", quality=95) # Force JPEG format for saving bottom_side_bytearray = bottom_side_bytearray.getvalue() bottom_side_bytelen = len(bottom_side_bytearray) # Vertex data(POSITION) vertices = [ (-1.0, -1.0, 0.0), ( 1.0, -1.0, 0.0), (-1.0, 1.0, 0.0), ( 1.0, 1.0, 0.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 data(NORMAL) normals = [( 0.0, 0.0, 1.0)] * 4 normal_bytearray = bytearray() for normal in normals: for value in normal: normal_bytearray.extend(struct.pack('f', value)) normal_bytelen = len(normal_bytearray) # Texture coordinates(TEXCOORD_0) texcoord_0s = [ (0.0, 1.0), (1.0, 1.0), (0.0, 0.0), (1.0, 0.0) ] 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 vertex_indices = [0, 1, 2, 1, 3, 2] vertex_index_bytearray = bytearray() for value in vertex_indices: vertex_index_bytearray.extend(struct.pack('H', value)) vertex_index_bytelen = len(vertex_index_bytearray) # Concatenation of the binary data section bytearray_list = [ vertex_bytearray, normal_bytearray, texcoord_0_bytearray, vertex_index_bytearray, front_bytearray, back_bytearray, left_side_bytearray, right_side_bytearray, top_side_bytearray, bottom_side_bytearray, ] bytelen_list = [ vertex_bytelen, normal_bytelen, texcoord_0_bytelen, vertex_index_bytelen, front_bytelen, back_bytelen, left_side_bytelen, right_side_bytelen, top_side_bytelen, bottom_side_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 # The first offset is 0 offset_list.pop() # Remove the end # GLBResource resources = [GLBResource(data=all_bytearray)] # Buffer buffers = [Buffer(byteLength=len(all_bytearray))] # BufferView 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), BufferView(buffer=0, byteOffset=offset_list[5], byteLength=bytelen_list[5], target=None), BufferView(buffer=0, byteOffset=offset_list[6], byteLength=bytelen_list[6], target=None), BufferView(buffer=0, byteOffset=offset_list[7], byteLength=bytelen_list[7], target=None), BufferView(buffer=0, byteOffset=offset_list[8], byteLength=bytelen_list[8], target=None), BufferView(buffer=0, byteOffset=offset_list[9], byteLength=bytelen_list[9], target=None), ] # Accessor 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), ] # Node card_thickness = 0.025 card_ratio_x = 0.8679999709129333 card_ratio_y = 1.2130000591278076 card_ratio_z = 1 if is_thick: nodes = [ Node(mesh=0, scale=[card_ratio_x, card_ratio_y, card_ratio_z], rotation=None, translation=[0, 0, card_thickness]), Node(mesh=1, scale=[card_ratio_x, card_ratio_y, card_ratio_z], rotation=[0, 1, 0, 0], translation=[0, 0, -card_thickness]), Node(mesh=2, scale=[card_thickness, card_ratio_y, card_ratio_z], rotation=[0, 0.7071, 0, 0.7071], translation=[ card_ratio_x, 0, 0]), # 左 Node(mesh=3, scale=[card_thickness, card_ratio_y, card_ratio_z], rotation=[0, -0.7071, 0, 0.7071], translation=[-card_ratio_x, 0, 0]), # 右 Node(mesh=4, scale=[card_ratio_x, card_thickness, card_ratio_z], rotation=[ 0.7071, 0, 0, 0.7071], translation=[0, card_ratio_y, 0]), # 上 Node(mesh=5, scale=[card_ratio_x, card_thickness, card_ratio_z], rotation=[-0.7071, 0, 0, 0.7071], translation=[0, -card_ratio_y, 0]), # 下 ] else: nodes = [ Node(mesh=0, scale=[card_ratio_x, card_ratio_y, card_ratio_z], rotation=None), Node(mesh=1, scale=[card_ratio_x, card_ratio_y, card_ratio_z], rotation=[0, 1, 0, 0]) ] # Scene scene = 0 if is_thick: scenes = [Scene(name='Scene', nodes=[0, 1, 2, 3, 4, 5])] else: scenes = [Scene(name='Scene', nodes=[0, 1])] 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