ikeda commited on
Commit
bac55b4
1 Parent(s): ff60f42

add 3dmodelmaker sources

Browse files
Files changed (44) hide show
  1. .gitattributes +2 -0
  2. .gitignore +1 -0
  3. app.py +113 -0
  4. data/cards/back/cats.png +3 -0
  5. data/cards/back/fossils.png +3 -0
  6. data/cards/back/jewels.png +3 -0
  7. data/cards/front/cats.png +3 -0
  8. data/cards/front/fossils.png +3 -0
  9. data/cards/front/jewels.png +3 -0
  10. data/cards/史跡カード(背景).png +3 -0
  11. data/cards/史跡カード(裏面).png +3 -0
  12. data/cards/史跡カードフレーム(橙).png +3 -0
  13. data/cards/史跡カードフレーム(白).png +3 -0
  14. data/cards/史跡カードフレーム(紫).png +3 -0
  15. data/cards/史跡カードフレーム(緑).png +3 -0
  16. data/cards/史跡カードフレーム(茶).png +3 -0
  17. data/cards/史跡カードフレーム(赤).png +3 -0
  18. data/cards/史跡カードフレーム(青).png +3 -0
  19. data/cards/史跡カードフレーム(黄).png +3 -0
  20. data/cards/史跡カードフレーム(黒).png +3 -0
  21. data/cards/史跡カード指定マーク(区指定).png +3 -0
  22. data/cards/史跡カード指定マーク(国指定).png +3 -0
  23. data/cards/史跡カード指定マーク(市指定).png +3 -0
  24. data/cards/史跡カード指定マーク(府指定).png +3 -0
  25. data/cards/史跡カード指定マーク(村指定).png +3 -0
  26. data/cards/史跡カード指定マーク(町指定).png +3 -0
  27. data/cards/史跡カード指定マーク(県指定).png +3 -0
  28. data/cards/史跡カード指定マーク(道指定).png +3 -0
  29. data/cards/史跡カード指定マーク(都指定).png +3 -0
  30. data/fonts/SourceHanSans-Bold.otf +3 -0
  31. data/fonts/SourceHanSans_LICENSE.txt +96 -0
  32. data/fonts/SourceHanSerif-Bold.otf +3 -0
  33. data/fonts/SourceHanSerif_LICENSE.txt +96 -0
  34. license.md +24 -0
  35. readme.txt +2 -0
  36. requirements.txt +6 -0
  37. src/card_model.py +311 -0
  38. src/constants.py +80 -0
  39. src/display_message_en.json +37 -0
  40. src/display_message_ja.json +37 -0
  41. src/extracted_objects_model.py +265 -0
  42. src/front_card_image.py +91 -0
  43. src/front_card_image_historic_site.py +150 -0
  44. src/picture_box_model.py +265 -0
.gitattributes CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ *.png filter=lfs diff=lfs merge=lfs -text
37
+ *.otf filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ *.pyc
app.py ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from PIL import Image as PIL_Image # gltflibのImageと被るので別名にする。
3
+ from io import BytesIO
4
+ import json
5
+ import os
6
+
7
+ import src.constants as constants
8
+
9
+ from src.front_card_image import create_card_image
10
+ from src.front_card_image_historic_site import create_historic_site_card_image
11
+ from src.picture_box_model import create_picture_box_model
12
+ from src.extracted_objects_model import create_extracted_objects_model
13
+ from src.card_model import create_card_model
14
+
15
+ language = os.environ.get('LANGUAGE', 'en')
16
+ print(f'LANGUAGE: {language}')
17
+
18
+ with open(f'src/display_message_{language}.json', 'r', encoding='utf-8') as f:
19
+ display_message_dict = json.load(f)
20
+ model_type_dict = display_message_dict['model_type_dict']
21
+ color_dict = display_message_dict['color_dict']
22
+
23
+ def create_3dmodel(model_no, title, color, mark, historic_site_type, difficulty, description, is_thick, image):
24
+
25
+ img_bytearray = BytesIO()
26
+ image['background'].save(img_bytearray, "JPEG", quality=95)
27
+ img_bytearray.seek(0) # 画像の先頭にシークしないと空データになってしまう。
28
+
29
+ option_dict = {
30
+ 'タイトル': title,
31
+ '色': color,
32
+ 'マーク': mark,
33
+ '史跡種類': historic_site_type,
34
+ '訪問難度': difficulty,
35
+ '説明文': description,
36
+ '厚み': '有' if is_thick else '無',
37
+ }
38
+
39
+ # model_noのコード値決定ルールを接頭語を付けた場合分けやファクトリ―クラスの作成を検討した方が良い
40
+ if model_no not in ['A', 'B']:
41
+ # カード画像(表面)の作成
42
+ if model_no == '1':
43
+ front_img_bytearray = create_historic_site_card_image(img_bytearray, option_dict)
44
+ else:
45
+ front_img_bytearray = create_card_image(model_no, img_bytearray, option_dict)
46
+
47
+ # カード画像(裏面)の取得
48
+ back_path = constants.back_card_img_dict[model_no]
49
+ back_img = PIL_Image.open(back_path)
50
+ back_img_bytearray = BytesIO()
51
+ back_img.convert('RGB').save(back_img_bytearray, "JPEG", quality=95)
52
+ back_img_bytearray.seek(0) # 画像の先頭にシークしないと空データになってしまう。
53
+
54
+ # カードの3Dモデルの作成(戻り値は作成したモデルのパス)
55
+ model_path = create_card_model(front_img_bytearray, back_img_bytearray, option_dict)
56
+
57
+ else:
58
+ # 3Dモデルの作成(戻り値は作成したモデルのパス)
59
+ if model_no == 'A':
60
+ model_path = create_picture_box_model(img_bytearray)
61
+ if model_no == 'B':
62
+ model_path = create_extracted_objects_model(img_bytearray)
63
+
64
+ # 作成した3Dモデルの送信
65
+ return model_path
66
+
67
+ with gr.Blocks() as demo:
68
+
69
+ gr.Markdown(display_message_dict['header'])
70
+
71
+ with gr.Tab(display_message_dict['tab_label_card_general']):
72
+ with gr.Row():
73
+ with gr.Column():
74
+ title = gr.Textbox(label=display_message_dict['label_title'], placeholder=display_message_dict['placeholder_title'])
75
+ model_type = gr.Radio([(model_type_dict[key], key) for key in model_type_dict], value='2', label=display_message_dict['label_card_type'])
76
+ with gr.Column():
77
+ description = gr.Textbox(lines=2, label=display_message_dict['label_description'], placeholder=display_message_dict['placeholder_description'])
78
+ is_thick = gr.Checkbox(label=display_message_dict['label_is_thick'], value=False, info=display_message_dict['info_is_thick'])
79
+ image = gr.ImageEditor(image_mode='RGB', sources="upload", type="pil", crop_size="1:1", label=display_message_dict['label_image'])
80
+
81
+ button = gr.Button(display_message_dict['label_button'])
82
+ button.click(
83
+ fn=lambda model_type, title, description, is_thick, image:
84
+ create_3dmodel(model_type, title, None, None, None, None, description, is_thick, image),
85
+ inputs=[model_type, title, description, is_thick, image],
86
+ outputs=[gr.Model3D(camera_position=(90, 90, 5))]
87
+ )
88
+
89
+ if language == 'ja':
90
+ with gr.Tab(display_message_dict['tab_label_historic_site_card']):
91
+ with gr.Row():
92
+ with gr.Column():
93
+ title = gr.Textbox(label=display_message_dict['label_title'], placeholder=display_message_dict['placeholder_title'])
94
+ color = gr.Radio([(color_dict[key], key) for key in color_dict], value=list(color_dict)[0], label=display_message_dict['label_color'])
95
+ mark = gr.Radio(display_message_dict['mark_list'], value=list(display_message_dict['mark_list'])[0], label=display_message_dict['label_mark'])
96
+ historic_site_type = gr.Textbox(label=display_message_dict["label_historic_site_type"], placeholder=display_message_dict["placeholder_historic_site_type"])
97
+ with gr.Column():
98
+ difficulty = gr.Slider(1, 5, 3, step=1, label=display_message_dict['label_difficulty'])
99
+ description = gr.Textbox(lines=2, label=display_message_dict['label_description'], placeholder=display_message_dict['placeholder_description'])
100
+ is_thick = gr.Checkbox(label=display_message_dict['label_is_thick'], value=False, info=display_message_dict['info_is_thick'])
101
+ image = gr.ImageEditor(image_mode='RGB', sources="upload", type="pil", crop_size="1:1", label=display_message_dict['label_image'])
102
+
103
+ button = gr.Button(display_message_dict['label_button'])
104
+ button.click(
105
+ fn=lambda title, color, mark, historic_site_type, difficulty, description, is_thick, image:
106
+ create_3dmodel('1', title, color, mark, historic_site_type, difficulty, description, is_thick, image),
107
+ inputs=[title, color, mark, historic_site_type, difficulty, description, is_thick, image],
108
+ outputs=[gr.Model3D(camera_position=(90, 90, 5))]
109
+ )
110
+
111
+ gr.Markdown(display_message_dict['footer_historic_site_card'])
112
+
113
+ demo.launch()
data/cards/back/cats.png ADDED

Git LFS Details

  • SHA256: 911bbfa04e817e4ba5f53c7f713138db9cb6dc40bcc74b6ff57fbca266ae0c49
  • Pointer size: 132 Bytes
  • Size of remote file: 1.42 MB
data/cards/back/fossils.png ADDED

Git LFS Details

  • SHA256: 6a80c89de5d305d0732c7d467bbc52bc33936c1d5af9327c12133aa34a6dd88f
  • Pointer size: 132 Bytes
  • Size of remote file: 1.92 MB
data/cards/back/jewels.png ADDED

Git LFS Details

  • SHA256: 85c5392d212dbdc3715ae021708718ed3ee603535540886928b711a66a44bb59
  • Pointer size: 132 Bytes
  • Size of remote file: 1.75 MB
data/cards/front/cats.png ADDED

Git LFS Details

  • SHA256: 75cde384346053fe37f77727a8dc1617945c68c7a502ee4feba6a1b743f67b3c
  • Pointer size: 131 Bytes
  • Size of remote file: 534 kB
data/cards/front/fossils.png ADDED

Git LFS Details

  • SHA256: f9ba6e5b4431e3ea4e8e58a286f16d458dd687a5fd4ba5f352c043f95b09d7a3
  • Pointer size: 131 Bytes
  • Size of remote file: 727 kB
data/cards/front/jewels.png ADDED

Git LFS Details

  • SHA256: 5cbbe68618229f266fea0a5efa0b58c6fb47353bcadc3aea6d89bb7fc84c2445
  • Pointer size: 131 Bytes
  • Size of remote file: 682 kB
data/cards/史跡カード(背景).png ADDED

Git LFS Details

  • SHA256: 8a81a37c5537e5c6e9026795aefb04b382a9628d66c7650bba3355e82cda9068
  • Pointer size: 132 Bytes
  • Size of remote file: 2.5 MB
data/cards/史跡カード(裏面).png ADDED

Git LFS Details

  • SHA256: 56f6565761d1839a061b7ca2d7b5c24290b5da15841255f9b76ae32d547fc059
  • Pointer size: 132 Bytes
  • Size of remote file: 2.6 MB
data/cards/史跡カードフレーム(橙).png ADDED

Git LFS Details

  • SHA256: 5978d746e4a1ec6f3403d27692cee4c5ed52bb85c80155ff9cf69c275512a03f
  • Pointer size: 132 Bytes
  • Size of remote file: 1.02 MB
data/cards/史跡カードフレーム(白).png ADDED

Git LFS Details

  • SHA256: b9ba3e8abe6d8b7584682f1b88f492a4a879258a946aab002a190ed88d10b9e8
  • Pointer size: 131 Bytes
  • Size of remote file: 956 kB
data/cards/史跡カードフレーム(紫).png ADDED

Git LFS Details

  • SHA256: b29349a5b8a1000753db95b98d5d4d8f2267c435f7156dc77226d0220f61a030
  • Pointer size: 131 Bytes
  • Size of remote file: 970 kB
data/cards/史跡カードフレーム(緑).png ADDED

Git LFS Details

  • SHA256: 2d85b17b5a33d7f0415ab06b1d7962f999aab32394f27805bd896f193cd537fb
  • Pointer size: 132 Bytes
  • Size of remote file: 1 MB
data/cards/史跡カードフレーム(茶).png ADDED

Git LFS Details

  • SHA256: af327285a0500894bc1782447fe683ded8404177543f7ea179be32084138e0bb
  • Pointer size: 132 Bytes
  • Size of remote file: 1.1 MB
data/cards/史跡カードフレーム(赤).png ADDED

Git LFS Details

  • SHA256: f95a21d247aeb6da28db5d8742861f88730e12ab70438bde035d9c0e71611267
  • Pointer size: 132 Bytes
  • Size of remote file: 1.03 MB
data/cards/史跡カードフレーム(青).png ADDED

Git LFS Details

  • SHA256: 2472c4deee8b25a706205768b38860a607e1d33b11e8de737bd856488b62f68d
  • Pointer size: 131 Bytes
  • Size of remote file: 997 kB
data/cards/史跡カードフレーム(黄).png ADDED

Git LFS Details

  • SHA256: a2e0d893b1c8680f5964fc9c5110a4cc8b4821b183d37c70403abf51fdd9725a
  • Pointer size: 132 Bytes
  • Size of remote file: 1.12 MB
data/cards/史跡カードフレーム(黒).png ADDED

Git LFS Details

  • SHA256: eb8be786c42e1a3420294154e58537bf2869a2382de6f44ab4bf7bd85ba22a92
  • Pointer size: 131 Bytes
  • Size of remote file: 523 kB
data/cards/史跡カード指定マーク(区指定).png ADDED

Git LFS Details

  • SHA256: 45539b66d76bc545e97b713cadb89db2778ef9004b459aa3e4041cab0219b413
  • Pointer size: 130 Bytes
  • Size of remote file: 20.9 kB
data/cards/史跡カード指定マーク(国指定).png ADDED

Git LFS Details

  • SHA256: 37107a9ce93311f319c26153bf883daa69da59182b6056c72ce7b6dabb32a611
  • Pointer size: 130 Bytes
  • Size of remote file: 28.1 kB
data/cards/史跡カード指定マーク(市指定).png ADDED

Git LFS Details

  • SHA256: 325990154e64db3e28f74300f2d9950081a91c40ecf750805c675c1a094a5a08
  • Pointer size: 130 Bytes
  • Size of remote file: 20.4 kB
data/cards/史跡カード指定マーク(府指定).png ADDED

Git LFS Details

  • SHA256: 7e9d4626c185bd10c7d5d3a5a31dca5f848c72b6e5e2d94e1937ea922711761d
  • Pointer size: 130 Bytes
  • Size of remote file: 21.7 kB
data/cards/史跡カード指定マーク(村指定).png ADDED

Git LFS Details

  • SHA256: 16c9d021c13af2abeee84a6d71fb5e12204b377c2e352afe8fbb32714e96432b
  • Pointer size: 130 Bytes
  • Size of remote file: 20.8 kB
data/cards/史跡カード指定マーク(町指定).png ADDED

Git LFS Details

  • SHA256: 9839cd0b28445421f79121c8f26f4de5c342923e9a95aa1310995af74f61d0cf
  • Pointer size: 130 Bytes
  • Size of remote file: 20.3 kB
data/cards/史跡カード指定マーク(県指定).png ADDED

Git LFS Details

  • SHA256: 095e23bfbaff78fe5150369a2e784d8574b88b8278306d3d07da62d5ecd47b1f
  • Pointer size: 130 Bytes
  • Size of remote file: 21.5 kB
data/cards/史跡カード指定マーク(道指定).png ADDED

Git LFS Details

  • SHA256: e77ba7fb7454385d02fa6b0cac7c3034a6c35ea38851b692371ef3763cbdc539
  • Pointer size: 130 Bytes
  • Size of remote file: 21.8 kB
data/cards/史跡カード指定マーク(都指定).png ADDED

Git LFS Details

  • SHA256: fcccf910e2e0ab1035b813ad119d40827ce8c3c0620fc095b29e736ea260e1f8
  • Pointer size: 130 Bytes
  • Size of remote file: 22 kB
data/fonts/SourceHanSans-Bold.otf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a6e567d2ac4458caa26ab1bbbcad9de587d6be7f798f761e66a0c237383f19bb
3
+ size 17032800
data/fonts/SourceHanSans_LICENSE.txt ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Copyright 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font
2
+ Name 'Source'. Source is a trademark of Adobe in the United States
3
+ and/or other countries.
4
+
5
+ This Font Software is licensed under the SIL Open Font License,
6
+ Version 1.1.
7
+
8
+ This license is copied below, and is also available with a FAQ at:
9
+ http://scripts.sil.org/OFL
10
+
11
+ -----------------------------------------------------------
12
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
13
+ -----------------------------------------------------------
14
+
15
+ PREAMBLE
16
+ The goals of the Open Font License (OFL) are to stimulate worldwide
17
+ development of collaborative font projects, to support the font
18
+ creation efforts of academic and linguistic communities, and to
19
+ provide a free and open framework in which fonts may be shared and
20
+ improved in partnership with others.
21
+
22
+ The OFL allows the licensed fonts to be used, studied, modified and
23
+ redistributed freely as long as they are not sold by themselves. The
24
+ fonts, including any derivative works, can be bundled, embedded,
25
+ redistributed and/or sold with any software provided that any reserved
26
+ names are not used by derivative works. The fonts and derivatives,
27
+ however, cannot be released under any other type of license. The
28
+ requirement for fonts to remain under this license does not apply to
29
+ any document created using the fonts or their derivatives.
30
+
31
+ DEFINITIONS
32
+ "Font Software" refers to the set of files released by the Copyright
33
+ Holder(s) under this license and clearly marked as such. This may
34
+ include source files, build scripts and documentation.
35
+
36
+ "Reserved Font Name" refers to any names specified as such after the
37
+ copyright statement(s).
38
+
39
+ "Original Version" refers to the collection of Font Software
40
+ components as distributed by the Copyright Holder(s).
41
+
42
+ "Modified Version" refers to any derivative made by adding to,
43
+ deleting, or substituting -- in part or in whole -- any of the
44
+ components of the Original Version, by changing formats or by porting
45
+ the Font Software to a new environment.
46
+
47
+ "Author" refers to any designer, engineer, programmer, technical
48
+ writer or other person who contributed to the Font Software.
49
+
50
+ PERMISSION & CONDITIONS
51
+ Permission is hereby granted, free of charge, to any person obtaining
52
+ a copy of the Font Software, to use, study, copy, merge, embed,
53
+ modify, redistribute, and sell modified and unmodified copies of the
54
+ Font Software, subject to the following conditions:
55
+
56
+ 1) Neither the Font Software nor any of its individual components, in
57
+ Original or Modified Versions, may be sold by itself.
58
+
59
+ 2) Original or Modified Versions of the Font Software may be bundled,
60
+ redistributed and/or sold with any software, provided that each copy
61
+ contains the above copyright notice and this license. These can be
62
+ included either as stand-alone text files, human-readable headers or
63
+ in the appropriate machine-readable metadata fields within text or
64
+ binary files as long as those fields can be easily viewed by the user.
65
+
66
+ 3) No Modified Version of the Font Software may use the Reserved Font
67
+ Name(s) unless explicit written permission is granted by the
68
+ corresponding Copyright Holder. This restriction only applies to the
69
+ primary font name as presented to the users.
70
+
71
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
72
+ Software shall not be used to promote, endorse or advertise any
73
+ Modified Version, except to acknowledge the contribution(s) of the
74
+ Copyright Holder(s) and the Author(s) or with their explicit written
75
+ permission.
76
+
77
+ 5) The Font Software, modified or unmodified, in part or in whole,
78
+ must be distributed entirely under this license, and must not be
79
+ distributed under any other license. The requirement for fonts to
80
+ remain under this license does not apply to any document created using
81
+ the Font Software.
82
+
83
+ TERMINATION
84
+ This license becomes null and void if any of the above conditions are
85
+ not met.
86
+
87
+ DISCLAIMER
88
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
89
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
90
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
91
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
92
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
93
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
94
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
95
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
96
+ OTHER DEALINGS IN THE FONT SOFTWARE.
data/fonts/SourceHanSerif-Bold.otf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7100a6ab4f5847220347d915d784100eeaa0a8f145b666f2bfbe1565dc6b9610
3
+ size 25455872
data/fonts/SourceHanSerif_LICENSE.txt ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Copyright 2017-2022 Adobe (http://www.adobe.com/), with Reserved Font
2
+ Name 'Source'. Source is a trademark of Adobe in the United States
3
+ and/or other countries.
4
+
5
+ This Font Software is licensed under the SIL Open Font License,
6
+ Version 1.1.
7
+
8
+ This license is copied below, and is also available with a FAQ at:
9
+ http://scripts.sil.org/OFL
10
+
11
+ -----------------------------------------------------------
12
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
13
+ -----------------------------------------------------------
14
+
15
+ PREAMBLE
16
+ The goals of the Open Font License (OFL) are to stimulate worldwide
17
+ development of collaborative font projects, to support the font
18
+ creation efforts of academic and linguistic communities, and to
19
+ provide a free and open framework in which fonts may be shared and
20
+ improved in partnership with others.
21
+
22
+ The OFL allows the licensed fonts to be used, studied, modified and
23
+ redistributed freely as long as they are not sold by themselves. The
24
+ fonts, including any derivative works, can be bundled, embedded,
25
+ redistributed and/or sold with any software provided that any reserved
26
+ names are not used by derivative works. The fonts and derivatives,
27
+ however, cannot be released under any other type of license. The
28
+ requirement for fonts to remain under this license does not apply to
29
+ any document created using the fonts or their derivatives.
30
+
31
+ DEFINITIONS
32
+ "Font Software" refers to the set of files released by the Copyright
33
+ Holder(s) under this license and clearly marked as such. This may
34
+ include source files, build scripts and documentation.
35
+
36
+ "Reserved Font Name" refers to any names specified as such after the
37
+ copyright statement(s).
38
+
39
+ "Original Version" refers to the collection of Font Software
40
+ components as distributed by the Copyright Holder(s).
41
+
42
+ "Modified Version" refers to any derivative made by adding to,
43
+ deleting, or substituting -- in part or in whole -- any of the
44
+ components of the Original Version, by changing formats or by porting
45
+ the Font Software to a new environment.
46
+
47
+ "Author" refers to any designer, engineer, programmer, technical
48
+ writer or other person who contributed to the Font Software.
49
+
50
+ PERMISSION & CONDITIONS
51
+ Permission is hereby granted, free of charge, to any person obtaining
52
+ a copy of the Font Software, to use, study, copy, merge, embed,
53
+ modify, redistribute, and sell modified and unmodified copies of the
54
+ Font Software, subject to the following conditions:
55
+
56
+ 1) Neither the Font Software nor any of its individual components, in
57
+ Original or Modified Versions, may be sold by itself.
58
+
59
+ 2) Original or Modified Versions of the Font Software may be bundled,
60
+ redistributed and/or sold with any software, provided that each copy
61
+ contains the above copyright notice and this license. These can be
62
+ included either as stand-alone text files, human-readable headers or
63
+ in the appropriate machine-readable metadata fields within text or
64
+ binary files as long as those fields can be easily viewed by the user.
65
+
66
+ 3) No Modified Version of the Font Software may use the Reserved Font
67
+ Name(s) unless explicit written permission is granted by the
68
+ corresponding Copyright Holder. This restriction only applies to the
69
+ primary font name as presented to the users.
70
+
71
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
72
+ Software shall not be used to promote, endorse or advertise any
73
+ Modified Version, except to acknowledge the contribution(s) of the
74
+ Copyright Holder(s) and the Author(s) or with their explicit written
75
+ permission.
76
+
77
+ 5) The Font Software, modified or unmodified, in part or in whole,
78
+ must be distributed entirely under this license, and must not be
79
+ distributed under any other license. The requirement for fonts to
80
+ remain under this license does not apply to any document created using
81
+ the Font Software.
82
+
83
+ TERMINATION
84
+ This license becomes null and void if any of the above conditions are
85
+ not met.
86
+
87
+ DISCLAIMER
88
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
89
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
90
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
91
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
92
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
93
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
94
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
95
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
96
+ OTHER DEALINGS IN THE FONT SOFTWARE.
license.md ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Pieno Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files
7
+ (/app.py, /src/*, /requirements.txt, /readme.md and /data/cards/*, the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
23
+
24
+
readme.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ This software(/app.py, /src/*, /requirements.txt, /readme.md and /data/cards/*) is released under the MIT License, see LICENSE.md.
2
+ /data/fonts/SourceHanSans-Bold.otf and SourceHanSerif-Bold.otf are released under the SIL OPEN FONT LICENSE, see SourceHanSans_LICENSE.txt and SourceHanSerif_LICENSE.txt.
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ gradio==4.8.0
2
+ Pillow==9.5.0
3
+ numpy==1.23.5
4
+ gltflib==1.0.13
5
+ triangle==20230923
6
+ opencv-python==4.8.0.76
src/card_model.py ADDED
@@ -0,0 +1,311 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import numpy as np
3
+ from PIL import Image as PIL_Image # gltflibのImageと被るので別名にする。
4
+ import struct
5
+ import uuid
6
+
7
+ from gltflib import (
8
+ GLTF, GLTFModel, Asset, Scene, Node, Mesh, Primitive, Attributes, Buffer, BufferView, Image, Texture, TextureInfo, Material, Sampler, Accessor, AccessorType,
9
+ BufferTarget, ComponentType, GLBResource, PBRMetallicRoughness)
10
+
11
+ # 共通設定情報
12
+ # バイナリ部分のオフセットやサイズに関わらない部分は予め定義できる。
13
+ # アセット
14
+ asset=Asset()
15
+
16
+ # イメージ
17
+ images=[
18
+ Image(mimeType='image/jpeg', bufferView=4),
19
+ Image(mimeType='image/jpeg',bufferView=5),
20
+ Image(mimeType='image/jpeg',bufferView=6),
21
+ Image(mimeType='image/jpeg',bufferView=7),
22
+ Image(mimeType='image/jpeg',bufferView=8),
23
+ Image(mimeType='image/jpeg',bufferView=9),
24
+ ]
25
+ # サンプラー
26
+ samplers = [Sampler(magFilter=9728, minFilter=9984)] # magFilter:最近傍フィルタリング、minFilter:ミップマップ+最近傍フィルタリング
27
+
28
+ # テクスチャ
29
+ textures = [
30
+ Texture(name='Front',sampler=0,source=0),
31
+ Texture(name='Back',sampler=0,source=1),
32
+ Texture(name='Left',sampler=0,source=2), # 左
33
+ Texture(name='Right',sampler=0,source=3), # 右
34
+ Texture(name='Top',sampler=0,source=4), # 上
35
+ Texture(name='Bottom',sampler=0,source=5), # 下
36
+ ]
37
+
38
+ # マテリアル
39
+ materials = [
40
+ Material(
41
+ pbrMetallicRoughness=PBRMetallicRoughness(
42
+ baseColorTexture=TextureInfo(index=0),
43
+ metallicFactor=0,
44
+ roughnessFactor=0.5
45
+ ),
46
+ name='Material0',
47
+ alphaMode='OPAQUE',
48
+ doubleSided=False
49
+ ),
50
+ Material(
51
+ pbrMetallicRoughness=PBRMetallicRoughness(
52
+ baseColorTexture=TextureInfo(index=1),
53
+ metallicFactor=0,
54
+ roughnessFactor=0.5
55
+ ),
56
+ name='Material1',
57
+ alphaMode='OPAQUE',
58
+ doubleSided=False
59
+ ),
60
+ Material(
61
+ pbrMetallicRoughness=PBRMetallicRoughness(
62
+ baseColorTexture=TextureInfo(index=2),
63
+ metallicFactor=0,
64
+ roughnessFactor=0.5
65
+ ),
66
+ name='Material2',
67
+ alphaMode='OPAQUE',
68
+ doubleSided=True
69
+ ),
70
+ Material(
71
+ pbrMetallicRoughness=PBRMetallicRoughness(
72
+ baseColorTexture=TextureInfo(index=3),
73
+ metallicFactor=0,
74
+ roughnessFactor=0.5
75
+ ),
76
+ name='Material3',
77
+ alphaMode='OPAQUE',
78
+ doubleSided=True
79
+ ),
80
+ Material(
81
+ pbrMetallicRoughness=PBRMetallicRoughness(
82
+ baseColorTexture=TextureInfo(index=4),
83
+ metallicFactor=0,
84
+ roughnessFactor=0.5
85
+ ),
86
+ name='Material4',
87
+ alphaMode='OPAQUE',
88
+ doubleSided=True
89
+ ),
90
+ Material(
91
+ pbrMetallicRoughness=PBRMetallicRoughness(
92
+ baseColorTexture=TextureInfo(index=5),
93
+ metallicFactor=0,
94
+ roughnessFactor=0.5
95
+ ),
96
+ name='Material5',
97
+ alphaMode='OPAQUE',
98
+ doubleSided=True
99
+ ),
100
+ ]
101
+
102
+ # メッシュ
103
+ meshes = [
104
+ Mesh(name='Front', primitives=[Primitive(attributes=Attributes(POSITION=0, NORMAL=1,TEXCOORD_0=2), indices=3, material=0)]),
105
+ Mesh(name='Back', primitives=[Primitive(attributes=Attributes(POSITION=0, NORMAL=1,TEXCOORD_0=2), indices=3, material=1)]),
106
+ Mesh(name='Left', primitives=[Primitive(attributes=Attributes(POSITION=0, NORMAL=1,TEXCOORD_0=2), indices=3, material=2)]),
107
+ Mesh(name='Right', primitives=[Primitive(attributes=Attributes(POSITION=0, NORMAL=1,TEXCOORD_0=2), indices=3, material=3)]),
108
+ Mesh(name='Top', primitives=[Primitive(attributes=Attributes(POSITION=0, NORMAL=1,TEXCOORD_0=2), indices=3, material=4)]),
109
+ Mesh(name='Bottom', primitives=[Primitive(attributes=Attributes(POSITION=0, NORMAL=1,TEXCOORD_0=2), indices=3, material=5)]),
110
+ ]
111
+
112
+ def create_card_model(front_img_bytearray, back_img_bytearray, option_dict):
113
+
114
+ is_thick = True if option_dict['厚み'] == '有' else False
115
+
116
+ # 表面画像
117
+ front_img = PIL_Image.open(front_img_bytearray).convert('RGB')
118
+ front_bytearray = io.BytesIO()
119
+ front_img.save(front_bytearray, format="JPEG", quality=95) # JPEG保存を強制
120
+ front_bytearray = front_bytearray.getvalue()
121
+ front_bytelen = len(front_bytearray)
122
+
123
+ # 裏面画像
124
+ back_img = PIL_Image.open(back_img_bytearray).convert('RGB')
125
+ back_bytearray = io.BytesIO()
126
+ back_img.save(back_bytearray, format="JPEG", quality=95) # JPEG保存を強制
127
+ back_bytearray = back_bytearray.getvalue()
128
+ back_bytelen = len(back_bytearray)
129
+
130
+ # 側面画像の作成(裏面の端の色を延長する)
131
+ back_img_array = np.array(back_img)
132
+ left_side_img = PIL_Image.fromarray(np.tile(back_img_array[:, [0], :], [1, 32, 1]))
133
+ right_side_img = PIL_Image.fromarray(np.tile(back_img_array[:, [back_img_array.shape[1] - 1], :], [1, 32, 1]))
134
+ top_side_img = PIL_Image.fromarray(np.tile(back_img_array[[0], :, :], [32, 1, 1]))
135
+ bottom_side_img = PIL_Image.fromarray(np.tile(back_img_array[[back_img_array.shape[0] - 1], :, :], [32, 1, 1]))
136
+ left_side_bytearray = io.BytesIO()
137
+ left_side_img.save(left_side_bytearray, format="JPEG", quality=95) # JPEG保存を強制
138
+ left_side_bytearray = left_side_bytearray.getvalue()
139
+ left_side_bytelen = len(left_side_bytearray)
140
+ right_side_bytearray = io.BytesIO()
141
+ right_side_img.save(right_side_bytearray, format="JPEG", quality=95) # JPEG保存を強制
142
+ right_side_bytearray = right_side_bytearray.getvalue()
143
+ right_side_bytelen = len(right_side_bytearray)
144
+ top_side_bytearray = io.BytesIO()
145
+ top_side_img.save(top_side_bytearray, format="JPEG", quality=95) # JPEG保存を強制
146
+ top_side_bytearray = top_side_bytearray.getvalue()
147
+ top_side_bytelen = len(top_side_bytearray)
148
+ bottom_side_bytearray = io.BytesIO()
149
+ bottom_side_img.save(bottom_side_bytearray, format="JPEG", quality=95) # JPEG保存を強制
150
+ bottom_side_bytearray = bottom_side_bytearray.getvalue()
151
+ bottom_side_bytelen = len(bottom_side_bytearray)
152
+
153
+ # 頂点データ(POSITION)
154
+ vertices = [
155
+ (-1.0, -1.0, 0.0),
156
+ ( 1.0, -1.0, 0.0),
157
+ (-1.0, 1.0, 0.0),
158
+ ( 1.0, 1.0, 0.0)
159
+ ]
160
+ vertex_bytearray = bytearray()
161
+ for vertex in vertices:
162
+ for value in vertex:
163
+ vertex_bytearray.extend(struct.pack('f', value))
164
+ vertex_bytelen = len(vertex_bytearray)
165
+ mins = [min([vertex[i] for vertex in vertices]) for i in range(3)]
166
+ maxs = [max([vertex[i] for vertex in vertices]) for i in range(3)]
167
+
168
+ # 法線データ(NORMAL)
169
+ normals = [( 0.0, 0.0, 1.0)] * 4
170
+ normal_bytearray = bytearray()
171
+ for normal in normals:
172
+ for value in normal:
173
+ normal_bytearray.extend(struct.pack('f', value))
174
+ normal_bytelen = len(normal_bytearray)
175
+
176
+ # テクスチャ座標(TEXCOORD_0)
177
+ texcoord_0s = [
178
+ (0.0, 1.0),
179
+ (1.0, 1.0),
180
+ (0.0, 0.0),
181
+ (1.0, 0.0)
182
+ ]
183
+ texcoord_0_bytearray = bytearray()
184
+ for texcoord_0 in texcoord_0s:
185
+ for value in texcoord_0:
186
+ texcoord_0_bytearray.extend(struct.pack('f', value))
187
+ texcoord_0_bytelen = len(texcoord_0_bytearray)
188
+
189
+ # 頂点インデックス
190
+ vertex_indices = [0, 1, 2, 1, 3, 2]
191
+ vertex_index_bytearray = bytearray()
192
+ for value in vertex_indices:
193
+ vertex_index_bytearray.extend(struct.pack('H', value))
194
+ vertex_index_bytelen = len(vertex_index_bytearray)
195
+
196
+ # バイナリデータ部分の結合
197
+ bytearray_list = [
198
+ vertex_bytearray,
199
+ normal_bytearray,
200
+ texcoord_0_bytearray,
201
+ vertex_index_bytearray,
202
+ front_bytearray,
203
+ back_bytearray,
204
+ left_side_bytearray,
205
+ right_side_bytearray,
206
+ top_side_bytearray,
207
+ bottom_side_bytearray,
208
+ ]
209
+ bytelen_list = [
210
+ vertex_bytelen,
211
+ normal_bytelen,
212
+ texcoord_0_bytelen,
213
+ vertex_index_bytelen,
214
+ front_bytelen,
215
+ back_bytelen,
216
+ left_side_bytelen,
217
+ right_side_bytelen,
218
+ top_side_bytelen,
219
+ bottom_side_bytelen
220
+ ]
221
+ bytelen_cumsum_list = list(np.cumsum(bytelen_list))
222
+ bytelen_cumsum_list = list(map(lambda x: int(x), bytelen_cumsum_list))
223
+
224
+ all_bytearray = bytearray()
225
+ for temp_bytearray in bytearray_list:
226
+ all_bytearray.extend(temp_bytearray)
227
+ offset_list = [0] + bytelen_cumsum_list # 最初のオフセットは0
228
+ offset_list.pop() # 末尾を削除
229
+
230
+ # リソースの作成
231
+ resources = [GLBResource(data=all_bytearray)]
232
+
233
+ # 各種設定
234
+ # バッファ
235
+ buffers = [Buffer(byteLength=len(all_bytearray))]
236
+
237
+ # バッファビュー
238
+ bufferViews = [
239
+ BufferView(buffer=0, byteOffset=offset_list[0], byteLength=bytelen_list[0], target=BufferTarget.ARRAY_BUFFER.value),
240
+ BufferView(buffer=0, byteOffset=offset_list[1], byteLength=bytelen_list[1], target=BufferTarget.ARRAY_BUFFER.value),
241
+ BufferView(buffer=0, byteOffset=offset_list[2], byteLength=bytelen_list[2], target=BufferTarget.ARRAY_BUFFER.value),
242
+ BufferView(buffer=0, byteOffset=offset_list[3], byteLength=bytelen_list[3], target=BufferTarget.ELEMENT_ARRAY_BUFFER.value),
243
+ BufferView(buffer=0, byteOffset=offset_list[4], byteLength=bytelen_list[4], target=None),
244
+ BufferView(buffer=0, byteOffset=offset_list[5], byteLength=bytelen_list[5], target=None),
245
+ BufferView(buffer=0, byteOffset=offset_list[6], byteLength=bytelen_list[6], target=None),
246
+ BufferView(buffer=0, byteOffset=offset_list[7], byteLength=bytelen_list[7], target=None),
247
+ BufferView(buffer=0, byteOffset=offset_list[8], byteLength=bytelen_list[8], target=None),
248
+ BufferView(buffer=0, byteOffset=offset_list[9], byteLength=bytelen_list[9], target=None),
249
+ ]
250
+
251
+ # アクセサー
252
+ accessors = [
253
+ Accessor(bufferView=0, componentType=ComponentType.FLOAT.value, count=len(vertices), type=AccessorType.VEC3.value, max=maxs, min=mins),
254
+ Accessor(bufferView=1, componentType=ComponentType.FLOAT.value, count=len(normals), type=AccessorType.VEC3.value, max=None, min=None),
255
+ Accessor(bufferView=2, componentType=ComponentType.FLOAT.value, count=len(texcoord_0s), type=AccessorType.VEC2.value, max=None, min=None),
256
+ Accessor(bufferView=3, componentType=ComponentType.UNSIGNED_SHORT.value, count=len(vertex_indices), type=AccessorType.SCALAR.value, max=None, min=None),
257
+ ]
258
+
259
+ # ノード
260
+ card_thickness = 0.025
261
+ card_ratio_x = 0.8679999709129333
262
+ card_ratio_y = 1.2130000591278076
263
+ card_ratio_z = 1
264
+ if is_thick:
265
+
266
+ nodes = [
267
+ Node(mesh=0, scale=[card_ratio_x, card_ratio_y, card_ratio_z], rotation=None, translation=[0, 0, card_thickness]),
268
+ Node(mesh=1, scale=[card_ratio_x, card_ratio_y, card_ratio_z], rotation=[0, 1, 0, 0], translation=[0, 0, -card_thickness]),
269
+ 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]), # 左
270
+ 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]), # 右
271
+ 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]), # 上
272
+ 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]), # 下
273
+ ]
274
+ else:
275
+ nodes = [
276
+ Node(mesh=0, scale=[card_ratio_x, card_ratio_y, card_ratio_z], rotation=None),
277
+ Node(mesh=1, scale=[card_ratio_x, card_ratio_y, card_ratio_z], rotation=[0, 1, 0, 0])
278
+ ]
279
+
280
+ # シーン
281
+ scene = 0
282
+ if is_thick:
283
+ scenes = [Scene(name='Scene', nodes=[0, 1, 2, 3, 4, 5])]
284
+ else:
285
+ scenes = [Scene(name='Scene', nodes=[0, 1])]
286
+
287
+ model = GLTFModel(
288
+ asset=asset,
289
+ buffers=buffers,
290
+ bufferViews=bufferViews,
291
+ accessors=accessors,
292
+ images=images,
293
+ samplers=samplers,
294
+ textures=textures,
295
+ materials=materials,
296
+ meshes=meshes,
297
+ nodes=nodes,
298
+ scene=scene,
299
+ scenes=scenes
300
+ )
301
+
302
+ gltf = GLTF(model=model, resources=resources)
303
+
304
+ tmp_filename = uuid.uuid4().hex
305
+ model_path = f'../tmp/{tmp_filename}.glb'
306
+
307
+ gltf.export(model_path)
308
+
309
+ return model_path
310
+
311
+
src/constants.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ model_info_message = \
2
+ '''\
3
+ 使える3Dモデルの情報です。「/info:番号」と入力すると入力情報の詳細を表示します。
4
+ 1.史跡カード額縁:歴史的建造物などを飾るカードの額縁に写真を飾ります。
5
+ 2.宝石額縁:きらびやかな宝石の額縁に写真を飾ります。
6
+ 3.化石額縁:種々の化石の額縁に写真を飾ります。
7
+ 4.猫額縁:猫のイラストの額縁に写真を飾ります。
8
+ A.写真ボックス:写真に厚みを付けて立体的にします。
9
+ B.オブジェクト切り抜き:(背景が単色であることを想定。上手くいかない場合もあります。)写真から切り抜いたオブジェクトに厚みを付けて立体的にします。
10
+ '''
11
+
12
+ upload_format_dict = \
13
+ {
14
+ '1': '/upload:1|画像ファイル名|緯度|経度|タイトル|CTユーザ名|Discordユーザ名|厚み|色|史跡種類|マーク|訪問難度|説明文',
15
+ '2': '/upload:2|画像ファイル名|緯度|経度|タイトル|CTユーザ名|Discordユーザ名|厚み|説明文',
16
+ '3': '/upload:3|画像ファイル名|緯度|経度|タイトル|CTユーザ名|Discordユーザ名|厚み|説明文',
17
+ '4': '/upload:4|画像ファイル名|緯度|経度|タイトル|CTユーザ名|Discordユーザ名|厚み|説明文',
18
+ 'A': '/upload:A|画像ファイル名|緯度|経度',
19
+ 'B': '/upload:B|画像ファイル名|緯度|経度',
20
+ }
21
+
22
+ model_info_detail_dict = \
23
+ {
24
+ '1': \
25
+ '''\
26
+ 補足1.「厚み」は「有」、「無」のいずれかを選んでください。
27
+ 補足2.「色」は以下の基準で選んでください。
28
+ 「茶」: 貝塚、集落跡、古墳、墓地等
29
+ 「白」: 都城跡、国郡庁、城跡、官公庁、戦跡、その他政治に関する遺跡
30
+ 「橙」: 社寺跡、その他祭祀信仰に関する遺跡
31
+ 「黄」: 学校、研究施設、文化施設、その他教育・学術・文化に関する遺跡
32
+ 「赤」: 医療・福祉施設、生活関連施設等
33
+ 「青」: 交通・通信施設、治山治水施設、生産遺跡、その他経済・生産活動に関する遺跡
34
+ 「黒」: 墳墓(大名・著名人)・碑
35
+ 「緑」: 旧宅、園池
36
+ 「紫」: 外国及び外国人に関する遺跡
37
+ 補足3.「マーク」は「国指定」、「都指定」、「道指定」、「府指定」、「県指定」、「区指定」、「市指定」、「町指定」、「村指定」のどれかから選んでください。
38
+ 補足4.「訪問難度」は1から5の整数値で指定してください。
39
+ ''',
40
+ '2': '補足1.「厚み」は「有」、「無」のいずれかを選んでください。',
41
+ '3': '補足1.「厚み」は「有」、「無」のいずれかを選んでください。',
42
+ '4': '補足1.「厚み」は「有」、「無」のいずれかを選んでください。',
43
+ }
44
+
45
+ def get_option_keys_from_upload_format(upload_format):
46
+ vertical_bar_indices = [i for i, char in enumerate(upload_format) if char == '|']
47
+ vertical_bar_indices.append(len(upload_format))
48
+ option_keys = [upload_format[vertical_bar_indices[i-1]+1:vertical_bar_indices[i]] for i in range(1, len(vertical_bar_indices))]
49
+ return option_keys
50
+
51
+ upload_format_option_dict = {
52
+ key: get_option_keys_from_upload_format(value)
53
+ for key, value in upload_format_dict.items()
54
+ }
55
+
56
+ upload_format_re_dict = \
57
+ {
58
+ '1': '^/upload:(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)',
59
+ '2': '^/upload:(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)',
60
+ '3': '^/upload:(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)',
61
+ '4': '^/upload:(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)',
62
+ 'A': '^/upload:(.*)\|(.*)\|(.*)\|(.*)',
63
+ 'B': '^/upload:(.*)\|(.*)\|(.*)\|(.*)',
64
+ }
65
+
66
+ front_card_img_dict = \
67
+ {
68
+ '1': 'data/cards/史跡カードフレーム(白).png', # 複数種類あり別の箇所で条件分岐で指定するが、ここではとりあえず白を指定
69
+ '2': 'data/cards/front/jewels.png',
70
+ '3': 'data/cards/front/fossils.png',
71
+ '4': 'data/cards/front/cats.png'
72
+ }
73
+
74
+ back_card_img_dict = \
75
+ {
76
+ '1': 'data/cards/史跡カード(裏面).png',
77
+ '2': 'data/cards/back/jewels.png',
78
+ '3': 'data/cards/back/fossils.png',
79
+ '4': 'data/cards/back/cats.png'
80
+ }
src/display_message_en.json ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "header": "# 3D Card Creation \nPlease enter various settings, upload an image and specify its range. Then, press the \"Create 3D Card\" button. \nOnce the 3D card is completed, it will be displayed at the bottom of the screen and you will be able to download it.",
3
+ "tab_label_card_general": "Card",
4
+ "tab_label_historic_site_card": "Historic Site Card",
5
+ "label_title": "Title",
6
+ "placeholder_title": "The title displayed on the top of the card.",
7
+ "label_card_type": "Card Type",
8
+ "model_type_dict": {
9
+ "2": "Jewel",
10
+ "3": "Fossil",
11
+ "4": "Cat"
12
+ },
13
+ "label_description": "Description",
14
+ "placeholder_description": "Please provide a brief description, up to about 90 characters.",
15
+ "label_is_thick": "Thickness",
16
+ "info_is_thick": "Please check this option if you want to add thickness to the card.",
17
+ "label_image": "Image",
18
+ "label_button": "Start 3D Card Creation",
19
+ "label_color": "Color(Historic Site Classification)",
20
+ "color_dict": {
21
+ "茶": "Brown: Shell mounds, settlement ruins, ancient tombs, graveyards",
22
+ "白": "Administrative buildings, battle sites, and other political-related ruins",
23
+ "橙": "Orange: Ruins of shrines and temples, and other relics related to rituals and beliefs",
24
+ "黄": "Yellow: Schools, research facilities, cultural facilities, and other ruins related to education, academia, and culture",
25
+ "赤": "Red: Medical and welfare facilities, living-related facilities",
26
+ "青": "Blue: Transportation and communication facilities, flood control and forest management facilities, production sites etc.",
27
+ "黒": "Black: Tombs (of lords and famous persons) and monuments",
28
+ "緑": "Green: Former residences, garden ponds",
29
+ "紫": "Purple: Ruins related to foreigners and foreign countries"
30
+ },
31
+ "label_mark": "Designation Type",
32
+ "mark_list": ["国指定", "都指定", "道指定", "府指定", "県指定", "区指定", "市指定", "町指定", "村指定"],
33
+ "label_historic_site_type": "Historic Site Type",
34
+ "placeholder_historic_site_type": "ex. Statue, Monument, Site, etc.",
35
+ "label_difficulty": "Difficulty",
36
+ "footer_historic_site_card": "The images for the Historic Site Cards are borrowed from the Historic Site Card Assets on this website. \nhttps://historic-site-card.com/find"
37
+ }
src/display_message_ja.json ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "header": "# 3Dカード作成 \n各種設定値の入力、画像のアップロードと範囲の指定を行った後、「3Dカード作成」ボタンを押してください。 \n3Dカードが完成したら画面下部に表示され、ダウンロードすることが出来ます。",
3
+ "tab_label_card_general": "カード(一般)",
4
+ "tab_label_historic_site_card": "史跡カード",
5
+ "label_title": "タイトル",
6
+ "placeholder_title": "カード上部に表示されるタイトルです。",
7
+ "label_card_type": "カード種類",
8
+ "model_type_dict": {
9
+ "2": "宝石",
10
+ "3": "化石",
11
+ "4": "猫"
12
+ },
13
+ "label_description": "説明文",
14
+ "placeholder_description": "全角50字程度までの簡単な説明を記載して下さい。",
15
+ "label_is_thick": "厚み",
16
+ "info_is_thick": "カードに厚みを付けるときはチェックして下さい。",
17
+ "label_image": "画像",
18
+ "label_button": "3Dカード作成",
19
+ "label_color": "色(史跡の分類)",
20
+ "color_dict": {
21
+ "茶": "「茶」: 貝塚、集落跡、古墳、墓地等",
22
+ "白": "「白」: 都城跡、国郡庁、城跡、官公庁、戦跡、その他政治に関する遺跡",
23
+ "橙": "「橙」: 社寺跡、その他祭祀信仰に関する遺跡",
24
+ "黄": "「黄」: 学校、研究施設、文化施設、その他教育・学術・文化に関する遺跡",
25
+ "赤": "「赤」: 医療・福祉施設、生活関連施設等",
26
+ "青": "「青」: 交通・通信施設、治山治水施設、生産遺跡、その他経済・生産活動に関する遺跡",
27
+ "黒": "「黒」: 墳墓(大名・著名人)・碑",
28
+ "緑": "「緑」: 旧宅、園池",
29
+ "紫": "「紫」: 外国及び外国人に関する遺跡"
30
+ },
31
+ "label_mark": "指定種類",
32
+ "mark_list": ["国指定", "都指定", "道指定", "府指定", "県指定", "区指定", "市指定", "町指定", "村指定"],
33
+ "label_historic_site_type": "史跡種類",
34
+ "placeholder_historic_site_type": "ex.銅像、石碑、記念碑、跡地、モニュメントなど。",
35
+ "label_difficulty": "訪問難度",
36
+ "footer_historic_site_card": "史跡カードの画像は、こちらのウェブサイトの史跡カードアセットをお借りしております。 \nhttps://historic-site-card.com/find"
37
+ }
src/extracted_objects_model.py ADDED
@@ -0,0 +1,265 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import numpy as np
3
+ from PIL import Image as PIL_Image # gltflibのImageと被るので別名にする。
4
+ import cv2
5
+ import struct
6
+ import triangle
7
+ import uuid
8
+
9
+ from gltflib import (
10
+ GLTF, GLTFModel, Asset, Scene, Node, Mesh, Primitive, Attributes, Buffer, BufferView, Image, Texture, TextureInfo, Material, Sampler, Accessor, AccessorType,
11
+ BufferTarget, ComponentType, GLBResource, PBRMetallicRoughness)
12
+
13
+ # 表面及び裏面の頂点リストの作成
14
+ def make_front_and_back_vertex_list(coordinate_list, img):
15
+
16
+ # 表面の頂点
17
+ front_vertex_list = []
18
+ # 裏面の頂点
19
+ back_vertex_list = []
20
+ for coordinates in coordinate_list:
21
+ front_vertices = []
22
+ back_vertices = []
23
+ # Y軸方向は画像とGLBで上下が逆になるので注意
24
+ for coordinate in coordinates:
25
+ front_vertices.append((coordinate[0] * 2 / img.size[0] - 1.0, -(coordinate[1] * 2 / img.size[1] - 1.0), 0.2))
26
+ back_vertices.append((coordinate[0] * 2 / img.size[0] - 1.0, -(coordinate[1] * 2 / img.size[1] - 1.0), -0.2))
27
+
28
+ front_vertex_list.append(front_vertices)
29
+ back_vertex_list.append(back_vertices)
30
+
31
+ return front_vertex_list, back_vertex_list
32
+
33
+ # メッシュの各種情報の作成
34
+ def make_mesh_data(coordinate_list, img):
35
+ front_vertex_list, back_vertex_list = make_front_and_back_vertex_list(coordinate_list, img)
36
+
37
+ # 頂点データ(POSITION)
38
+ vertices = []
39
+ # 頂点インデックス決定時に使うオフセット値のリスト
40
+ front_offset = 0
41
+ front_offset_list = []
42
+ back_offset_list = []
43
+ for front_vertices, back_vertices in zip(front_vertex_list, back_vertex_list):
44
+ vertices.extend(front_vertices)
45
+ vertices.extend(back_vertices)
46
+
47
+ back_offset = front_offset + len(front_vertices)
48
+ front_offset_list.append(front_offset)
49
+ back_offset_list.append(back_offset)
50
+ front_offset += len(front_vertices) + len(back_vertices)
51
+
52
+ # 法線データ(NORMAL)
53
+ normals = []
54
+ for front_vertices, back_vertices in zip(front_vertex_list, back_vertex_list):
55
+ normals.extend([( 0.0, 0.0, 1.0)] * len(front_vertices))
56
+ normals.extend([( 0.0, 0.0, -1.0)] * len(back_vertices))
57
+
58
+ # テクスチャ座標(TEXCOORD_0)
59
+ # 画像は左上原点になり、Y軸の上下を変える必要がある。
60
+ texcoord_0s = [((vertex[0] + 1.0) / 2.0, 1.0 - ((vertex[1] + 1.0) / 2.0) ) for vertex in vertices]
61
+
62
+ # 頂点インデックス
63
+ vertex_indices = []
64
+ for front_vertices, back_vertices, front_offset, back_offset \
65
+ in zip(front_vertex_list, back_vertex_list, front_offset_list, back_offset_list):
66
+ polygon = {
67
+ 'vertices': np.array(front_vertices)[:, :2],
68
+ 'segments': np.array([( i, (i + 1) % (len(front_vertices)) ) for i in range(len(front_vertices))]) # 各辺を定義
69
+ }
70
+ triangulate_result = triangle.triangulate(polygon, 'p')
71
+ vertex_indices.extend(list(np.array(triangulate_result['triangles']+front_offset).flatten())) # 表面
72
+ vertex_indices.extend(list((np.array(triangulate_result['triangles'])+back_offset).flatten())) # 裏面
73
+ vertex_indices.extend(list(np.array([[front_offset + i,
74
+ front_offset + (i + 1) % len(front_vertices),
75
+ back_offset + i]
76
+ for i in range(len(front_vertices))]).flatten())) # 側面1
77
+ vertex_indices.extend(list(np.array([[back_offset + i,
78
+ back_offset + (i + 1) % len(back_vertices),
79
+ front_offset+ (i + 1) % len(front_vertices)] for i in range(len(front_vertices))]).flatten())) # 側面2
80
+
81
+ return vertices, normals, texcoord_0s, vertex_indices
82
+
83
+ def create_extracted_objects_model(img_bytearray):
84
+
85
+ # 画像の取得
86
+ img = PIL_Image.open(img_bytearray).convert('RGB')
87
+ img_bytearray = io.BytesIO()
88
+ img.save(img_bytearray, format="JPEG", quality=95)
89
+ img_bytearray = img_bytearray.getvalue()
90
+ img_bytelen = len(img_bytearray)
91
+
92
+ # 3Dモデルのスケールの計算
93
+ scale_factor = np.power(img.size[0] * img.size[1], 0.5)
94
+ scale = (img.size[0] / scale_factor, img.size[1] / scale_factor, 0.4)
95
+
96
+ # 画像主要部分の頂点の取得
97
+ base_color = img.getpixel((0, 0))
98
+ mask = PIL_Image.new('RGB', img.size)
99
+ for i in range(img.size[0]):
100
+ for j in range(img.size[1]):
101
+ if base_color == img.getpixel((i, j)):
102
+ mask.putpixel((i, j), (0, 0, 0))
103
+ else:
104
+ mask.putpixel((i, j), (255, 255, 255))
105
+
106
+ opening = cv2.morphologyEx(np.array(mask), cv2.MORPH_OPEN, kernel=np.ones((15, 15),np.uint8))
107
+ contours, _ = cv2.findContours(cv2.cvtColor(np.array(opening), cv2.COLOR_RGB2GRAY), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
108
+ coordinate_list = []
109
+ for contour in contours:
110
+ coordinates = []
111
+ for [[x, y]] in contour:
112
+ coordinates.append((x, y))
113
+ coordinate_list.append(coordinates)
114
+
115
+ # 各種データの作成
116
+ vertices, normals, texcoord_0s, vertex_indices = make_mesh_data(coordinate_list, img)
117
+
118
+ # 頂点データ(POSITION)
119
+ vertex_bytearray = bytearray()
120
+ for vertex in vertices:
121
+ for value in vertex:
122
+ vertex_bytearray.extend(struct.pack('f', value))
123
+ vertex_bytelen = len(vertex_bytearray)
124
+ mins = [min([vertex[i] for vertex in vertices]) for i in range(3)]
125
+ maxs = [max([vertex[i] for vertex in vertices]) for i in range(3)]
126
+
127
+ # 法線データ(NORMAL)
128
+ normal_bytearray = bytearray()
129
+ for normal in normals:
130
+ for value in normal:
131
+ normal_bytearray.extend(struct.pack('f', value))
132
+ normal_bytelen = len(normal_bytearray)
133
+
134
+ # テクスチャ座標(TEXCOORD_0)
135
+ texcoord_0s = [
136
+ ((vertex[0] + 1.0) / 2.0, 1.0 - ((vertex[1] + 1.0) / 2.0) ) for vertex in vertices
137
+ ]
138
+ texcoord_0_bytearray = bytearray()
139
+ for texcoord_0 in texcoord_0s:
140
+ for value in texcoord_0:
141
+ texcoord_0_bytearray.extend(struct.pack('f', value))
142
+ texcoord_0_bytelen = len(texcoord_0_bytearray)
143
+
144
+ # 頂点インデックス
145
+ vertex_index_bytearray = bytearray()
146
+ for value in vertex_indices:
147
+ vertex_index_bytearray.extend(struct.pack('H', value))
148
+ vertex_index_bytelen = len(vertex_index_bytearray)
149
+
150
+ # バイナリデータ部分の結合
151
+ bytearray_list = [
152
+ vertex_bytearray,
153
+ normal_bytearray,
154
+ texcoord_0_bytearray,
155
+ vertex_index_bytearray,
156
+ img_bytearray,
157
+ ]
158
+ bytelen_list = [
159
+ vertex_bytelen,
160
+ normal_bytelen,
161
+ texcoord_0_bytelen,
162
+ vertex_index_bytelen,
163
+ img_bytelen,
164
+ ]
165
+ bytelen_cumsum_list = list(np.cumsum(bytelen_list))
166
+ bytelen_cumsum_list = list(map(lambda x: int(x), bytelen_cumsum_list))
167
+
168
+ all_bytearray = bytearray()
169
+ for temp_bytearray in bytearray_list:
170
+ all_bytearray.extend(temp_bytearray)
171
+ offset_list = [0] + bytelen_cumsum_list # 最初のオフセットは0
172
+ offset_list.pop() # 末尾を削除
173
+
174
+ # リソースの作成
175
+ resources = [GLBResource(data=all_bytearray)]
176
+
177
+ # 各種設定
178
+ # アセット
179
+ asset=Asset()
180
+
181
+ # バッファ
182
+ buffers = [Buffer(byteLength=len(all_bytearray))]
183
+
184
+ # バッファビュー
185
+ bufferViews = [
186
+ BufferView(buffer=0, byteOffset=offset_list[0], byteLength=bytelen_list[0], target=BufferTarget.ARRAY_BUFFER.value),
187
+ BufferView(buffer=0, byteOffset=offset_list[1], byteLength=bytelen_list[1], target=BufferTarget.ARRAY_BUFFER.value),
188
+ BufferView(buffer=0, byteOffset=offset_list[2], byteLength=bytelen_list[2], target=BufferTarget.ARRAY_BUFFER.value),
189
+ BufferView(buffer=0, byteOffset=offset_list[3], byteLength=bytelen_list[3], target=BufferTarget.ELEMENT_ARRAY_BUFFER.value),
190
+ BufferView(buffer=0, byteOffset=offset_list[4], byteLength=bytelen_list[4], target=None),
191
+ ]
192
+
193
+ # アクセサー
194
+ accessors = [
195
+ Accessor(bufferView=0, componentType=ComponentType.FLOAT.value, count=len(vertices), type=AccessorType.VEC3.value, max=maxs, min=mins),
196
+ Accessor(bufferView=1, componentType=ComponentType.FLOAT.value, count=len(normals), type=AccessorType.VEC3.value, max=None, min=None),
197
+ Accessor(bufferView=2, componentType=ComponentType.FLOAT.value, count=len(texcoord_0s), type=AccessorType.VEC2.value, max=None, min=None),
198
+ Accessor(bufferView=3, componentType=ComponentType.UNSIGNED_SHORT.value, count=len(vertex_indices), type=AccessorType.SCALAR.value, max=None, min=None)
199
+ ]
200
+
201
+ # イメージ
202
+ images=[
203
+ Image(mimeType='image/jpeg', bufferView=4),
204
+ ]
205
+
206
+ # サンプラー
207
+ samplers = [Sampler(magFilter=9728, minFilter=9984)] # magFilter:最近傍フィルタリング、minFilter:ミップマップ+最近傍フィルタリング
208
+
209
+ # テクスチャ
210
+ textures = [
211
+ Texture(name='Main',sampler=0,source=0),
212
+ ]
213
+
214
+ # マテリアル
215
+ materials = [
216
+ Material(
217
+ pbrMetallicRoughness=PBRMetallicRoughness(
218
+ baseColorTexture=TextureInfo(index=0),
219
+ metallicFactor=0,
220
+ roughnessFactor=1
221
+ ),
222
+ name='Material0',
223
+ alphaMode='OPAQUE',
224
+ doubleSided=True
225
+ ),
226
+ ]
227
+
228
+ # メッシュ
229
+ meshes = [
230
+ Mesh(name='Main', primitives=[Primitive(attributes=Attributes(POSITION=0, NORMAL=1,TEXCOORD_0=2),
231
+ indices=3, material=0, mode=4)]),
232
+ ]
233
+
234
+ # ノード
235
+ nodes = [
236
+ Node(mesh=0,rotation=None, scale=scale),
237
+ ]
238
+
239
+ # シーン
240
+ scene = 0
241
+ scenes = [Scene(name='Scene', nodes=[0])]
242
+
243
+ model = GLTFModel(
244
+ asset=asset,
245
+ buffers=buffers,
246
+ bufferViews=bufferViews,
247
+ accessors=accessors,
248
+ images=images,
249
+ samplers=samplers,
250
+ textures=textures,
251
+ materials=materials,
252
+ meshes=meshes,
253
+ nodes=nodes,
254
+ scene=scene,
255
+ scenes=scenes
256
+ )
257
+
258
+ gltf = GLTF(model=model, resources=resources)
259
+
260
+ tmp_filename = uuid.uuid4().hex
261
+ model_path = f'../tmp/{tmp_filename}.glb'
262
+
263
+ gltf.export(model_path)
264
+
265
+ return model_path
src/front_card_image.py ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from PIL import Image, ImageDraw, ImageFont
2
+ from io import BytesIO
3
+
4
+ from src.constants import front_card_img_dict
5
+
6
+ # カード画像各領域のピクセル位置情報
7
+ # 写真
8
+ PICTURE_LT_XY = (65, 188)
9
+ PICTURE_RB_XY = (802, 925)
10
+ PICTURE_SIZE = (PICTURE_RB_XY[0] - PICTURE_LT_XY[0], PICTURE_RB_XY[1] - PICTURE_LT_XY[1])
11
+
12
+ # タイトル
13
+ # ある程度の余白を作る。
14
+ TITLE_LT_XY = (65, 45)
15
+ TITLE_RB_XY = (647, 132) # マーク挿入部分と重ならないような位置
16
+ TITLE_SIZE = (TITLE_RB_XY[0] - TITLE_LT_XY[0], TITLE_RB_XY[1] - TITLE_LT_XY[1])
17
+
18
+ # 説明文本体
19
+ DESCRIPTION_LT_XY = (46, 972)
20
+ DESCRIPTION_RB_XY = (810, 1174)
21
+ DESCRIPTION_SIZE = (DESCRIPTION_RB_XY[0] - DESCRIPTION_LT_XY[0], DESCRIPTION_RB_XY[1] - DESCRIPTION_LT_XY[1])
22
+
23
+ # フォント関連
24
+ # 明朝体
25
+ font_selif_path = 'data/fonts/SourceHanSerif-Bold.otf'
26
+ # ゴシック体
27
+ font_sanselif_path = 'data/fonts/SourceHanSans-Bold.otf'
28
+
29
+ def crop_center(pil_img, crop_width, crop_height):
30
+ img_width, img_height = pil_img.size
31
+ return pil_img.crop(((img_width - crop_width) // 2,
32
+ (img_height - crop_height) // 2,
33
+ (img_width + crop_width) // 2,
34
+ (img_height + crop_height) // 2))
35
+
36
+ def crop_max_square(pil_img):
37
+ return crop_center(pil_img, min(pil_img.size), min(pil_img.size))
38
+
39
+ def create_card_image(model_no, img_bytearray, option_dict):
40
+
41
+ # 画像の読み込みとトリミング
42
+ picture_img = Image.open(img_bytearray)
43
+ picture_img = crop_max_square(picture_img)
44
+ picture_img = picture_img.resize(PICTURE_SIZE)
45
+
46
+ # カードの読み込み
47
+ card_img = Image.open(front_card_img_dict[model_no])
48
+
49
+ # 写真の埋め込み
50
+ card_img.paste(picture_img, PICTURE_LT_XY)
51
+
52
+ # 各種書き込み準備
53
+ card_imgdraw = ImageDraw.Draw(card_img)
54
+
55
+ # タイトル埋め込み
56
+ # (複数行対応は必要ならば対応)
57
+ title_font_size = 100
58
+ while True:
59
+ title_font = ImageFont.truetype(font_selif_path, title_font_size)
60
+ title_bbox = card_imgdraw.textbbox(TITLE_LT_XY , option_dict['タイトル'], title_font)
61
+
62
+ if (title_bbox[2] <= TITLE_RB_XY[0] and title_bbox[3] <= TITLE_RB_XY[1]) or title_font_size <= 30:
63
+ break
64
+ title_font_size -= 1
65
+
66
+ card_imgdraw.text((TITLE_LT_XY[0], int((TITLE_LT_XY[1] + TITLE_RB_XY[1]) / 2)), option_dict['タイトル'], fill='black', font=title_font, anchor='lm')
67
+
68
+ # 説明文埋め込み
69
+ description_font = ImageFont.truetype(font_sanselif_path, 40)
70
+
71
+ description_list = []
72
+ description_length = len(option_dict['説明文'])
73
+ temp_start = 0
74
+ for i in range(description_length):
75
+ temp_end = i
76
+ description_line_bbox = card_imgdraw.textbbox((0, 0), option_dict['説明文'][temp_start:temp_end+1], description_font)
77
+ if description_line_bbox[2] > DESCRIPTION_SIZE[0]:
78
+ description_list.append(option_dict['説明文'][temp_start:temp_end])
79
+ temp_start = i
80
+
81
+ description_list.append(option_dict['説明文'][temp_start:])
82
+ description_display = '\n'.join(description_list)
83
+
84
+ card_imgdraw.text(DESCRIPTION_LT_XY, description_display, fill='black', font=description_font)
85
+
86
+ # バイナリデータの出力
87
+ output_img_bytearray = BytesIO()
88
+ card_img.convert('RGB').save(output_img_bytearray, "JPEG", quality=95)
89
+ output_img_bytearray.seek(0) # 画像の先頭にシークしないと空データになってしまう。
90
+
91
+ return output_img_bytearray
src/front_card_image_historic_site.py ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from PIL import Image, ImageDraw, ImageFont
2
+ from io import BytesIO
3
+
4
+ # カード画像関連
5
+ card_frame_path_dict = {
6
+ '橙': 'data/cards/史跡カードフレーム(橙).png',
7
+ '白': 'data/cards/史跡カードフレーム(白).png',
8
+ '紫': 'data/cards/史跡カードフレーム(紫).png',
9
+ '緑': 'data/cards/史跡カードフレーム(緑).png',
10
+ '茶': 'data/cards/史跡カードフレーム(茶).png',
11
+ '赤': 'data/cards/史跡カードフレーム(赤).png',
12
+ '青': 'data/cards/史跡カードフレーム(青).png',
13
+ '黄': 'data/cards/史跡カードフレーム(黄).png',
14
+ '黒': 'data/cards/史跡カードフレーム(黒).png'
15
+ }
16
+ card_mark_path_dict = {
17
+ '区指定': 'data/cards/史跡カード指定マーク(区指定).png',
18
+ '国指定': 'data/cards/史跡カード指定マーク(国指定).png',
19
+ '市指定': 'data/cards/史跡カード指定マーク(市指定).png',
20
+ '府指定': 'data/cards/史跡カード指定マーク(府指定).png',
21
+ '村指定': 'data/cards/史跡カード指定マーク(村指定).png',
22
+ '町指定': 'data/cards/史跡カード指定マーク(町指定).png',
23
+ '県指定': 'data/cards/史跡カード指定マーク(県指定).png',
24
+ '道指定': 'data/cards/史跡カード指定マーク(道指定).png',
25
+ '都指定': 'data/cards/史跡カード指定マーク(都指定).png'
26
+ }
27
+
28
+ # カード画像各領域のピクセル位置情報
29
+ # 写真
30
+ PICTURE_LT_XY = (65, 188)
31
+ PICTURE_RB_XY = (802, 925)
32
+ PICTURE_SIZE = (PICTURE_RB_XY[0] - PICTURE_LT_XY[0], PICTURE_RB_XY[1] - PICTURE_LT_XY[1])
33
+
34
+ # タイトル
35
+ # ある程度の余白を作る。
36
+ TITLE_LT_XY = (65, 45)
37
+ TITLE_RB_XY = (647, 132) # マーク挿入部分と重ならないような位置
38
+ TITLE_SIZE = (TITLE_RB_XY[0] - TITLE_LT_XY[0], TITLE_RB_XY[1] - TITLE_LT_XY[1])
39
+
40
+ # 説明欄区切り線
41
+ DESCRIPTION_LINE_L_XY = (52, 1024)
42
+ DESCRIPTION_LINE_R_XY = (816, 1024)
43
+
44
+ # 史跡種類
45
+ HS_TYPE_LT_XY = (56, 972)
46
+
47
+ # 訪問難度
48
+ DIFFICULTY_LT_XY = (444, 972)
49
+
50
+ # 説明文本体
51
+ DESCRIPTION_LT_XY = (46, 1024)
52
+ DESCRIPTION_RB_XY = (810, 1174)
53
+ DESCRIPTION_SIZE = (DESCRIPTION_RB_XY[0] - DESCRIPTION_LT_XY[0], DESCRIPTION_RB_XY[1] - DESCRIPTION_LT_XY[1])
54
+
55
+
56
+ # フォント関連
57
+ # 明朝体
58
+ font_selif_path = 'data/fonts/SourceHanSerif-Bold.otf'
59
+ # ゴシック体
60
+ font_sanselif_path = 'data/fonts/SourceHanSans-Bold.otf'
61
+
62
+ def crop_center(pil_img, crop_width, crop_height):
63
+ img_width, img_height = pil_img.size
64
+ return pil_img.crop(((img_width - crop_width) // 2,
65
+ (img_height - crop_height) // 2,
66
+ (img_width + crop_width) // 2,
67
+ (img_height + crop_height) // 2))
68
+
69
+ def crop_max_square(pil_img):
70
+ return crop_center(pil_img, min(pil_img.size), min(pil_img.size))
71
+
72
+ def create_historic_site_card_image(img_bytearray, option_dict):
73
+
74
+ # 画像の読み込みとトリミング
75
+ picture_img = Image.open(img_bytearray)
76
+ picture_img = crop_max_square(picture_img)
77
+ picture_img = picture_img.resize(PICTURE_SIZE)
78
+
79
+
80
+ # カードの読み込み
81
+ card_img = Image.open(card_frame_path_dict[option_dict['色']])
82
+
83
+ # マークの追加
84
+ mark_image = Image.open(card_mark_path_dict[option_dict['マーク']])
85
+ card_img.paste(mark_image, mask=mark_image)
86
+
87
+ # 写真の埋め込み
88
+ card_img.paste(picture_img, PICTURE_LT_XY)
89
+
90
+ # 各種書き込み準備
91
+ card_imgdraw = ImageDraw.Draw(card_img)
92
+
93
+ # カード枠線の追加
94
+ card_imgdraw.line(( (0, 0), (card_img.size[0], 0) ), fill='black', width=15)
95
+ card_imgdraw.line(( (0, 0), (0, card_img.size[1]) ), fill='black', width=15)
96
+ card_imgdraw.line(( (card_img.size[0], 0), card_img.size), fill='black', width=15)
97
+ card_imgdraw.line(( (0, card_img.size[1]), card_img.size), fill='black', width=15)
98
+
99
+ # 説明欄区切り線の追加
100
+ card_imgdraw.line((DESCRIPTION_LINE_L_XY, DESCRIPTION_LINE_R_XY), fill='black', width=3)
101
+
102
+ # タイトル埋め込み
103
+ # (複数行対応は必要ならば対応)
104
+ title_font_size = 100
105
+ while True:
106
+ title_font = ImageFont.truetype(font_selif_path, title_font_size)
107
+ title_bbox = card_imgdraw.textbbox(TITLE_LT_XY , option_dict['タイトル'], title_font)
108
+
109
+ if (title_bbox[2] <= TITLE_RB_XY[0] and title_bbox[3] <= TITLE_RB_XY[1]) or title_font_size <= 30:
110
+ break
111
+ title_font_size -= 1
112
+
113
+ card_imgdraw.text((TITLE_LT_XY[0], int((TITLE_LT_XY[1] + TITLE_RB_XY[1]) / 2)), option_dict['タイトル'], fill='black', font=title_font, anchor='lm')
114
+
115
+ # 史跡種類埋め込み
116
+ hs_type_display = f'種類:{option_dict["史跡種類"]}'
117
+ hs_typefont = ImageFont.truetype(font_sanselif_path, 40)
118
+ card_imgdraw.text(HS_TYPE_LT_XY, hs_type_display, fill='black', font=hs_typefont, anchor='lt')
119
+
120
+ # 訪問難度埋め込み
121
+ difficulty = int(option_dict['訪問難度'])
122
+ difficulty = 1 if difficulty < 1 else 5 if difficulty > 5 else difficulty
123
+ difficulty_display = '訪問難度:' + '☆' * difficulty + '★' * (5 - difficulty)
124
+ difficulty_font = ImageFont.truetype(font_sanselif_path, 40)
125
+ card_imgdraw.text(DIFFICULTY_LT_XY, difficulty_display, fill='black', font=difficulty_font, anchor='lt')
126
+
127
+ # 説明文埋め込み
128
+ description_font = ImageFont.truetype(font_sanselif_path, 40)
129
+
130
+ description_list = []
131
+ description_length = len(option_dict['説明文'])
132
+ temp_start = 0
133
+ for i in range(description_length):
134
+ temp_end = i
135
+ description_line_bbox = card_imgdraw.textbbox((0, 0), option_dict['説明文'][temp_start:temp_end+1], description_font)
136
+ if description_line_bbox[2] > DESCRIPTION_SIZE[0]:
137
+ description_list.append(option_dict['説明文'][temp_start:temp_end])
138
+ temp_start = i
139
+
140
+ description_list.append(option_dict['説明文'][temp_start:])
141
+ description_display = '\n'.join(description_list)
142
+
143
+ card_imgdraw.text(DESCRIPTION_LT_XY, description_display, fill='black', font=description_font)
144
+
145
+ # バイナリデータの出力
146
+ output_img_bytearray = BytesIO()
147
+ card_img.convert('RGB').save(output_img_bytearray, "JPEG", quality=95)
148
+ output_img_bytearray.seek(0) # 画像の先頭にシークしないと空データになってしまう。
149
+
150
+ return output_img_bytearray
src/picture_box_model.py ADDED
@@ -0,0 +1,265 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import numpy as np
3
+
4
+ from PIL import Image as PIL_Image # gltflibのImageと被るので別名にする。
5
+ import struct
6
+ import uuid
7
+
8
+ from gltflib import (
9
+ GLTF, GLTFModel, Asset, Scene, Node, Mesh, Primitive, Attributes, Buffer, BufferView, Image, Texture, TextureInfo, Material, Sampler, Accessor, AccessorType,
10
+ BufferTarget, ComponentType, GLBResource, PBRMetallicRoughness)
11
+
12
+ # 共通バイナリ情報
13
+ # 頂点データ(POSITION)
14
+ vertices = [
15
+ [ 1.0, 1.0, -1.0,],
16
+ [ 1.0, 1.0, -1.0,],
17
+ [ 1.0, 1.0, -1.0,],
18
+ [ 1.0, -1.0, -1.0,],
19
+ [ 1.0, -1.0, -1.0,],
20
+ [ 1.0, -1.0, -1.0,],
21
+ [ 1.0, 1.0, 1.0,],
22
+ [ 1.0, 1.0, 1.0,],
23
+ [ 1.0, 1.0, 1.0,],
24
+ [ 1.0, -1.0, 1.0,],
25
+ [ 1.0, -1.0, 1.0,],
26
+ [ 1.0, -1.0, 1.0,],
27
+ [-1.0, 1.0, -1.0,],
28
+ [-1.0, 1.0, -1.0,],
29
+ [-1.0, 1.0, -1.0,],
30
+ [-1.0, -1.0, -1.0,],
31
+ [-1.0, -1.0, -1.0,],
32
+ [-1.0, -1.0, -1.0,],
33
+ [-1.0, 1.0, 1.0,],
34
+ [-1.0, 1.0, 1.0,],
35
+ [-1.0, 1.0, 1.0,],
36
+ [-1.0, -1.0, 1.0,],
37
+ [-1.0, -1.0, 1.0,],
38
+ [-1.0, -1.0, 1.0,],
39
+ ]
40
+ vertex_bytearray = bytearray()
41
+ for vertex in vertices:
42
+ for value in vertex:
43
+ vertex_bytearray.extend(struct.pack('f', value))
44
+ vertex_bytelen = len(vertex_bytearray)
45
+ mins = [min([vertex[i] for vertex in vertices]) for i in range(3)]
46
+ maxs = [max([vertex[i] for vertex in vertices]) for i in range(3)]
47
+
48
+ # 法線データ(NORMAL)
49
+ normals = [
50
+ [ 0.0, 0.0, -1.0,],
51
+ [ 0.0, 1.0, -0.0,],
52
+ [ 1.0, 0.0, -0.0,],
53
+ [ 0.0, -1.0, -0.0,],
54
+ [ 0.0, 0.0, -1.0,],
55
+ [ 1.0, 0.0, -0.0,],
56
+ [ 0.0, 0.0, 1.0,],
57
+ [ 0.0, 1.0, -0.0,],
58
+ [ 1.0, 0.0, -0.0,],
59
+ [ 0.0, -1.0, -0.0,],
60
+ [ 0.0, 0.0, 1.0,],
61
+ [ 1.0, 0.0, -0.0,],
62
+ [-1.0, 0.0, -0.0,],
63
+ [ 0.0, 0.0, -1.0,],
64
+ [ 0.0, 1.0, -0.0,],
65
+ [-1.0, 0.0, -0.0,],
66
+ [ 0.0, -1.0, -0.0,],
67
+ [ 0.0, 0.0, -1.0,],
68
+ [-1.0, 0.0, -0.0,],
69
+ [ 0.0, 0.0, 1.0,],
70
+ [ 0.0, 1.0, -0.0,],
71
+ [-1.0, 0.0, -0.0,],
72
+ [ 0.0, -1.0, -0.0,],
73
+ [ 0.0, 0.0, 1.0,],
74
+ ]
75
+ normal_bytearray = bytearray()
76
+ for normal in normals:
77
+ for value in normal:
78
+ normal_bytearray.extend(struct.pack('f', value))
79
+ normal_bytelen = len(normal_bytearray)
80
+
81
+ # テクスチャ座標(TEXCOORD_0)
82
+ texcoord_0s = [
83
+ [0.9, 0.1],[0.9, 0.0],[1.0, 0.1],
84
+ [0.9, 1.0],[0.9, 0.9],[1.0, 0.9],
85
+ [0.9, 0.1],[0.9, 0.1],[0.9, 0.1],
86
+ [0.9, 0.9],[0.9, 0.9],[0.9, 0.9],
87
+ [0.0, 0.1],[0.1, 0.1],[0.1, 0.0],
88
+ [0.0, 0.9],[0.1, 1.0],[0.1, 0.9],
89
+ [0.1, 0.1],[0.1, 0.1],[0.1, 0.1],
90
+ [0.1, 0.9],[0.1, 0.9],[0.1, 0.9],
91
+ ]
92
+ texcoord_0_bytearray = bytearray()
93
+ for texcoord_0 in texcoord_0s:
94
+ for value in texcoord_0:
95
+ texcoord_0_bytearray.extend(struct.pack('f', value))
96
+ texcoord_0_bytelen = len(texcoord_0_bytearray)
97
+
98
+ # 頂点インデックス
99
+ vertex_indices = [
100
+ 1, 14, 20,
101
+ 1, 20, 7,
102
+ 10, 6, 19,
103
+ 10, 19, 23,
104
+ 21, 18, 12,
105
+ 21, 12, 15,
106
+ 16, 3, 9,
107
+ 16, 9, 22,
108
+ 5, 2, 8,
109
+ 5, 8, 11,
110
+ 17, 13, 0,
111
+ 17, 0, 4
112
+ ]
113
+
114
+ vertex_index_bytearray = bytearray()
115
+ for value in vertex_indices:
116
+ vertex_index_bytearray.extend(struct.pack('H', value))
117
+ vertex_index_bytelen = len(vertex_index_bytearray)
118
+
119
+ def create_picture_box_model(img_bytearray):
120
+
121
+ # 画像の取得
122
+ img = PIL_Image.open(img_bytearray).convert('RGB')
123
+
124
+ # 辺の長さが8で割れる数値になるように調整
125
+ img = img.resize((8 * (img.size[0] // 8), 8 * (img.size[1] // 8)))
126
+ temp_img = PIL_Image.new('RGB', (int(img.size[0] * 1.25), int(img.size[1] * 1.25)), 'white')
127
+ temp_img.paste(img, (int(temp_img.size[0] / 10), int(temp_img.size[1] / 10)))
128
+ img = temp_img.copy()
129
+
130
+ for offset_x in range(0, int(temp_img.size[0] / 10)):
131
+ 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))
132
+ for offset_x in range(int(temp_img.size[0] / 10 * 9), img.size[0]):
133
+ 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))
134
+
135
+ for offset_y in range(0, int(temp_img.size[1] / 10)):
136
+ 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))
137
+ for offset_y in range(int(temp_img.size[1] / 10 * 9), img.size[1]):
138
+ 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))
139
+
140
+ img_bytearray = io.BytesIO()
141
+ img.save(img_bytearray, format="JPEG", quality=95)
142
+ img_bytearray = img_bytearray.getvalue()
143
+ img_bytelen = len(img_bytearray)
144
+
145
+ # 3Dモデルのスケールの計算
146
+ scale_factor = np.power(img.size[0] * img.size[1], 0.5)
147
+ scale = (img.size[0] / scale_factor, img.size[1] / scale_factor, 0.1)
148
+
149
+ # バイナリデータ部分の結合
150
+ bytearray_list = [
151
+ vertex_bytearray,
152
+ normal_bytearray,
153
+ texcoord_0_bytearray,
154
+ vertex_index_bytearray,
155
+ img_bytearray,
156
+ ]
157
+ bytelen_list = [
158
+ vertex_bytelen,
159
+ normal_bytelen,
160
+ texcoord_0_bytelen,
161
+ vertex_index_bytelen,
162
+ img_bytelen,
163
+ ]
164
+ bytelen_cumsum_list = list(np.cumsum(bytelen_list))
165
+ bytelen_cumsum_list = list(map(lambda x: int(x), bytelen_cumsum_list))
166
+
167
+ all_bytearray = bytearray()
168
+ for temp_bytearray in bytearray_list:
169
+ all_bytearray.extend(temp_bytearray)
170
+ offset_list = [0] + bytelen_cumsum_list # 最初のオフセットは0
171
+ offset_list.pop() # 末尾を削除
172
+
173
+ # リソースの作成
174
+ resources = [GLBResource(data=all_bytearray)]
175
+
176
+ # 各種設定
177
+ # アセット
178
+ asset=Asset()
179
+
180
+ # バッファ
181
+ buffers = [Buffer(byteLength=len(all_bytearray))]
182
+
183
+ # バッファビュー
184
+ bufferViews = [
185
+ BufferView(buffer=0, byteOffset=offset_list[0], byteLength=bytelen_list[0], target=BufferTarget.ARRAY_BUFFER.value),
186
+ BufferView(buffer=0, byteOffset=offset_list[1], byteLength=bytelen_list[1], target=BufferTarget.ARRAY_BUFFER.value),
187
+ BufferView(buffer=0, byteOffset=offset_list[2], byteLength=bytelen_list[2], target=BufferTarget.ARRAY_BUFFER.value),
188
+ BufferView(buffer=0, byteOffset=offset_list[3], byteLength=bytelen_list[3], target=BufferTarget.ELEMENT_ARRAY_BUFFER.value),
189
+ BufferView(buffer=0, byteOffset=offset_list[4], byteLength=bytelen_list[4], target=None),
190
+ ]
191
+
192
+ # アクセサー
193
+ accessors = [
194
+ Accessor(bufferView=0, componentType=ComponentType.FLOAT.value, count=len(vertices), type=AccessorType.VEC3.value, max=maxs, min=mins),
195
+ Accessor(bufferView=1, componentType=ComponentType.FLOAT.value, count=len(normals), type=AccessorType.VEC3.value, max=None, min=None),
196
+ Accessor(bufferView=2, componentType=ComponentType.FLOAT.value, count=len(texcoord_0s), type=AccessorType.VEC2.value, max=None, min=None),
197
+ Accessor(bufferView=3, componentType=ComponentType.UNSIGNED_SHORT.value, count=len(vertex_indices), type=AccessorType.SCALAR.value, max=None, min=None)
198
+ ]
199
+
200
+ # イメージ
201
+ images=[
202
+ Image(mimeType='image/jpeg', bufferView=4),
203
+ ]
204
+
205
+ # サンプラー
206
+ samplers = [Sampler(magFilter=9728, minFilter=9984)] # magFilter:最近傍フィルタリング、minFilter:ミップマップ+最近傍フィルタリング
207
+
208
+ # テクスチャ
209
+ textures = [
210
+ Texture(name='Image',sampler=0,source=0),
211
+ ]
212
+
213
+ # マテリアル
214
+ materials = [
215
+ Material(
216
+ pbrMetallicRoughness=PBRMetallicRoughness(
217
+ baseColorTexture=TextureInfo(index=0),
218
+ metallicFactor=0,
219
+ roughnessFactor=0.5
220
+ ),
221
+ name='Material0',
222
+ alphaMode='BLEND',
223
+ doubleSided=False
224
+ ),
225
+ ]
226
+
227
+ # メッシュ
228
+ meshes = [
229
+ Mesh(name='Image', primitives=[Primitive(attributes=Attributes(POSITION=0, NORMAL=1,TEXCOORD_0=2),indices=3, material=0)]),
230
+ ]
231
+
232
+ # ノード
233
+ nodes = [
234
+ Node(mesh=0,rotation=None, scale=scale),
235
+ ]
236
+
237
+ # シーン
238
+ scene = 0
239
+ scenes = [Scene(name='Scene', nodes=[0])]
240
+
241
+ model = GLTFModel(
242
+ asset=asset,
243
+ buffers=buffers,
244
+ bufferViews=bufferViews,
245
+ accessors=accessors,
246
+ images=images,
247
+ samplers=samplers,
248
+ textures=textures,
249
+ materials=materials,
250
+ meshes=meshes,
251
+ nodes=nodes,
252
+ scene=scene,
253
+ scenes=scenes
254
+ )
255
+
256
+ gltf = GLTF(model=model, resources=resources)
257
+
258
+ tmp_filename = uuid.uuid4().hex
259
+ model_path = f'../tmp/{tmp_filename}.glb'
260
+
261
+ gltf.export(model_path)
262
+
263
+ return model_path
264
+
265
+