3dmodelmaker / src /picture_box_model.py
ikeda
add 3dmodelmaker sources
bac55b4
raw
history blame
8.6 kB
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