├── .gitignore ├── LICENSE ├── README.md ├── Setting1.png ├── Setting10.png ├── Setting11.png ├── Setting12.png ├── Setting13.png ├── Setting14.png ├── Setting2.png ├── Setting3.png ├── Setting4.png ├── Setting5.png ├── Setting6.png ├── Setting7.png ├── Setting8.png ├── Setting9.png ├── Title.png ├── Trouble1_1.png ├── Trouble1_2.png ├── Trouble1_3.png ├── Trouble1_4.png ├── back.png ├── front.png ├── image1.png ├── image2.png ├── image3.png ├── image4.png ├── image5.png ├── image6.png ├── image7.png ├── image8.png ├── image9.png ├── love2d3d.py ├── readme.html └── trouble1.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Old version 2 | The files in github is old vaersion. 3 | You can get the latest from [BOOTH](https://fujisunflower.booth.pm/items/1333674) 4 | Githubにあるファイルは古いバージョンです。 5 | 最新版は[BOOTH](https://fujisunflower.booth.pm/items/1333674) から入手出来ます。 6 | # Love2D3D 7 | Blender addon to create 3D object from 2D image. 8 | You can use usual paint tool for the creation. 9 | The addon can create armature (bone) for 3D object. 10 | [YouTube](https://youtu.be/hdJOKAm-ZE0), [BOOTH](https://fujisunflower.booth.pm/items/1333674) or [wiki](https://github.com/FujiSunflower/love2d3d/wiki) 11 | 画像から3Dモデルを作成するBlenderのアドオンです。 12 | 使い慣れたペイントツールで描いた絵を立体化出来ます。 13 | 3Dモデルを動かすために必要なボーンの生成も出来るようになりました。 14 | [YouTube](https://youtu.be/hdJOKAm-ZE0)・[BOOTH](https://fujisunflower.booth.pm/items/1333674) 15 | ・[詳細情報](https://github.com/FujiSunflower/love2d3d/wiki) 16 |  17 | # How to use 18 |  19 |  20 |  21 |  22 |  23 |  24 |  25 |  26 |  27 |  28 |  29 |  30 |  31 |  32 | -------------------------------------------------------------------------------- /Setting1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FujiSunflower/love2d3d/ce2184fb7368fc28ab5a0d815322dece327f573d/Setting1.png -------------------------------------------------------------------------------- /Setting10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FujiSunflower/love2d3d/ce2184fb7368fc28ab5a0d815322dece327f573d/Setting10.png -------------------------------------------------------------------------------- /Setting11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FujiSunflower/love2d3d/ce2184fb7368fc28ab5a0d815322dece327f573d/Setting11.png -------------------------------------------------------------------------------- /Setting12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FujiSunflower/love2d3d/ce2184fb7368fc28ab5a0d815322dece327f573d/Setting12.png -------------------------------------------------------------------------------- /Setting13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FujiSunflower/love2d3d/ce2184fb7368fc28ab5a0d815322dece327f573d/Setting13.png -------------------------------------------------------------------------------- /Setting14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FujiSunflower/love2d3d/ce2184fb7368fc28ab5a0d815322dece327f573d/Setting14.png -------------------------------------------------------------------------------- /Setting2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FujiSunflower/love2d3d/ce2184fb7368fc28ab5a0d815322dece327f573d/Setting2.png -------------------------------------------------------------------------------- /Setting3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FujiSunflower/love2d3d/ce2184fb7368fc28ab5a0d815322dece327f573d/Setting3.png -------------------------------------------------------------------------------- /Setting4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FujiSunflower/love2d3d/ce2184fb7368fc28ab5a0d815322dece327f573d/Setting4.png -------------------------------------------------------------------------------- /Setting5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FujiSunflower/love2d3d/ce2184fb7368fc28ab5a0d815322dece327f573d/Setting5.png -------------------------------------------------------------------------------- /Setting6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FujiSunflower/love2d3d/ce2184fb7368fc28ab5a0d815322dece327f573d/Setting6.png -------------------------------------------------------------------------------- /Setting7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FujiSunflower/love2d3d/ce2184fb7368fc28ab5a0d815322dece327f573d/Setting7.png -------------------------------------------------------------------------------- /Setting8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FujiSunflower/love2d3d/ce2184fb7368fc28ab5a0d815322dece327f573d/Setting8.png -------------------------------------------------------------------------------- /Setting9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FujiSunflower/love2d3d/ce2184fb7368fc28ab5a0d815322dece327f573d/Setting9.png -------------------------------------------------------------------------------- /Title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FujiSunflower/love2d3d/ce2184fb7368fc28ab5a0d815322dece327f573d/Title.png -------------------------------------------------------------------------------- /Trouble1_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FujiSunflower/love2d3d/ce2184fb7368fc28ab5a0d815322dece327f573d/Trouble1_1.png -------------------------------------------------------------------------------- /Trouble1_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FujiSunflower/love2d3d/ce2184fb7368fc28ab5a0d815322dece327f573d/Trouble1_2.png -------------------------------------------------------------------------------- /Trouble1_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FujiSunflower/love2d3d/ce2184fb7368fc28ab5a0d815322dece327f573d/Trouble1_3.png -------------------------------------------------------------------------------- /Trouble1_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FujiSunflower/love2d3d/ce2184fb7368fc28ab5a0d815322dece327f573d/Trouble1_4.png -------------------------------------------------------------------------------- /back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FujiSunflower/love2d3d/ce2184fb7368fc28ab5a0d815322dece327f573d/back.png -------------------------------------------------------------------------------- /front.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FujiSunflower/love2d3d/ce2184fb7368fc28ab5a0d815322dece327f573d/front.png -------------------------------------------------------------------------------- /image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FujiSunflower/love2d3d/ce2184fb7368fc28ab5a0d815322dece327f573d/image1.png -------------------------------------------------------------------------------- /image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FujiSunflower/love2d3d/ce2184fb7368fc28ab5a0d815322dece327f573d/image2.png -------------------------------------------------------------------------------- /image3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FujiSunflower/love2d3d/ce2184fb7368fc28ab5a0d815322dece327f573d/image3.png -------------------------------------------------------------------------------- /image4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FujiSunflower/love2d3d/ce2184fb7368fc28ab5a0d815322dece327f573d/image4.png -------------------------------------------------------------------------------- /image5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FujiSunflower/love2d3d/ce2184fb7368fc28ab5a0d815322dece327f573d/image5.png -------------------------------------------------------------------------------- /image6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FujiSunflower/love2d3d/ce2184fb7368fc28ab5a0d815322dece327f573d/image6.png -------------------------------------------------------------------------------- /image7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FujiSunflower/love2d3d/ce2184fb7368fc28ab5a0d815322dece327f573d/image7.png -------------------------------------------------------------------------------- /image8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FujiSunflower/love2d3d/ce2184fb7368fc28ab5a0d815322dece327f573d/image8.png -------------------------------------------------------------------------------- /image9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FujiSunflower/love2d3d/ce2184fb7368fc28ab5a0d815322dece327f573d/image9.png -------------------------------------------------------------------------------- /love2d3d.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2018 Fuji Sunflower 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | import bpy 16 | import numpy as np 17 | from bpy.props import FloatProperty, BoolProperty, StringProperty, IntProperty 18 | from bpy_extras.object_utils import AddObjectHelper, object_data_add 19 | import bgl 20 | from mathutils import Vector, Matrix 21 | from mathutils.kdtree import KDTree 22 | import gpu 23 | from gpu_extras.batch import batch_for_shader 24 | import sys 25 | 26 | bl_info = { 27 | "name": "Love2D3D", 28 | "author": "Fuji Sunflower", 29 | "version": (3, 0), 30 | "blender": (2, 80, 0), 31 | "location": "3D View > Menu > Add > Mesh, Armature. Sidebar > View", 32 | "description": "Add 3D object from image or its armature.", 33 | "warning": "", 34 | "support": "COMMUNITY", 35 | "wiki_url": "https://github.com/FujiSunflower/love2d3d/wiki", 36 | "tracker_url": "https://github.com/FujiSunflower/love2d3d/issues", 37 | "category": "Add Mesh" 38 | } 39 | 40 | RGBA = 4 # Color size per pixels 41 | RGB = 3 # Color size per pixels 42 | R = 0 # Index of color 43 | G = 1 # Index of color 44 | B = 2 # Index of color 45 | A = 3 # Index of color 46 | X = 0 # Index 47 | Y = 1 # Index 48 | LEFT = 2 # Index 49 | RIGHT = 3 # Index 50 | BOTTOM = 4 # Index 51 | TOP = 5 # Index 52 | QUAD = 4 # Vertex Numer of Quad 53 | FRONT = 0 54 | BACK = 1 55 | NAME = "Love2D3D" # Name of 3D object 56 | BOUND_LEFT = 0 # Index of bounds 57 | BOUND_RIGHT = 1 # Index of bounds 58 | BOUND_BACK = 2 # Index of bounds 59 | BOUND_FRONT = 3 # Index of bounds 60 | BOUND_TOP = 4 # Index of bounds 61 | BOUND_BOTTOM = 5 # Index of bounds 62 | BOUND_CENTER = 6 # Index of bounds 63 | BRANCH_BOOST = 3.0 64 | BONE_TYPE_ANY = -1 # Index of bone types 65 | BONE_TYPE_BODY = 0 # Index of bone types 66 | BONE_TYPE_HEAD = 1 # Index of bone types 67 | BONE_TYPE_ARM_LEFT = 2 # Index of bone types 68 | BONE_TYPE_ARM_RIGHT = 3 # Index of bone types 69 | BONE_TYPE_LEG_LEFT = 4 # Index of bone types 70 | BONE_TYPE_LEG_RIGHT = 5 # Index of bone types 71 | BONE_TYPE_FINGER_LEFT = 7 # Index of bone types 72 | BONE_TYPE_FINGER_RIGHT = 8 # Index of bone types 73 | 74 | 75 | class LOVE2D3D_OT_preview(bpy.types.Operator): 76 | 77 | bl_idname = "object.love2d3d_preview_mesh" 78 | bl_label = "Preview love2D3D mesh" 79 | bl_description = "Preview mesh of love2D3D" 80 | bl_options = {'INTERNAL'} 81 | _handle = None 82 | vertex_shader = ''' 83 | uniform mat4 modelMatrix; 84 | uniform mat4 viewProjectionMatrix; 85 | 86 | in vec2 position; 87 | in vec2 uv; 88 | 89 | out vec2 uvInterp; 90 | 91 | void main() 92 | { 93 | uvInterp = uv; 94 | vec4 p = vec4(position, 0.0, 1.0); 95 | gl_Position = viewProjectionMatrix * modelMatrix * p; 96 | } 97 | ''' 98 | 99 | fragment_shader = ''' 100 | uniform sampler2D image; 101 | 102 | in vec2 uvInterp; 103 | 104 | void main() 105 | { 106 | gl_FragColor = texture(image, uvInterp); 107 | gl_FragColor.a = 0.5; 108 | } 109 | ''' 110 | 111 | def modal(self, context, event): 112 | area = context.area 113 | if area is None: 114 | return {'PASS_THROUGH'} 115 | area.tag_redraw() 116 | preview = context.window_manager.love2d3d.preview 117 | if not preview: 118 | return {'FINISHED'} 119 | return {'PASS_THROUGH'} 120 | 121 | def preview(self, context): 122 | image_front = context.window_manager.love2d3d.image_front # Image ID 123 | if image_front == "" or image_front is None: 124 | return 125 | self.shader = gpu.types.GPUShader( 126 | self.vertex_shader, self.fragment_shader) 127 | scale = context.window_manager.love2d3d.scale 128 | self.image = context.blend_data.images[image_front] # Get image 129 | if self.image.gl_load(): 130 | raise Exception() 131 | w, h = self.image.size 132 | w *= scale 133 | h *= scale 134 | view_align = context.window_manager.love2d3d.view_align 135 | lb = (-w / 2.0, -h / 2.0) 136 | rb = (w / 2.0, -h / 2.0) 137 | rt = (w / 2.0, h / 2.0) 138 | lt = (-w / 2.0, h / 2.0) 139 | self.batch = batch_for_shader( 140 | self.shader, 'TRI_FAN', 141 | { 142 | "position": (lb, rb, rt, lt), 143 | "uv": ((0, 0), (1, 0), (1, 1), (0, 1)), 144 | }, 145 | ) 146 | bgl.glActiveTexture(bgl.GL_TEXTURE0) 147 | bgl.glBindTexture(bgl.GL_TEXTURE_2D, self.image.bindcode) 148 | cursor = context.scene.cursor.location 149 | bgl.glEnable(bgl.GL_BLEND) 150 | self.shader.bind() 151 | if view_align: 152 | iview = Matrix(context.region_data.view_matrix). \ 153 | inverted_safe().to_3x3().to_4x4() 154 | self.shader.uniform_float( 155 | "modelMatrix", Matrix.Translation(cursor) @ iview) 156 | else: 157 | mat_rot = Matrix.Rotation(np.radians(90.0), 4, 'X') # rot matrix 158 | self.shader.uniform_float( 159 | "modelMatrix", Matrix.Translation(cursor) @ mat_rot) 160 | self.shader.uniform_float( 161 | "viewProjectionMatrix", context.region_data.perspective_matrix) 162 | self.shader.uniform_float("image", 0) 163 | self.batch.draw(self.shader) 164 | bgl.glDisable(bgl.GL_BLEND) 165 | 166 | def _handle_remove(self, context): 167 | if LOVE2D3D_OT_preview._handle is not None: 168 | bpy.types.SpaceView3D.draw_handler_remove( 169 | LOVE2D3D_OT_preview._handle, 'WINDOW') 170 | LOVE2D3D_OT_preview._handle = None 171 | 172 | def _handle_add(self, context): 173 | if LOVE2D3D_OT_preview._handle is None: 174 | space = bpy.types.SpaceView3D 175 | LOVE2D3D_OT_preview._handle = space.draw_handler_add( 176 | self.preview, (context,), 'WINDOW', 'POST_VIEW') 177 | 178 | def invoke(self, context, event): 179 | preview = context.window_manager.love2d3d.preview 180 | if context.area.type == 'VIEW_3D': 181 | if not preview: 182 | context.window_manager.love2d3d.preview = True 183 | else: 184 | context.window_manager.love2d3d.preview = False 185 | self._handle_remove(context) 186 | return {'FINISHED'} 187 | self._handle_add(context) 188 | context.window_manager.modal_handler_add(self) 189 | return {'RUNNING_MODAL'} 190 | else: 191 | self.report({'WARNING'}, "View3D not found, cannot run operator") 192 | self._handle_remove(context) 193 | return {'CANCELLED'} 194 | 195 | 196 | class LOVE2D3D_OT_createObject(bpy.types.Operator, AddObjectHelper): 197 | 198 | bl_idname = "object.love2d3d_add_mesh" 199 | bl_label = "Add love2D3D Mesh" 200 | bl_description = "Add 3D object from 2D image" 201 | bl_options = {'REGISTER', 'UNDO'} 202 | 203 | def execute(self, context): 204 | love2d3d = context.window_manager.love2d3d 205 | # debug_time = datetime.datetime.today() 206 | image = love2d3d.image_front # Image ID 207 | if image == "": 208 | return {"CANCELLED"} 209 | image = context.blend_data.images[image] # Get image 210 | resolution = love2d3d.rough # Get resolution 211 | w, h = image.size # Image width and height 212 | pixels = image.pixels[:] # Get slice of color infomation 213 | fronts = [] 214 | backs = [[True for i in range(w)] for j in range(h)] # whether bg 215 | ex = h - resolution # End of list 216 | ey = w - resolution # End of list 217 | opacity = love2d3d.opacity # use opacity or not 218 | threshold = love2d3d.threshold # bg's threshold 219 | for y in range(resolution, ex)[::resolution]: 220 | left = 0 + y * w 221 | il = RGBA * left # Get left index of color in image 222 | for x in range(resolution, ey)[::resolution]: 223 | back = False 224 | for v in range(resolution): 225 | for u in range(resolution): 226 | p = (x + u) + (y + v) * w # cuurent index in pixels 227 | i = RGBA * p # Get each index of color in image 228 | if opacity: # Whether opaque or not 229 | c = pixels[i + A] # each opacity in image 230 | cl = pixels[il + A] # left opacity in image 231 | back = back or c <= threshold 232 | else: # Whether same color or not 233 | c = pixels[i:i + RGB] # each RGB in image 234 | cl = pixels[il:il + RGB] # left RGB in image 235 | back = back or abs(c[R] - cl[R]) + \ 236 | abs(c[G] - cl[G]) \ 237 | + abs(cl[B] - cl[B]) <= threshold * 3.0 238 | if back: 239 | break 240 | if back: 241 | break 242 | backs[y][x] = back 243 | if not back: 244 | fronts.append((x // resolution, y // resolution)) 245 | del ex, ey, i, il, c, cl, back, pixels, p, left 246 | terms = [] # Edges of image 247 | for k, f in enumerate(fronts): 248 | fx = f[X] 249 | fy = f[Y] 250 | x = fx * resolution 251 | y = fy * resolution 252 | left = backs[y][x-resolution] 253 | right = backs[y][x+resolution] 254 | back = backs[y-resolution][x] 255 | top = backs[y+resolution][x] 256 | if not backs[y][x] and (left or right or back or top): 257 | terms.append((fx, fy)) # Get edge 258 | fronts[k] = (fx, fy, left, right, back, top) # Insert edge info 259 | lens = [[0.0 for i in range(w)[::resolution]] 260 | for j in range(h)[::resolution]] 261 | if len(fronts) == 0: 262 | return {"CANCELLED"} 263 | kd = KDTree(len(terms)) 264 | for i, t in enumerate(terms): 265 | kd.insert((t[X], t[Y], 0), i) 266 | kd.balance() 267 | ls = [0.0 for f in fronts] 268 | for k, f in enumerate(fronts): 269 | co_find = (f[X], f[Y], 0) 270 | co, index, dist = kd.find(co_find) 271 | ls[k] = dist 272 | # ms = np.sqrt(ls) + 1 # length array with softning 273 | ms = np.array([l + 1 for l in ls]) 274 | m = np.max(ms) 275 | ls = np.divide(ms, m) # Nomalize 276 | ms = (np.sin(ls * np.pi * 0.5) + 0) 277 | # ms = (np.arcsin(ls) + 0) 278 | 279 | for k, f in enumerate(fronts): 280 | fx = f[X] 281 | fy = f[Y] 282 | ls = ms[k] / 4.0 # Blur of height for edge 283 | lens[fy][fx] += ls 284 | fxi = fx + 1 285 | fyi = fy + 1 286 | lens[fy][fxi] += ls 287 | lens[fyi][fx] += ls 288 | lens[fyi][fxi] += ls 289 | del fx, fy, fxi, fyi, left, right, back, top, k, f, ms, ls, m 290 | verts = [] 291 | nei = 1 # Neighbor 292 | uvs = [] 293 | uvx = 0 / w 294 | uvy = 0 / h 295 | backs = [] 296 | view_align = love2d3d.view_align 297 | scale = love2d3d.scale 298 | s = min(w, h) / 8 299 | depth_front = s * love2d3d.depth_front * scale 300 | depth_back = s * love2d3d.depth_back * scale 301 | for f in fronts: 302 | x = f[X] 303 | y = f[Y] 304 | xi = x + nei 305 | yi = y + nei 306 | x1 = x * resolution 307 | x2 = xi * resolution 308 | y1 = y * resolution 309 | y2 = yi * resolution 310 | lu = x1 / w 311 | ru = x2 / w 312 | bu = y1 / h 313 | tu = y2 / h 314 | x1 = (x1 - w / 2) * scale 315 | x2 = (x2 - w / 2) * scale 316 | y1 = (y1 - h / 2) * scale 317 | y2 = (y2 - h / 2) * scale 318 | # Front face 319 | if view_align: 320 | p1 = (x1, y2, lens[yi][x] * depth_front) 321 | p2 = (x1, y1, lens[y][x] * depth_front) 322 | p3 = (x2, y1, lens[y][xi] * depth_front) 323 | p4 = (x2, y2, lens[yi][xi] * depth_front) 324 | else: 325 | p1 = (x1, -lens[yi][x] * depth_front, y2) 326 | p2 = (x1, -lens[y][x] * depth_front, y1) 327 | p3 = (x2, -lens[y][xi] * depth_front, y1) 328 | p4 = (x2, -lens[yi][xi] * depth_front, y2) 329 | verts.extend([p1, p2, p3, p4]) 330 | u1 = (lu + uvx, tu + uvy) 331 | u2 = (lu + uvx, bu + uvy) 332 | u3 = (ru + uvx, bu + uvy) 333 | u4 = (ru + uvx, tu + uvy) 334 | uvs.extend([u1, u2, u3, u4]) 335 | backs.append(FRONT) 336 | # Back face 337 | if view_align: 338 | p5 = (x2, y2, -lens[yi][xi] * depth_back) 339 | p6 = (x2, y1, -lens[y][xi] * depth_back) 340 | p7 = (x1, y1, -lens[y][x] * depth_back) 341 | p8 = (x1, y2, -lens[yi][x] * depth_back) 342 | else: 343 | p5 = (x2, lens[yi][xi] * depth_back, y2) 344 | p6 = (x2, lens[y][xi] * depth_back, y1) 345 | p7 = (x1, lens[y][x] * depth_back, y1) 346 | p8 = (x1, lens[yi][x] * depth_back, y2) 347 | verts.extend([p5, p6, p7, p8]) 348 | uvs.extend([u4, u3, u2, u1]) 349 | backs.append(BACK) 350 | if f[LEFT]: # Left face 351 | verts.extend([p8, p7, p2, p1]) 352 | uvs.extend([u1, u2, u2, u1]) 353 | backs.append(FRONT) 354 | if f[RIGHT]: # Right face 355 | verts.extend([p4, p3, p6, p5]) 356 | uvs.extend([u4, u3, u3, u4]) 357 | backs.append(FRONT) 358 | if f[TOP]: # Top face 359 | verts.extend([p8, p1, p4, p5]) 360 | uvs.extend([u1, u1, u4, u4]) 361 | backs.append(FRONT) 362 | if f[BOTTOM]: # Bottom face 363 | verts.extend([p2, p7, p6, p3]) 364 | uvs.extend([u2, u2, u3, u3]) 365 | backs.append(FRONT) 366 | del p1, p2, p3, p4, p5, p6, p7, p8, lens, nei, x, y 367 | del xi, yi, lu, ru, bu, tu, x1, x2, y1, y2 368 | del u1, u2, u3, u4 369 | faces = [(0, 0, 0, 0)] * (len(verts) // QUAD) 370 | for n, f in enumerate(faces): 371 | faces[n] = (QUAD * n, QUAD * n + 1, QUAD * n + 2, QUAD * n + 3) 372 | msh = bpy.data.meshes.new(NAME) 373 | msh.from_pydata(verts, [], faces) # Coordinate is Blender Coordinate 374 | msh.update() 375 | del verts, faces 376 | # obj = object_data_add(context, msh, operator=self).object 377 | obj = object_data_add(context, msh, operator=self) 378 | if view_align: 379 | obj.rotation_euler = Matrix(context.region_data.view_matrix).\ 380 | inverted_safe().to_euler() 381 | context.view_layer.objects.active = obj 382 | channel_name = "uv" 383 | msh.uv_layers.new(name=channel_name) # Create UV coordinate 384 | for idx, dat in enumerate(msh.uv_layers[channel_name].data): 385 | dat.uv = uvs[idx] 386 | del uvs 387 | matf = bpy.data.materials.new('Front') # Crate fornt material 388 | matf.use_nodes = True 389 | shader = matf.node_tree.nodes["Principled BSDF"] 390 | node = matf.node_tree.nodes.new("ShaderNodeTexImage") 391 | node.image = image 392 | input = node.outputs[0] 393 | output = matf.node_tree.get_output_node("ALL").inputs[0] 394 | matf.node_tree.links.new(input, shader.inputs[0]) 395 | matf.node_tree.links.new(shader.outputs[0], output) 396 | obj.data.materials.append(matf) 397 | matb = bpy.data.materials.new('Back') # Crate back material 398 | matb.use_nodes = True 399 | shader = matb.node_tree.nodes["Principled BSDF"] 400 | node = matb.node_tree.nodes.new("ShaderNodeTexImage") 401 | image_back = love2d3d.image_back 402 | if image_back == "": 403 | node.image = image 404 | else: 405 | image = context.blend_data.images[image_back] 406 | node.image = image 407 | input = node.outputs[0] 408 | output = matb.node_tree.get_output_node("ALL").inputs[0] 409 | matb.node_tree.links.new(input, shader.inputs[0]) 410 | matb.node_tree.links.new(shader.outputs[0], output) 411 | obj.data.materials.append(matb) 412 | for k, f in enumerate(obj.data.polygons): 413 | f.material_index = backs[k] # Set back material 414 | context.view_layer.objects.active = obj 415 | bpy.ops.object.mode_set(mode='EDIT') # Remove doubled point 416 | bpy.ops.mesh.remove_doubles() 417 | bpy.ops.object.mode_set(mode='OBJECT') 418 | context.view_layer.objects.active = obj 419 | bpy.ops.object.modifier_add(type='SMOOTH') 420 | smo = obj.modifiers["Smooth"] 421 | smo.iterations = love2d3d.smooth 422 | bpy.ops.object.modifier_add(type='DISPLACE') 423 | dis = obj.modifiers["Displace"] 424 | dis.strength = love2d3d.fat * scale / 0.01 425 | dec = None 426 | if love2d3d.decimate: 427 | bpy.ops.object.modifier_add(type='DECIMATE') 428 | dec = obj.modifiers["Decimate"] 429 | dec.ratio = love2d3d.decimate_ratio 430 | if love2d3d.modifier: 431 | bpy.ops.object.modifier_apply(apply_as='DATA', modifier="Smooth") 432 | bpy.ops.object.modifier_apply(apply_as='DATA', modifier="Displace") 433 | if love2d3d.decimate: 434 | bpy.ops.object.modifier_apply( 435 | apply_as='DATA', modifier="Decimate") 436 | obj.select_set(True) 437 | bpy.ops.object.shade_smooth() 438 | return {'FINISHED'} 439 | 440 | def draw(self, context): 441 | layout = self.layout 442 | love2d3d = context.window_manager.love2d3d 443 | col = layout.column(align=True) 444 | row = col.row() 445 | row.label(text="Image", icon="IMAGE_DATA") 446 | row.operator("image.open", icon="FILEBROWSER", text="") 447 | row.operator("image.new", icon="DUPLICATE", text="") 448 | col.prop_search(love2d3d, 449 | "image_front", context.blend_data, "images") 450 | col.prop_search(love2d3d, 451 | "image_back", context.blend_data, "images") 452 | layout.separator() 453 | col = layout.column(align=True) 454 | col.label(text="Separation", icon="IMAGE_RGB_ALPHA") 455 | col.prop(love2d3d, "threshold") 456 | col.prop(love2d3d, "opacity") 457 | layout.separator() 458 | col = layout.column(align=True) 459 | col.label(text="Geometry", icon="EDITMODE_HLT") 460 | col.prop(love2d3d, "view_align") 461 | col.prop(love2d3d, "depth_front") 462 | col.prop(love2d3d, "depth_back") 463 | col.prop(love2d3d, "scale") 464 | layout.separator() 465 | col = layout.column(align=True) 466 | col.label(text="Quality", icon="MOD_SMOOTH") 467 | col.prop(love2d3d, "rough") 468 | col.prop(love2d3d, "smooth") 469 | col.prop(love2d3d, "fat") 470 | layout.separator() 471 | col = layout.column(align=True) 472 | col.label(text="Decimate", icon="MOD_DECIM") 473 | col.prop(love2d3d, "decimate") 474 | col.prop(love2d3d, "decimate_ratio") 475 | layout.separator() 476 | col = layout.column(align=True) 477 | col.label(text="Option", icon="MODIFIER") 478 | col.prop(love2d3d, "modifier") 479 | 480 | def invoke(self, context, event): 481 | wm = context.window_manager 482 | return wm.invoke_props_dialog(self) 483 | 484 | 485 | class LOVE2D3D_OT_createArmature(bpy.types.Operator): 486 | 487 | bl_idname = "object.love2d3d_add_aramature" 488 | bl_label = "Add love2d3d armature" 489 | bl_description = "Add armature to selected objects" 490 | bl_options = {'REGISTER', 'UNDO'} 491 | finger_limit_angle: bpy.props.FloatProperty( 492 | name="Finger limit", description="Limit angle of finger", 493 | min=0.0, default=np.radians(8.0), subtype='ANGLE') 494 | hips_limit_angle: bpy.props.FloatProperty( 495 | name="Hips limit", description="Limit angle of hips", 496 | min=0.0, max=np.radians(90.0), 497 | default=np.radians(5.0), subtype='ANGLE') 498 | center_limit_angle: bpy.props.FloatProperty( 499 | name="Center limit", description="Limit angle of center", 500 | min=0.0, max=np.radians(90.0), 501 | default=np.radians(5.0), subtype='ANGLE') 502 | arm_limit_angle: bpy.props.FloatProperty( 503 | name="Arm limit", description="Limit angle of arm", 504 | min=0.0, max=np.radians(90.0), 505 | default=np.radians(30.0), subtype='ANGLE') 506 | leg_limit_angle: bpy.props.FloatProperty( 507 | name="Leg limit", description="Limit angle of leg", 508 | min=0.0, max=np.radians(90.0), 509 | default=np.radians(5.0), subtype='ANGLE') 510 | any_limit_angle: bpy.props.FloatProperty( 511 | name="Any limit", description="Limit angle of any bone", 512 | min=0.0, max=np.radians(90.0), 513 | default=np.radians(30.0), subtype='ANGLE') 514 | hand_limit_angle: bpy.props.FloatProperty( 515 | name="Hand limit", description="Limit angle of hand", 516 | min=0.0, max=np.radians(90.0), 517 | default=np.radians(45.0), subtype='ANGLE') 518 | branch_boost: bpy.props.FloatProperty( 519 | name="Boost", description="How many points hit as branch", 520 | min=0.01, default=3.0) 521 | finger_branch_boost: bpy.props.FloatProperty( 522 | name="Finger boost", 523 | description="How many points hit as branch in finger", 524 | min=0.01, default=3.0) 525 | gather_ratio: bpy.props.FloatProperty( 526 | name="Gather", description="How many branchs gather", 527 | min=0.0, max=100.0, default=10, subtype='PERCENTAGE') 528 | finger_gather_ratio: bpy.props.FloatProperty( 529 | name="Finger gather", description="How many branchs gather in finger", 530 | min=0.0, max=100.0, default=1, subtype='PERCENTAGE') 531 | tip_gather_ratio: bpy.props.FloatProperty( 532 | name="Tip gather", description="How many tips gather in finger", 533 | min=0.0, max=100.0, default=3, subtype='PERCENTAGE') 534 | 535 | def execute(self, context): 536 | return self.skinning(context) 537 | 538 | def draw(self, context): 539 | love2d3d = context.window_manager.love2d3d 540 | layout = self.layout 541 | layout.label(text="Main", icon="ARMATURE_DATA") 542 | col = layout.column(align=True) 543 | col.label(text="Resolution", icon="LATTICE_DATA") 544 | col.prop(love2d3d, "armature_resolution") 545 | col = layout.column(align=True) 546 | col.label(text="Limit", icon="CONSTRAINT") 547 | col.prop(self, "hips_limit_angle") 548 | col.prop(self, "center_limit_angle") 549 | col.prop(self, "arm_limit_angle") 550 | col.prop(self, "leg_limit_angle") 551 | col.prop(self, "any_limit_angle") 552 | col = layout.column(align=True) 553 | col.label(text="Amount", icon="EDITMODE_HLT") 554 | col.prop(self, "branch_boost") 555 | col.prop(self, "gather_ratio") 556 | layout.separator() 557 | # layout = layout.column(align=True) 558 | layout.label(text="Finger", icon="HAND") 559 | col = layout.column(align=True) 560 | col.prop(love2d3d, "armature_finger") 561 | col.label(text="Resolution", icon="LATTICE_DATA") 562 | col.prop(love2d3d, "armature_finger_resolution") 563 | col = layout.column(align=True) 564 | col.label(text="Limit", icon="CONSTRAINT") 565 | col.prop(self, "hand_limit_angle") 566 | col.prop(self, "finger_limit_angle") 567 | col = layout.column(align=True) 568 | col.label(text="Amount", icon="EDITMODE_HLT") 569 | col.prop(self, "finger_branch_boost") 570 | col.prop(self, "finger_gather_ratio") 571 | col.prop(self, "tip_gather_ratio") 572 | 573 | def bound_loc(self, obj): 574 | """ 575 | Getting bounds of object. 576 | """ 577 | bound = obj.bound_box 578 | mat = Matrix(obj.matrix_world) 579 | xs = [] 580 | ys = [] 581 | zs = [] 582 | for b in bound: 583 | loc = mat @ Vector(b) 584 | xs.append(loc.x) 585 | ys.append(loc.y) 586 | zs.append(loc.z) 587 | left = max(xs) 588 | right = min(xs) 589 | back = max(ys) 590 | front = min(ys) 591 | top = max(zs) 592 | bottom = min(zs) 593 | center = Vector(((left + right) * 0.5, (back + front) * 0.5, 594 | (top + bottom) * 0.5)) 595 | return (left, right, back, front, top, bottom, center) 596 | 597 | def primary_obj(self, group): 598 | """ 599 | Deciding of primary bone in group. 600 | """ 601 | max_volume = 0.0 602 | max_obj = None 603 | for obj in group: 604 | b = self.bound_loc(obj) 605 | le = b[BOUND_LEFT] 606 | ri = b[BOUND_RIGHT] 607 | ba = b[BOUND_BACK] 608 | fr = b[BOUND_FRONT] 609 | to = b[BOUND_TOP] 610 | bo = b[BOUND_BOTTOM] 611 | volume = (le - ri) * (ba - fr) * (to - bo) 612 | if max_volume < volume: 613 | max_volume = volume 614 | max_obj = obj 615 | return max_obj 616 | 617 | def _make_group(self, objects, index, hits): 618 | """ 619 | Recursion call of objects collision. 620 | """ 621 | current_count = len(hits) 622 | b = self.bound_loc(objects[index]) 623 | le = b[BOUND_LEFT] 624 | ri = b[BOUND_RIGHT] 625 | ba = b[BOUND_BACK] 626 | fr = b[BOUND_FRONT] 627 | to = b[BOUND_TOP] 628 | bo = b[BOUND_BOTTOM] 629 | neighbors = [] 630 | for k, neighbor in enumerate(objects): 631 | if index == k: 632 | continue 633 | n = self.bound_loc(neighbor) 634 | n_le = n[BOUND_LEFT] 635 | n_ri = n[BOUND_RIGHT] 636 | n_ba = n[BOUND_BACK] 637 | n_fr = n[BOUND_FRONT] 638 | n_to = n[BOUND_TOP] 639 | n_bo = n[BOUND_BOTTOM] 640 | avoid_x = le < n_ri or n_le < ri 641 | avoid_y = ba < n_fr or n_ba < fr 642 | avoid_z = to < n_bo or n_to < bo 643 | avoid = avoid_x or avoid_y or avoid_z 644 | if not avoid: # Hit 645 | neighbors.append(k) 646 | for neighbor in neighbors: 647 | already = False 648 | for hit in hits: 649 | already = already or neighbor == hit 650 | if not already: 651 | hits.append(neighbor) 652 | if current_count == len(hits): 653 | return True 654 | for h in hits: 655 | g = self._make_group(objects, h, hits) 656 | if g: 657 | return True 658 | 659 | def make_group(self, objects): 660 | """ 661 | Grouping of objects. 662 | """ 663 | groups = [] 664 | alredys = [False for l in objects] 665 | for k, object in enumerate(objects): 666 | if alredys[k]: 667 | continue 668 | hits = [k, ] 669 | self._make_group(objects, k, hits) 670 | group = [] 671 | for hit in hits: 672 | alredys[hit] = True 673 | group.append(objects[hit]) 674 | groups.append(group) 675 | return groups 676 | 677 | def skinning(self, context): 678 | # debug_time = datetime.datetime.today() 679 | if len(context.selected_objects) == 0: 680 | return {"CANCELLED"} 681 | objects = [] # Only Mesh 682 | for obj in context.selected_objects: 683 | if isinstance(obj.data, bpy.types.Mesh): 684 | objects.append(obj) 685 | if len(objects) == 0: 686 | return {"CANCELLED"} 687 | center = Vector((0, 0, 0)) 688 | sample = 0 689 | top = -sys.float_info.max 690 | bottom = sys.float_info.max 691 | 692 | for obj in objects: 693 | b = self.bound_loc(obj) 694 | to = b[BOUND_TOP] 695 | bo = b[BOUND_BOTTOM] 696 | ce = b[BOUND_CENTER] 697 | center += ce 698 | sample += 1 699 | top = max(top, to) 700 | bottom = min(bottom, bo) 701 | center /= sample 702 | """ 703 | Detect body 704 | """ 705 | min_length = sys.float_info.max 706 | body = None 707 | for obj in objects: 708 | b = self.bound_loc(obj) 709 | ce = b[BOUND_CENTER] 710 | length = (ce - center).length_squared 711 | if length < min_length: 712 | min_length = length 713 | body = obj 714 | if body is None: 715 | return 716 | """ 717 | Detect others 718 | """ 719 | heads = [] 720 | right_arms = [] 721 | left_arms = [] 722 | right_legs = [] 723 | left_legs = [] 724 | hips_height = self.lerp(bottom, top, 0.333) 725 | for obj in objects: 726 | body_bound = self.bound_loc(body) 727 | body_center = body_bound[BOUND_CENTER] 728 | # mat = Matrix(body.matrix_world) 729 | body_left = body_bound[BOUND_LEFT] 730 | body_right = body_bound[BOUND_RIGHT] 731 | body_radius = (body_left - body_right) * 0.5 732 | if body == obj: 733 | continue 734 | bound = self.bound_loc(obj) 735 | center = bound[BOUND_CENTER] 736 | radius = abs(center.x - body_center.x) 737 | if center.z < hips_height: 738 | if center.x < body_center.x: 739 | right_legs.append(obj) 740 | else: 741 | left_legs.append(obj) 742 | elif radius < body_radius: 743 | heads.append(obj) 744 | else: 745 | if center.x < body_center.x: 746 | right_arms.append(obj) 747 | else: 748 | left_arms.append(obj) 749 | """ 750 | Create armature 751 | """ 752 | bpy.ops.object.armature_add( 753 | location=(0.0, 0.0, 0.0), enter_editmode=True) 754 | # arma = context.active_object 755 | arma = context.view_layer.objects.active 756 | # context.object.show_x_ray = True 757 | context.object.show_in_front = True 758 | """ 759 | Body bone 760 | """ 761 | bone = arma.data.edit_bones[0] 762 | bone.name = "hips" 763 | hips, chest = self.create_bone( 764 | context, arma, bone, body, None, 765 | arma.data.edit_bones, bone_type=BONE_TYPE_BODY) 766 | """ 767 | Leg bones 768 | """ 769 | self.create_grouped_bone( 770 | context, right_legs, arma, hips, BONE_TYPE_LEG_RIGHT) 771 | self.create_grouped_bone( 772 | context, left_legs, arma, hips, BONE_TYPE_LEG_LEFT) 773 | 774 | head_groups = self.make_group(heads) 775 | for group in head_groups: 776 | primary_head = self.primary_obj(group) 777 | bone = arma.data.edit_bones.new("head") 778 | primary_bone = self.create_head(bone, primary_head, chest) 779 | for obj in group: 780 | if obj == primary_head: 781 | continue 782 | bone = arma.data.edit_bones.new("head") 783 | self.create_bone( 784 | context, arma, bone, obj, primary_bone, 785 | arma.data.edit_bones, bone_type=BONE_TYPE_ANY) 786 | self.create_grouped_bone( 787 | context, right_arms, 788 | arma, chest, BONE_TYPE_ARM_RIGHT) 789 | self.create_grouped_bone( 790 | context, left_arms, 791 | arma, chest, BONE_TYPE_ARM_LEFT) 792 | if context.window_manager.love2d3d.armature_finger: 793 | """ 794 | Rename fingers 795 | """ 796 | left_fingers = [] 797 | right_fingers = [] 798 | for bone in arma.data.edit_bones: 799 | if bone.name.startswith("finger") and not bone.use_connect: 800 | if bone.name.endswith(".L"): 801 | left_fingers.append(bone) 802 | else: 803 | right_fingers.append(bone) 804 | lefts = sorted(left_fingers, key=lambda bone: bone.head.y) 805 | rights = sorted(right_fingers, key=lambda bone: bone.head.y) 806 | for k, bone4 in enumerate(lefts): 807 | bone4.name = "finger" + str(k) + ".04" + ".L" 808 | bone3 = bone4.children_recursive[0] 809 | bone3.name = "finger" + str(k) + ".03" + ".L" 810 | bone2 = bone3.children_recursive[0] 811 | bone2.name = "finger" + str(k) + ".02" + ".L" 812 | bone1 = bone2.children_recursive[0] 813 | bone1.name = "finger" + str(k) + ".01" + ".L" 814 | for k, bone4 in enumerate(rights): 815 | bone4.name = "finger" + str(k) + ".04" + ".R" 816 | bone3 = bone4.children_recursive[0] 817 | bone3.name = "finger" + str(k) + ".03" + ".R" 818 | bone2 = bone3.children_recursive[0] 819 | bone2.name = "finger" + str(k) + ".02" + ".R" 820 | bone1 = bone2.children_recursive[0] 821 | bone1.name = "finger" + str(k) + ".01" + ".R" 822 | for bone in arma.data.edit_bones: 823 | bone.select = True 824 | bpy.ops.armature.calculate_roll(type='GLOBAL_POS_Z') 825 | bpy.ops.object.mode_set(mode='OBJECT', toggle=False) 826 | for obj in objects: 827 | obj.select_set(True) 828 | # context.scene.objects.active = arma 829 | context.view_layer.objects.active = arma 830 | bpy.ops.object.parent_set(type='ARMATURE_AUTO') 831 | # print(datetime.datetime.today() - debug_time) 832 | return {'FINISHED'} 833 | 834 | def create_grouped_bone(self, context, objects, 835 | armature, parent, bone_type): 836 | groups = self.make_group(objects) 837 | for group in groups: 838 | primary = self.primary_obj(group) 839 | bone = armature.data.edit_bones.new("bone") 840 | k, primary_bone = self.create_bone( 841 | context, armature, bone, primary, 842 | parent, armature.data.edit_bones, bone_type=bone_type) 843 | for obj in group: 844 | if obj == primary: 845 | continue 846 | bone = armature.data.edit_bones.new("bone") 847 | self.create_bone( 848 | context, armature, bone, obj, 849 | primary_bone, 850 | armature.data.edit_bones, 851 | bone_type=BONE_TYPE_ANY) 852 | 853 | def lerp(self, start, end, ratio): 854 | return start * (1 - ratio) + end * ratio 855 | 856 | def invlerp(self, value, start, end, r): 857 | ratio = (value - start) / (end - start) 858 | i = int(ratio * r + 0.5) 859 | # i = min(max(0, i), r - 1) 860 | i = min(max(0, i), r) 861 | return i 862 | 863 | def debug_point(self, context, location, type='PLAIN_AXES'): 864 | o = context.blend_data.objects.new("P", None) 865 | o.location = location 866 | o.scale = (0.01, 0.01, 0.01) 867 | # context.scene.objects.link(o) 868 | context.scene.collection.objects.link(o) 869 | # o.empty_draw_type = type 870 | o.empty_display_type = type 871 | 872 | def create_bone(self, context, armature, bone, obj, chest, 873 | bones, bone_type=BONE_TYPE_BODY, fingers=None): 874 | finger = fingers is not None 875 | if finger: 876 | bones.remove(bone) 877 | depsgraph = context.evaluated_depsgraph_get() 878 | object_eval = obj.evaluated_get(depsgraph) 879 | mesh_from_eval = object_eval.to_mesh() 880 | # mesh = obj.to_mesh() 881 | mesh = mesh_from_eval 882 | polygons = fingers if finger else mesh.polygons 883 | mat = Matrix(obj.matrix_world) 884 | if finger: 885 | polygons = fingers 886 | xs = [(mat @ Vector(polygon.center)).x for polygon in polygons] 887 | ys = [(mat @ Vector(polygon.center)).y for polygon in polygons] 888 | zs = [(mat @ Vector(polygon.center)).z for polygon in polygons] 889 | le = max(xs) 890 | ri = min(xs) 891 | ba = max(ys) 892 | fr = min(ys) 893 | to = max(zs) 894 | bo = min(zs) 895 | else: 896 | polygons = mesh.polygons 897 | b = self.bound_loc(obj) 898 | le = b[BOUND_LEFT] 899 | ri = b[BOUND_RIGHT] 900 | ba = b[BOUND_BACK] 901 | fr = b[BOUND_FRONT] 902 | to = b[BOUND_TOP] 903 | bo = b[BOUND_BOTTOM] 904 | ce = Vector(( 905 | self.lerp(ri, le, 0.5), self.lerp(fr, ba, 0.5), 906 | self.lerp(bo, to, 0.5))) 907 | if bone_type == BONE_TYPE_BODY: 908 | # ce = b[BOUND_CENTER] 909 | body_top = Vector((ce.x, ce.y, to)) 910 | body_bottom = Vector((ce.x, ce.y, bo)) 911 | len_x = le - ri 912 | len_y = ba - fr 913 | len_z = to - bo 914 | love2d3d = context.window_manager.love2d3d 915 | armature_resolution = love2d3d.armature_resolution 916 | finger_resolution = love2d3d.armature_finger_resolution 917 | if finger: 918 | lattice = min(len_x, len_y, len_z) / finger_resolution # units 919 | else: 920 | lattice = min(len_x, len_y, len_z) / armature_resolution # units 921 | if lattice == 0.0: 922 | return None, None 923 | rx = int(len_x / lattice) # x loop count 924 | ry = int(len_y / lattice) # y loop count 925 | rz = int(len_z / lattice) # z loop count 926 | rx = max(1, rx) 927 | ry = max(1, ry) 928 | rz = max(1, rz) 929 | mx = 1.0 / float(rx) 930 | my = 1.0 / float(ry) 931 | mz = 1.0 / float(rz) 932 | start = (0, 0, 0) 933 | if bone_type == BONE_TYPE_BODY: 934 | origin = body_bottom 935 | else: 936 | origin = Vector(chest.tail) 937 | centers = [] 938 | """ 939 | Volume separation process to reduce polygons' calculation. 940 | """ 941 | loop = 2 942 | half_x = self.lerp(ri, le, 0.5) 943 | half_y = self.lerp(fr, ba, 0.5) 944 | half_z = self.lerp(bo, to, 0.5) 945 | p = loop 946 | cakes = [[[[] for x in range(p)] for y in range(p)]for z in range(p)] 947 | for polygon in polygons: # Nearest polygon 948 | center = mat @ Vector(polygon.center) 949 | x = 0 if center.x <= half_x else 1 950 | y = 0 if center.y <= half_y else 1 951 | z = 0 if center.z <= half_z else 1 952 | cakes[z][y][x].append(polygon) 953 | kds = [[[None for x in range(loop)] for y in range(loop)] 954 | for z in range(loop)] 955 | for x in range(loop): 956 | for y in range(loop): 957 | for z in range(loop): 958 | cake = cakes[z][y][x] 959 | kd = KDTree(len(cake)) 960 | for i, polygon in enumerate(cake): 961 | kd.insert(mat @ Vector(polygon.center), i) 962 | kd.balance() 963 | kds[z][y][x] = kd 964 | """ 965 | Deciding process of inside points. 966 | """ 967 | for x in range(rx + 1): 968 | for y in range(ry + 1): 969 | for z in range(rz + 1): 970 | current = Vector(( 971 | self.lerp(ri, le, x * mx), 972 | self.lerp(fr, ba, y * my), 973 | self.lerp(bo, to, z * mz))) 974 | min_polygon = None 975 | s = 0 if current.x <= half_x else 1 976 | t = 0 if current.y <= half_y else 1 977 | u = 0 if current.z <= half_z else 1 978 | co_find = (current.x, current.y, current.z) 979 | co, index, dist = kds[u][t][s].find(co_find) 980 | if index is None: 981 | continue 982 | min_polygon = cakes[u][t][s][index] 983 | if min_polygon is None: 984 | continue 985 | normal = mat.to_quaternion() @ Vector(min_polygon.normal) 986 | center = mat @ (min_polygon.center) 987 | """ 988 | Approximation of polygon's region. 989 | """ 990 | min_length = np.sqrt(min_polygon.area) * 0.5 991 | vec = current - center 992 | coeff = vec.dot(normal) # Projection along Normal 993 | vec -= coeff * normal 994 | length = vec.length_squared # This is mistake. But good. 995 | close = coeff 996 | if length < min_length and close < 0: 997 | if finger: 998 | loc = coeff * normal + center 999 | lx, ly, lz = loc.xyz 1000 | u = self.invlerp(lx, ri, le, rx) 1001 | v = self.invlerp(ly, fr, ba, ry) 1002 | w = self.invlerp(lz, bo, to, rz) 1003 | centers.append((u, v, w)) # Volume thinning 1004 | else: 1005 | centers.append((x, y, z)) 1006 | # lx, ly, lz = center.xyz 1007 | # u = self.invlerp(lx, ri, le, rx) 1008 | # v = self.invlerp(ly, fr, ba, ry) 1009 | # w = self.invlerp(lz, bo, to, rz) 1010 | # centers.append((u, v, w)) 1011 | """ 1012 | Getting process of the nearest point from origin. 1013 | """ 1014 | center_kd = KDTree(len(centers)) 1015 | for i, c in enumerate(centers): 1016 | x, y, z = c 1017 | current = Vector(( 1018 | self.lerp(ri, le, x * mx), 1019 | self.lerp(fr, ba, y * my), 1020 | self.lerp(bo, to, z * mz))) 1021 | center_kd.insert(current.xyz, i) 1022 | center_kd.balance() 1023 | co, index, dist = center_kd.find(origin.xyz) 1024 | start = centers[index] 1025 | x, y, z = start 1026 | current = Vector(( 1027 | self.lerp(ri, le, x * mx), 1028 | self.lerp(fr, ba, y * my), 1029 | self.lerp(bo, to, z * mz))) 1030 | if not finger: 1031 | bone.head = current 1032 | """ 1033 | Getting process of the farthest point. 1034 | """ 1035 | end = start 1036 | max_length = 0.0 1037 | max_coeff = 0.0 1038 | for c in centers: 1039 | x, y, z = c 1040 | current = Vector(( 1041 | self.lerp(ri, le, x * mx), 1042 | self.lerp(fr, ba, y * my), 1043 | self.lerp(bo, to, z * mz))) 1044 | u, v, w = start 1045 | s = Vector(( 1046 | self.lerp(ri, le, u * mx), 1047 | self.lerp(fr, ba, v * my), 1048 | self.lerp(bo, to, w * mz))) 1049 | if finger: 1050 | vec = chest.tail - chest.head 1051 | coeff = (current - s).dot(vec) 1052 | if max_coeff < coeff: 1053 | max_coeff = coeff 1054 | end = (x, y, z) 1055 | else: 1056 | length = (current - s).length_squared 1057 | if max_length < length: 1058 | max_length = length 1059 | end = (x, y, z) 1060 | sx, sy, sz = start 1061 | 1062 | def limit_hips(index): 1063 | stem = (body_top - body_bottom) 1064 | cx, cy, cz = centers[index] 1065 | current = Vector(( 1066 | self.lerp(ri, le, cx * mx), 1067 | self.lerp(fr, ba, cy * my), 1068 | self.lerp(bo, to, cz * mz))) 1069 | branch = current - body_bottom 1070 | if stem.length_squared == 0.0 or branch.length_squared == 0.0: 1071 | return False 1072 | return stem.angle(branch) < self.hips_limit_angle 1073 | 1074 | if bone_type == BONE_TYPE_BODY: 1075 | min_length = sys.float_info.max 1076 | """ 1077 | Getting process of hips point. 1078 | """ 1079 | co, index, dist = center_kd.find(origin.xyz, filter=limit_hips) 1080 | if co is None: 1081 | start_loc = body_bottom 1082 | else: 1083 | start_loc = Vector(( 1084 | body_bottom.x, 1085 | body_bottom.y, 1086 | Vector(co).z)) 1087 | else: 1088 | start_loc = Vector(( 1089 | self.lerp(ri, le, sx * mx), 1090 | self.lerp(fr, ba, sy * my), 1091 | self.lerp(bo, to, sz * mz))) 1092 | if not finger: 1093 | bone.head = start_loc 1094 | ex, ey, ez = end 1095 | if bone_type == BONE_TYPE_BODY: 1096 | end_loc = body_top 1097 | else: 1098 | end_loc = Vector(( 1099 | self.lerp(ri, le, ex * mx), 1100 | self.lerp(fr, ba, ey * my), 1101 | self.lerp(bo, to, ez * mz))) 1102 | """ 1103 | Getting process of center and neck point. 1104 | """ 1105 | co, index, dist = center_kd.find(start_loc.lerp(end_loc, 0.5).xyz) 1106 | center_loc = co 1107 | co, index, dist = center_kd.find(start_loc.lerp(end_loc, 0.75).xyz) 1108 | neck_loc = co 1109 | if bone_type == BONE_TYPE_BODY: 1110 | hips_loc = start_loc.lerp(end_loc, 0.25) 1111 | bone.tail = hips_loc 1112 | center_loc = start_loc.lerp(end_loc, 0.5) 1113 | neck_loc = start_loc.lerp(end_loc, 0.75) 1114 | elif finger: 1115 | pass 1116 | else: 1117 | bone.tail = center_loc 1118 | bone.parent = chest 1119 | parent = bone 1120 | start_bone = bone 1121 | """ 1122 | Create process of primary bones. 1123 | """ 1124 | if bone_type == BONE_TYPE_BODY: 1125 | bone = bones.new("waist") 1126 | bone.head = hips_loc 1127 | bone.tail = center_loc 1128 | bone.parent = parent 1129 | bone.use_connect = True 1130 | parent = bone 1131 | bone = bones.new("chest") 1132 | bone.head = center_loc 1133 | bone.tail = neck_loc 1134 | bone.parent = parent 1135 | bone.use_connect = True 1136 | parent = bone 1137 | chest_bone = bone 1138 | bone = bones.new("neck") 1139 | bone.head = neck_loc 1140 | bone.tail = end_loc 1141 | bone.parent = parent 1142 | bone.use_connect = True 1143 | end_bone = bone 1144 | 1145 | elif bone_type == BONE_TYPE_FINGER_LEFT or\ 1146 | bone_type == BONE_TYPE_FINGER_RIGHT: 1147 | name = "finger" 1148 | end_bone = None 1149 | start_bone = None 1150 | else: 1151 | if bone_type == BONE_TYPE_HEAD: 1152 | name = "bone" 1153 | elif bone_type == BONE_TYPE_ARM_LEFT: 1154 | name = "shoulder.L" 1155 | elif bone_type == BONE_TYPE_ARM_RIGHT: 1156 | name = "shoulder.R" 1157 | elif bone_type == BONE_TYPE_LEG_LEFT: 1158 | name = "pelvis.L" 1159 | elif bone_type == BONE_TYPE_LEG_RIGHT: 1160 | name = "pelvis.R" 1161 | else: 1162 | name = "bone" 1163 | bone.name = name 1164 | vec = (origin - start_loc) 1165 | basis = (start_loc - center_loc).normalized() 1166 | coeff = vec.dot(basis) 1167 | bone.head = 0.5 * coeff * basis + start_loc 1168 | bone.tail = start_loc 1169 | 1170 | if bone_type == BONE_TYPE_HEAD: 1171 | name = "bone" 1172 | elif bone_type == BONE_TYPE_ARM_LEFT: 1173 | name = "upper_arm.L" 1174 | elif bone_type == BONE_TYPE_ARM_RIGHT: 1175 | name = "upper_arm.R" 1176 | elif bone_type == BONE_TYPE_LEG_LEFT: 1177 | name = "thigh.L" 1178 | elif bone_type == BONE_TYPE_LEG_RIGHT: 1179 | name = "thigh.R" 1180 | else: 1181 | name = "bone" 1182 | bone = bones.new(name) 1183 | bone.head = start_loc 1184 | bone.tail = center_loc 1185 | bone.parent = parent 1186 | bone.use_connect = True 1187 | parent = bone 1188 | if bone_type == BONE_TYPE_HEAD: 1189 | name = "bone" 1190 | elif bone_type == BONE_TYPE_ARM_LEFT: 1191 | name = "forearm.L" 1192 | elif bone_type == BONE_TYPE_ARM_RIGHT: 1193 | name = "forearm.R" 1194 | elif bone_type == BONE_TYPE_LEG_LEFT: 1195 | name = "shin.L" 1196 | elif bone_type == BONE_TYPE_LEG_RIGHT: 1197 | name = "shin.R" 1198 | else: 1199 | name = "bone" 1200 | bone = bones.new(name) 1201 | bone.head = center_loc 1202 | bone.tail = neck_loc 1203 | bone.parent = parent 1204 | bone.use_connect = True 1205 | parent = bone 1206 | if bone_type == BONE_TYPE_HEAD: 1207 | name = "bone" 1208 | elif bone_type == BONE_TYPE_ARM_LEFT: 1209 | name = "hand.L" 1210 | elif bone_type == BONE_TYPE_ARM_RIGHT: 1211 | name = "hand.R" 1212 | elif bone_type == BONE_TYPE_LEG_LEFT: 1213 | name = "foot.L" 1214 | elif bone_type == BONE_TYPE_LEG_RIGHT: 1215 | name = "foot.R" 1216 | else: 1217 | name = "bone" 1218 | bone = bones.new(name) 1219 | bone.head = neck_loc 1220 | bone.tail = end_loc 1221 | bone.parent = parent 1222 | bone.use_connect = True 1223 | end_bone = bone 1224 | 1225 | """ 1226 | Caluculate process of volume. 1227 | """ 1228 | volume_xs = [0 for x in range(rx + 1)] 1229 | volume_ys = [0 for y in range(ry + 1)] 1230 | volume_zs = [0 for z in range(rz + 1)] 1231 | unit_x = my * mz 1232 | unit_y = mz * mx 1233 | unit_z = mx * my 1234 | for center in centers: 1235 | x, y, z = center 1236 | volume_xs[x] += unit_x 1237 | volume_ys[y] += unit_y 1238 | volume_zs[z] += unit_z 1239 | hit_xs = [] 1240 | hit_ys = [] 1241 | hit_zs = [] 1242 | threshopld = 1.0 1243 | ratio = self.finger_branch_boost if finger else self.branch_boost 1244 | soft_x = unit_x * 0.0001 1245 | soft_y = unit_y * 0.0001 1246 | soft_z = unit_z * 0.0001 1247 | """ 1248 | Differential process of volume in log scale. 1249 | It tell us like "A's scale is B's scale of x1, x10, x100...". 1250 | """ 1251 | for x in range(1, rx + 1 - 1): 1252 | vm = volume_xs[x - 1] 1253 | v0 = volume_xs[x] 1254 | v1 = volume_xs[x + 1] 1255 | sv = np.log2(v0 + soft_x) * ratio 1256 | ev = np.log2(v1 + soft_x) * ratio 1257 | mv = np.log2(vm + soft_x) * ratio 1258 | diff = (2 * sv - ev - mv) ** 2 1259 | if threshopld <= diff: 1260 | hit_xs.append(x) 1261 | for y in range(1, ry + 1 - 1): 1262 | vm = volume_ys[y - 1] 1263 | v0 = volume_ys[y] 1264 | v1 = volume_ys[y + 1] 1265 | sv = np.log2(v0 + soft_y) * ratio 1266 | ev = np.log2(v1 + soft_y) * ratio 1267 | mv = np.log2(vm + soft_y) * ratio 1268 | diff = (2 * sv - ev - mv) ** 2 1269 | if threshopld <= diff: 1270 | hit_ys.append(y) 1271 | for z in range(1, rz + 1 - 1): 1272 | vm = volume_zs[z - 1] 1273 | v0 = volume_zs[z] 1274 | v1 = volume_zs[z + 1] 1275 | sv = np.log2(v0 + soft_z) * ratio 1276 | ev = np.log2(v1 + soft_z) * ratio 1277 | mv = np.log2(vm + soft_z) * ratio 1278 | diff = (2 * sv - ev - mv) ** 2 1279 | if threshopld <= diff: 1280 | hit_zs.append(z) 1281 | """ 1282 | Diciding process of branch point like fingers. 1283 | """ 1284 | hits = [] 1285 | average = Vector((0, 0, 0)) 1286 | dispersion = 0.0 1287 | sum = 0 1288 | center_limit = self.center_limit_angle 1289 | for x in hit_xs: 1290 | for y in hit_ys: 1291 | for z in hit_zs: 1292 | for center in centers: 1293 | u, v, w = center 1294 | current = Vector(( 1295 | self.lerp(ri, le, u * mx), 1296 | self.lerp(fr, ba, v * my), 1297 | self.lerp(bo, to, w * mz))) 1298 | free = True 1299 | """ 1300 | Avoiding process of body's neck. 1301 | It is because bad points for shoulders. 1302 | """ 1303 | if bone_type == BONE_TYPE_BODY: 1304 | proj0 = Vector(( 1305 | current.x, 1306 | body_bottom.y, 1307 | current.z)) 1308 | proj1 = Vector((current.x, body_top.y, current.z)) 1309 | branch0 = (proj0 - body_bottom) 1310 | branch1 = (proj1 - body_top) 1311 | stem = (body_top - body_bottom) 1312 | if branch0.length_squared == 0.0 or\ 1313 | branch1.length_squared == 0.0: 1314 | continue 1315 | angle0 = stem.angle(branch0) 1316 | angle1 = stem.angle(branch1) 1317 | free = center_limit < angle0 and\ 1318 | center_limit < angle1 1319 | if x == u and y == v and z == w and free: 1320 | hits.append((u, v, w)) 1321 | average += current 1322 | dispersion += current.length_squared 1323 | sum += 1 1324 | """ 1325 | Gathering process of branch points. 1326 | """ 1327 | if sum == 0: 1328 | return start_bone, end_bone 1329 | average /= sum 1330 | dispersion /= sum 1331 | dispersion -= average.length_squared 1332 | finger_ratio = (dispersion * self.finger_gather_ratio * 0.01) 1333 | body_ratio = (dispersion * self.gather_ratio * 0.01) 1334 | gather_ratio = finger_ratio if finger else body_ratio 1335 | bound = le, ri, ba, fr, to, bo 1336 | m = mx, my, mz 1337 | gathers = self.gather_point(hits, bound, m, gather_ratio) 1338 | joints = [] 1339 | """ 1340 | Averaging process of gatherd branch points. 1341 | """ 1342 | for gather in gathers: 1343 | average = Vector((0, 0, 0)) 1344 | sum = 0 1345 | for point in gather: 1346 | px, py, pz = hits[point] 1347 | p_loc = Vector(( 1348 | self.lerp(ri, le, px * mx), 1349 | self.lerp(fr, ba, py * my), 1350 | self.lerp(bo, to, pz * mz))) 1351 | average += p_loc 1352 | sum += 1 1353 | if sum == 0: 1354 | continue 1355 | average /= sum 1356 | joints.append(average) 1357 | """ 1358 | Calculating and creating process of bones. 1359 | """ 1360 | def create_joint(centers, hinges, bounds, rs, ms, dispersion, name, 1361 | parent, bone_type, end=Vector((0, 0, 0))): 1362 | le, ri, ba, fr, to, bo = bounds 1363 | rx, ry, rz = rs 1364 | ms = (mx, my, mz) 1365 | tips = [( 1366 | self.invlerp(hinge[1].x, ri, le, rx), 1367 | self.invlerp(hinge[1].y, fr, ba, ry), 1368 | self.invlerp(hinge[1].z, bo, to, rz)) for hinge in hinges] 1369 | if bone_type == BONE_TYPE_FINGER_LEFT or\ 1370 | bone_type == BONE_TYPE_FINGER_RIGHT: 1371 | gathers = self.gather_point(tips, bounds, ms, 1372 | dispersion, parent=parent, end=end) 1373 | else: 1374 | gathers = self.gather_point(tips, bounds, ms, dispersion) 1375 | """ 1376 | Averaging process of gathered tips. 1377 | """ 1378 | averages = [Vector((0, 0, 0)) for g in gathers] 1379 | for k, gather in enumerate(gathers): 1380 | if bone_type == BONE_TYPE_FINGER_LEFT or\ 1381 | bone_type == BONE_TYPE_FINGER_RIGHT: 1382 | average = Vector((0, 0, 0)) 1383 | max_close = 0.0 1384 | max_point = Vector((0, 0, 0)) 1385 | vec = end - parent.head 1386 | for point in gather: 1387 | px, py, pz = tips[point] 1388 | p_loc = Vector(( 1389 | self.lerp(ri, le, px * mx), 1390 | self.lerp(fr, ba, py * my), 1391 | self.lerp(bo, to, pz * mz))) 1392 | close = p_loc.dot(vec) 1393 | if max_close < close: 1394 | max_close = close 1395 | max_point = p_loc 1396 | averages[k] = max_point 1397 | else: 1398 | average = Vector((0, 0, 0)) 1399 | sum = 0 1400 | for point in gather: 1401 | px, py, pz = tips[point] 1402 | p_loc = Vector(( 1403 | self.lerp(ri, le, px * mx), 1404 | self.lerp(fr, ba, py * my), 1405 | self.lerp(bo, to, pz * mz))) 1406 | average += p_loc 1407 | sum += 1 1408 | average /= sum 1409 | averages[k] = average 1410 | """ 1411 | Grouping hinge by average points. 1412 | """ 1413 | groups = [[] for a in averages] 1414 | for hinge in hinges: 1415 | joint, tip = hinge 1416 | min_length = sys.float_info.max 1417 | min_index = 0 1418 | for k, average in enumerate(averages): 1419 | length = (average - tip).length_squared 1420 | if length < min_length: 1421 | min_length = length 1422 | min_index = k 1423 | groups[min_index].append(joint) 1424 | end_bones = [] 1425 | for k, tip in enumerate(averages): 1426 | max_length = -sys.float_info.max 1427 | max_joint = tip 1428 | group = groups[k] 1429 | if len(group) == 0: 1430 | continue 1431 | for joint in group: 1432 | length = (joint - tip).length_squared 1433 | if max_length < length: 1434 | max_length = length 1435 | max_joint = joint 1436 | """ 1437 | Elbow 1438 | """ 1439 | f = max_joint.lerp(tip, 0.5).xyz 1440 | co, index, dist = center_kd.find(f) 1441 | min_e = co 1442 | """ 1443 | Hand neck 1444 | """ 1445 | f = max_joint.lerp(tip, 0.75).xyz 1446 | co, index, dist = center_kd.find(f) 1447 | min_n = co 1448 | """ 1449 | Finger tip 1450 | """ 1451 | f = max_joint.lerp(tip, 0.875).xyz 1452 | co, index, dist = center_kd.find(f) 1453 | min_t = co 1454 | """ 1455 | Shoulder 1456 | """ 1457 | vec = (max_joint - min_e).normalized() 1458 | coeff = (parent.tail - max_joint).dot(vec) 1459 | s = vec * coeff * 0.5 + max_joint 1460 | co, index, dist = center_kd.find(s.xyz) 1461 | min_s = co 1462 | if bone_type == BONE_TYPE_HEAD: 1463 | name = "bone" 1464 | elif bone_type == BONE_TYPE_ARM_LEFT: 1465 | name = "shoulder.L" 1466 | elif bone_type == BONE_TYPE_ARM_RIGHT: 1467 | name = "shoulder.R" 1468 | elif bone_type == BONE_TYPE_LEG_LEFT: 1469 | name = "pelvis.L" 1470 | elif bone_type == BONE_TYPE_LEG_RIGHT: 1471 | name = "pelvis.R" 1472 | elif bone_type == BONE_TYPE_FINGER_LEFT: 1473 | name = "finger" + self.index_bone(max_joint) + ".L" 1474 | elif bone_type == BONE_TYPE_FINGER_RIGHT: 1475 | name = "finger" + self.index_bone(max_joint) + ".R" 1476 | else: 1477 | name = "bone" 1478 | 1479 | if bone_type != BONE_TYPE_FINGER_LEFT and\ 1480 | bone_type != BONE_TYPE_FINGER_RIGHT: 1481 | bone = bones.new(name) 1482 | bone.head = min_s 1483 | bone.tail = max_joint 1484 | bone.parent = parent 1485 | p = bone 1486 | if bone_type == BONE_TYPE_HEAD: 1487 | name = "bone" 1488 | elif bone_type == BONE_TYPE_ARM_LEFT: 1489 | name = "upper_arm.L" 1490 | elif bone_type == BONE_TYPE_ARM_RIGHT: 1491 | name = "upper_arm.R" 1492 | elif bone_type == BONE_TYPE_LEG_LEFT: 1493 | name = "thigh.L" 1494 | elif bone_type == BONE_TYPE_LEG_RIGHT: 1495 | name = "thigh.R" 1496 | elif bone_type == BONE_TYPE_FINGER_LEFT: 1497 | name = "finger" + self.index_bone(max_joint) + ".L" 1498 | elif bone_type == BONE_TYPE_FINGER_RIGHT: 1499 | name = "finger" + self.index_bone(max_joint) + ".R" 1500 | else: 1501 | name = "bone" 1502 | bone = bones.new(name) 1503 | bone.head = max_joint 1504 | bone.tail = min_e 1505 | if bone_type == BONE_TYPE_FINGER_LEFT or\ 1506 | bone_type == BONE_TYPE_FINGER_RIGHT: 1507 | bone.parent = parent 1508 | else: 1509 | bone.parent = p 1510 | bone.use_connect = True 1511 | p = bone 1512 | if bone_type == BONE_TYPE_HEAD: 1513 | name = "bone" 1514 | elif bone_type == BONE_TYPE_ARM_LEFT: 1515 | name = "forearm.L" 1516 | elif bone_type == BONE_TYPE_ARM_RIGHT: 1517 | name = "forearm.R" 1518 | elif bone_type == BONE_TYPE_LEG_LEFT: 1519 | name = "shin.L" 1520 | elif bone_type == BONE_TYPE_LEG_RIGHT: 1521 | name = "shin.R" 1522 | elif bone_type == BONE_TYPE_FINGER_LEFT: 1523 | name = "finger" + self.index_bone(max_joint) + ".L" 1524 | elif bone_type == BONE_TYPE_FINGER_RIGHT: 1525 | name = "finger" + self.index_bone(max_joint) + ".R" 1526 | else: 1527 | name = "bone" 1528 | bone = bones.new(name) 1529 | bone.head = min_e 1530 | bone.tail = min_n 1531 | bone.parent = p 1532 | bone.use_connect = True 1533 | p = bone 1534 | if bone_type == BONE_TYPE_HEAD: 1535 | name = "bone" 1536 | elif bone_type == BONE_TYPE_ARM_LEFT: 1537 | name = "hand.L" 1538 | elif bone_type == BONE_TYPE_ARM_RIGHT: 1539 | name = "hand.R" 1540 | elif bone_type == BONE_TYPE_LEG_LEFT: 1541 | name = "foot.L" 1542 | elif bone_type == BONE_TYPE_LEG_RIGHT: 1543 | name = "foot.R" 1544 | elif bone_type == BONE_TYPE_FINGER_LEFT: 1545 | name = "finger" + self.index_bone(max_joint) + ".L" 1546 | elif bone_type == BONE_TYPE_FINGER_RIGHT: 1547 | name = "finger" + self.index_bone(max_joint) + ".R" 1548 | else: 1549 | name = "bone" 1550 | bone = bones.new(name) 1551 | bone.head = min_n 1552 | if bone_type == BONE_TYPE_FINGER_LEFT or\ 1553 | bone_type == BONE_TYPE_FINGER_RIGHT: 1554 | bone.tail = min_t 1555 | else: 1556 | bone.tail = tip 1557 | bone.parent = p 1558 | bone.use_connect = True 1559 | p = bone 1560 | if bone_type == BONE_TYPE_FINGER_LEFT: 1561 | name = "finger" + self.index_bone(max_joint) + ".L" 1562 | bone = bones.new(name) 1563 | bone.head = min_n 1564 | bone.tail = tip 1565 | bone.parent = p 1566 | bone.use_connect = True 1567 | elif bone_type == BONE_TYPE_FINGER_RIGHT: 1568 | name = "finger" + self.index_bone(max_joint) + ".R" 1569 | bone = bones.new(name) 1570 | bone.head = min_n 1571 | bone.tail = tip 1572 | bone.parent = p 1573 | bone.use_connect = True 1574 | end_bones.append(bone) 1575 | 1576 | return end_bones 1577 | """ 1578 | Getting process of tips like fingers's tip. 1579 | """ 1580 | left_hands = [] 1581 | right_hands = [] 1582 | left_foots = [] 1583 | right_foots = [] 1584 | if bone_type == BONE_TYPE_BODY: 1585 | right_arms = [] 1586 | left_arms = [] 1587 | right_legs = [] 1588 | left_legs = [] 1589 | for joint in joints: 1590 | arm = hips_loc.z < joint.z 1591 | left = 0 < joint.x - center_loc.x 1592 | if arm: 1593 | stem = neck_loc - center_loc 1594 | branch = joint - center_loc 1595 | else: 1596 | stem = start_loc - center_loc 1597 | branch = joint - center_loc 1598 | if stem.length_squared == 0.0 or branch.length_squared == 0.0: 1599 | continue 1600 | branch_normal = branch.normalized() 1601 | angle = stem.angle(branch) 1602 | arm_angle = self.arm_limit_angle 1603 | leg_angle = self.leg_limit_angle 1604 | limit_angle = arm_angle if arm else leg_angle 1605 | if limit_angle < angle: 1606 | max_close = -sys.float_info.max 1607 | max_loc = joint 1608 | for center in centers: 1609 | x, y, z = center 1610 | current = Vector(( 1611 | self.lerp(ri, le, x * mx), 1612 | self.lerp(fr, ba, y * my), 1613 | self.lerp(bo, to, z * mz))) 1614 | if arm: 1615 | height = hips_loc.z < current.z 1616 | else: 1617 | height = current.z < hips_loc.z 1618 | close = (current - joint).dot(branch_normal) 1619 | if height and max_close < close: 1620 | max_close = close 1621 | max_loc = current 1622 | if arm: 1623 | if left: 1624 | left_arms.append((joint, max_loc)) 1625 | else: 1626 | right_arms.append((joint, max_loc)) 1627 | else: 1628 | if left: 1629 | left_legs.append((joint, max_loc)) 1630 | else: 1631 | right_legs.append((joint, max_loc)) 1632 | bounds = (le, ri, ba, fr, to, bo) 1633 | ms = (mx, my, mz) 1634 | rs = (rx, ry, rz) 1635 | left_hands = create_joint( 1636 | centers, left_arms, bounds, rs, ms, 1637 | gather_ratio, "Arm.L", chest_bone, BONE_TYPE_ARM_LEFT) 1638 | right_hands = create_joint( 1639 | centers, right_arms, bounds, rs, ms, 1640 | gather_ratio, "Arm.R", chest_bone, BONE_TYPE_ARM_RIGHT) 1641 | left_foots = create_joint( 1642 | centers, left_legs, bounds, rs, ms, 1643 | gather_ratio, "Leg.L", start_bone, BONE_TYPE_LEG_LEFT) 1644 | right_foots = create_joint( 1645 | centers, right_legs, bounds, rs, ms, 1646 | gather_ratio, "Leg.R", start_bone, BONE_TYPE_LEG_RIGHT) 1647 | elif (bone_type == BONE_TYPE_FINGER_LEFT or 1648 | bone_type == BONE_TYPE_FINGER_RIGHT): 1649 | tips = [] 1650 | for joint in joints: 1651 | stem = end_loc - chest.head 1652 | branch = joint - chest.head 1653 | if stem.length_squared == 0.0 or branch.length_squared == 0.0: 1654 | continue 1655 | branch_normal = branch.normalized() 1656 | angle = stem.angle(branch) 1657 | limit_angle = self.finger_limit_angle 1658 | close = stem.dot(branch) 1659 | if 0 < close: 1660 | max_close = -sys.float_info.max 1661 | max_loc = joint 1662 | for center in centers: 1663 | x, y, z = center 1664 | current = Vector(( 1665 | self.lerp(ri, le, x * mx), 1666 | self.lerp(fr, ba, y * my), 1667 | self.lerp(bo, to, z * mz))) 1668 | vec0 = current - joint 1669 | close = vec0.dot(stem) 1670 | if vec0.length_squared == 0.0: 1671 | continue 1672 | angle = vec0.angle(stem) 1673 | if max_close < close and angle < limit_angle: 1674 | max_close = close 1675 | max_loc = current 1676 | tips.append((joint, max_loc)) 1677 | bounds = (le, ri, ba, fr, to, bo) 1678 | ms = (mx, my, mz) 1679 | rs = (rx, ry, rz) 1680 | tip_gather_ratio = dispersion * self.tip_gather_ratio * 0.01 1681 | create_joint(centers, tips, bounds, rs, ms, 1682 | tip_gather_ratio, name, chest, bone_type, end=end_loc) 1683 | else: 1684 | tips = [] 1685 | for joint in joints: 1686 | stem = end_loc - neck_loc 1687 | branch = joint - neck_loc 1688 | if stem.length_squared == 0.0 or branch.length_squared == 0.0: 1689 | continue 1690 | branch_normal = branch.normalized() 1691 | angle = stem.angle(branch) 1692 | limit_angle = self.any_limit_angle 1693 | close = stem.dot(branch) 1694 | if 0 < close and limit_angle < angle: 1695 | max_close = -sys.float_info.max 1696 | max_loc = joint 1697 | for center in centers: 1698 | x, y, z = center 1699 | current = Vector(( 1700 | self.lerp(ri, le, x * mx), 1701 | self.lerp(fr, ba, y * my), 1702 | self.lerp(bo, to, z * mz))) 1703 | close = (current - joint).dot(branch_normal) 1704 | if max_close < close: 1705 | max_close = close 1706 | max_loc = current 1707 | tips.append((joint, max_loc)) 1708 | bounds = (le, ri, ba, fr, to, bo) 1709 | ms = (mx, my, mz) 1710 | rs = (rx, ry, rz) 1711 | if (bone_type == BONE_TYPE_ARM_LEFT or 1712 | bone_type == BONE_TYPE_ARM_RIGHT) and\ 1713 | context.window_manager.love2d3d.armature_finger: 1714 | pass 1715 | else: 1716 | create_joint(centers, tips, bounds, rs, ms, gather_ratio, 1717 | name, end_bone, BONE_TYPE_ANY) 1718 | if bone_type == BONE_TYPE_ARM_LEFT: 1719 | left_hands.append(end_bone) 1720 | elif bone_type == BONE_TYPE_ARM_RIGHT: 1721 | right_hands.append(end_bone) 1722 | """ 1723 | Finger process. 1724 | """ 1725 | if not finger and context.window_manager.love2d3d.armature_finger: 1726 | self.create_finger(context, armature, obj, polygons, mat, bones, 1727 | left_hands, BONE_TYPE_FINGER_LEFT) 1728 | self.create_finger(context, armature, obj, polygons, mat, bones, 1729 | right_hands, BONE_TYPE_FINGER_RIGHT) 1730 | """ 1731 | Finish process. 1732 | """ 1733 | if not finger: 1734 | object_eval.to_mesh_clear() 1735 | return start_bone, end_bone 1736 | 1737 | def index_bone(self, location): 1738 | y = int(location.y * 1000) 1739 | return str(y) 1740 | 1741 | def create_finger(self, context, armature, obj, polygons, 1742 | mat, bones, hands, bone_type): 1743 | for hand in hands: 1744 | fingers = [] 1745 | for polygon in polygons: 1746 | center = mat @ Vector(polygon.center) 1747 | vec = hand.tail - hand.head 1748 | m = hand.head.lerp(hand.tail, 0.0) 1749 | coeff = (center - m).dot(vec) 1750 | vec1 = center - hand.head 1751 | if vec.length_squared == 0.0 or vec1.length_squared == 0.0: 1752 | continue 1753 | angle = vec.angle(vec1) 1754 | if 0 < coeff and angle < self.hand_limit_angle: 1755 | fingers.append(polygon) 1756 | if len(fingers) != 0: 1757 | bone = armature.data.edit_bones.new("head") 1758 | self.create_bone(context, armature, bone, obj, hand, bones, 1759 | bone_type=bone_type, fingers=fingers) 1760 | 1761 | def gather_point(self, points, bound, m, dispersion, 1762 | parent=None, end=Vector((0, 0, 0))): 1763 | """ 1764 | Gathering points to gathers. 1765 | """ 1766 | gathers = [] 1767 | alreadys = [False for p in points] 1768 | for k, point in enumerate(points): 1769 | if alreadys[k]: 1770 | continue 1771 | hits = [k, ] 1772 | self._gather_point(k, points, bound, m, dispersion, 1773 | hits, parent=parent, end=end) 1774 | gathers.append(hits) 1775 | for hit in hits: 1776 | alreadys[hit] = True 1777 | return gathers 1778 | 1779 | def _gather_point(self, index, points, bound, m, 1780 | dispersion, hits, parent=None, end=Vector((0, 0, 0))): 1781 | """ 1782 | Recursion call of points' collision. 1783 | """ 1784 | point = points[index] 1785 | current_count = len(hits) 1786 | px, py, pz = point 1787 | le, ri, ba, fr, to, bo = bound 1788 | mx, my, mz = m 1789 | if parent is not None: 1790 | vec = (end - parent.head).normalized() 1791 | p_loc = Vector(( 1792 | self.lerp(ri, le, px * mx), 1793 | self.lerp(fr, ba, py * my), 1794 | self.lerp(bo, to, pz * mz))) 1795 | coeff = (p_loc - parent.head).dot(vec) 1796 | p_loc = (p_loc - parent.head) - coeff * vec 1797 | else: 1798 | p_loc = Vector(( 1799 | self.lerp(ri, le, px * mx), 1800 | self.lerp(fr, ba, py * my), 1801 | self.lerp(bo, to, pz * mz))) 1802 | neighbors = [] 1803 | for k, neighbor in enumerate(points): 1804 | if neighbor == point: 1805 | continue 1806 | nx, ny, nz = neighbor 1807 | if parent is not None: 1808 | vec = (end - parent.head).normalized() 1809 | n_loc = Vector(( 1810 | self.lerp(ri, le, nx * mx), 1811 | self.lerp(fr, ba, ny * my), 1812 | self.lerp(bo, to, nz * mz))) 1813 | coeff = (n_loc - parent.head).dot(vec) 1814 | n_loc = (n_loc - parent.head) - coeff * vec 1815 | else: 1816 | n_loc = Vector(( 1817 | self.lerp(ri, le, nx * mx), 1818 | self.lerp(fr, ba, ny * my), 1819 | self.lerp(bo, to, nz * mz))) 1820 | length = (p_loc - n_loc).length_squared 1821 | if length < dispersion: 1822 | neighbors.append(k) 1823 | for neighbor in neighbors: 1824 | already = False 1825 | for hit in hits: 1826 | already = already or hit == neighbor 1827 | if not already: 1828 | hits.append(neighbor) 1829 | if current_count == len(hits): 1830 | return True 1831 | for neighbor in neighbors: 1832 | g = self._gather_point(neighbor, points, bound, m, 1833 | dispersion, hits, parent=parent, end=end) 1834 | if g: 1835 | return True 1836 | 1837 | def create_head(self, bone, obj, chest): 1838 | b = self.bound_loc(obj) 1839 | p = b[BOUND_TOP] 1840 | n = b[BOUND_BOTTOM] 1841 | bone.head = Vector((obj.location.x, obj.location.y, n)) 1842 | bone.tail = Vector((obj.location.x, obj.location.y, p)) 1843 | bone.parent = chest 1844 | return bone 1845 | 1846 | 1847 | class LOVE2D3D_MT_menu(bpy.types.Menu): 1848 | bl_idname = "LOVE2D3D_MT_menu" 1849 | bl_label = "Love2D3D" 1850 | bl_space_type = 'VIEW_3D' 1851 | bl_region_type = 'UI' 1852 | bl_category = "Tool" 1853 | bl_context = "objectmode" 1854 | 1855 | def draw(self, context): 1856 | layout = self.layout 1857 | layout.separator() 1858 | layout.operator("image.open", icon="FILE_IMAGE", text="Open") 1859 | layout.separator() 1860 | layout.operator(LOVE2D3D_OT_createObject.bl_idname, 1861 | text="Create", icon="OUTLINER_OB_MESH") 1862 | layout.separator() 1863 | layout.operator(LOVE2D3D_OT_createArmature.bl_idname, 1864 | text="Create", icon="OUTLINER_OB_ARMATURE") 1865 | 1866 | 1867 | class LOVE2D3D_PT_panel(bpy.types.Panel): 1868 | bl_idname = "LOVE2D3D_PT_panel" 1869 | bl_label = "Love2D3D" 1870 | bl_space_type = 'VIEW_3D' 1871 | bl_region_type = 'UI' 1872 | bl_category = "View" 1873 | bl_context = "objectmode" 1874 | 1875 | def draw(self, context): 1876 | layout = self.layout 1877 | preview = context.window_manager.love2d3d.preview 1878 | icon = 'HIDE_OFF' if preview else 'HIDE_ON' 1879 | layout.operator(LOVE2D3D_OT_preview.bl_idname, 1880 | text="Preview", icon=icon) 1881 | row = layout.row() 1882 | row.prop_search(context.window_manager.love2d3d, 1883 | "image_front", context.blend_data, "images") 1884 | row.operator("image.open", icon="FILEBROWSER", text="") 1885 | row.operator("image.new", icon="DUPLICATE", text="") 1886 | layout.prop(context.window_manager.love2d3d, "view_align") 1887 | 1888 | 1889 | class Love2D3DProps(bpy.types.PropertyGroup): 1890 | image_front: StringProperty(name="Front", 1891 | description="Front image of mesh") 1892 | image_back: StringProperty(name="Back", 1893 | description="Back image of mesh") 1894 | rough: IntProperty(name="Rough", 1895 | description="Roughness of image", min=1, 1896 | default=8, subtype="PIXEL") 1897 | smooth: IntProperty(name="Smooth", 1898 | description="Smoothness of mesh", 1899 | min=1, default=30) 1900 | scale: FloatProperty(name="Scale", 1901 | description="Length per pixel", 1902 | unit="LENGTH", min=0.001, 1903 | default=0.01, precision=4) 1904 | depth_front: FloatProperty(name="Front", 1905 | description="Depth of front face", 1906 | unit="NONE", min=0, default=1) 1907 | depth_back: FloatProperty(name="Back", 1908 | description="Depth of back face", 1909 | unit="NONE", min=0, default=1) 1910 | fat: FloatProperty(name="Fat", 1911 | description="Fat of mesh", 1912 | default=0.2, min=0.0) 1913 | modifier: BoolProperty(name="Modifier", 1914 | description="Apply modifiers to object", 1915 | default=True) 1916 | threshold: FloatProperty(name="Threshold", 1917 | description="Threshold of background" + 1918 | "in image", 1919 | min=0.0, max=1.0, 1920 | default=0.0, subtype="FACTOR") 1921 | opacity: BoolProperty(name="Opacity", 1922 | description="Use Opacity for threshold") 1923 | view_align: BoolProperty(name="View align", 1924 | description="Use view align for mesh") 1925 | preview: BoolProperty(name="Preview", 1926 | description="Use preview for mesh now", 1927 | options={'HIDDEN'}) 1928 | decimate: BoolProperty(name="Decimate", 1929 | description="Use decimate modifier to object", 1930 | default=False) 1931 | decimate_ratio: FloatProperty(name="Ratio", 1932 | description="Decimate ratio", 1933 | default=0.2, min=0.0, 1934 | max=1.0, subtype="FACTOR") 1935 | shadeless: BoolProperty(name="Shadeless", 1936 | description="Use shadeless in" + 1937 | " object's material", 1938 | default=True) 1939 | armature_resolution: FloatProperty(name="Resolution", 1940 | description="Resolution of" + 1941 | "calculation", 1942 | min=1, default=6.0) 1943 | armature_finger_resolution: FloatProperty(name="Finger resolution", 1944 | description="Finger's" + 1945 | "resolution" + 1946 | "of calculation", 1947 | min=1, default=6.0) 1948 | armature_finger: BoolProperty(name="Finger", 1949 | description="Use finger in armature", 1950 | default=False) 1951 | 1952 | 1953 | classes = [ 1954 | LOVE2D3D_OT_preview, 1955 | LOVE2D3D_OT_createObject, 1956 | LOVE2D3D_OT_createArmature, 1957 | LOVE2D3D_PT_panel, 1958 | LOVE2D3D_MT_menu, 1959 | Love2D3DProps 1960 | ] 1961 | 1962 | 1963 | def draw_mesh_item(self, context): 1964 | layout = self.layout 1965 | layout.operator(LOVE2D3D_OT_createObject.bl_idname, 1966 | text="Love2D3D", icon="MESH_UVSPHERE") 1967 | 1968 | 1969 | def draw_armature_item(self, context): 1970 | layout = self.layout 1971 | layout.operator(LOVE2D3D_OT_createArmature.bl_idname, 1972 | text="Love2D3D", icon="OUTLINER_OB_ARMATURE") 1973 | 1974 | 1975 | def draw_item(self, context): 1976 | layout = self.layout 1977 | layout.separator() 1978 | layout.menu(LOVE2D3D_MT_menu.bl_idname, icon='PLUGIN') 1979 | 1980 | 1981 | def register(): 1982 | for c in classes: 1983 | bpy.utils.register_class(c) 1984 | bpy.types.VIEW3D_MT_mesh_add.append(draw_mesh_item) 1985 | bpy.types.VIEW3D_MT_armature_add.append(draw_armature_item) 1986 | bpy.types.WindowManager.love2d3d \ 1987 | = bpy.props.PointerProperty(type=Love2D3DProps) 1988 | 1989 | 1990 | def unregister(): 1991 | bpy.types.VIEW3D_MT_mesh_add.remove(draw_mesh_item) 1992 | bpy.types.VIEW3D_MT_armature_add.remove(draw_armature_item) 1993 | del bpy.types.WindowManager.love2d3d 1994 | for c in classes: 1995 | bpy.utils.unregister_class(c) 1996 | 1997 | 1998 | if __name__ == "__main__": 1999 | register() 2000 | -------------------------------------------------------------------------------- /readme.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
15 | 画像から3Dモデルを作り、スキニングもするBlenderのアドオンです。
16 |
Blender addon to add 3D object from 2D image or its armature.
17 |