diff --git a/.gitignore b/.gitignore index 85a3e05..5c40a74 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ -addons/.idea/ -addons/io_scene_gltf2/.idea/ -addons/io_sketchfab_plugin/io/* -releases/* \ No newline at end of file +.idea/ +releases/* +__pycache__/ +**.pyc +sketchfab/.cache diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index c98c9df..0000000 --- a/.gitmodules +++ /dev/null @@ -1,4 +0,0 @@ -[submodule "glTF-Blender-IO"] - path = glTF-Blender-IO - url = https://github.com/KhronosGroup/glTF-Blender-IO.git - branch = master diff --git a/.project b/.project deleted file mode 100644 index cb7e9ba..0000000 --- a/.project +++ /dev/null @@ -1,17 +0,0 @@ - - - glTF-Blender-IO - - - - - - org.python.pydev.PyDevBuilder - - - - - - org.python.pydev.pythonNature - - diff --git a/.pydevproject b/.pydevproject deleted file mode 100644 index 632174e..0000000 --- a/.pydevproject +++ /dev/null @@ -1,8 +0,0 @@ - - -Default -python 3.0 - -C:\Program Files\Blender Foundation\Blender\doc\python_api\pypredef - - diff --git a/README.md b/README.md index c36a452..badcc25 100644 --- a/README.md +++ b/README.md @@ -24,13 +24,11 @@ After installing the addon, two optional settings are available:

-**Note to Blender 2.79 OSX/Linux users:** The addon uses an embedded version of the SSL library. Do not hesitate to [report an issue](#report-an-issue) if you encounter any issue related to SSL. -
## Login -After installation, the addon is available in the 3D view in the tab 'Sketchfab' in the Properties panel (shortcut **N**) for Blender 2.80 and after, and in the Tools panel (shortcut **T**) for Blender 2.79. +After installation, the addon is available in the 3D view in the tab 'Sketchfab' in the Properties panel (shortcut **N**) for Blender 2.80+. Login (mandatory to import or export models) can be achieved through using the email and password associated to your Sketchfab account, or by using your API token, available in the settings of your [Sketchfab account](https://sketchfab.com/settings/password): @@ -113,7 +111,7 @@ There is no "quick fix" for those kinds of behaviours, which are actively being Here is a list of known issues on import, as well as some possible fixes. -Please note that the materials are being converted from Sketchfab to Cycles in Blender 2.79, and Eevee in Blender 2.80. If a material looks wrong, using the **Node editor** could therefore help you fixing possible issues. +Please note that the materials are being converted from Sketchfab to Eevee in Blender 2.80+. If a material looks wrong, using the **Node editor** could therefore help you fixing possible issues. #### Mesh not parented to armature diff --git a/addons/io_sketchfab_plugin/__init__.py b/__init__.py similarity index 96% rename from addons/io_sketchfab_plugin/__init__.py rename to __init__.py index 41ba98b..08d32a8 100644 --- a/addons/io_sketchfab_plugin/__init__.py +++ b/__init__.py @@ -34,50 +34,13 @@ IntProperty, PointerProperty) -from .io import * -from .io.imp.gltf2_io_gltf import * -from .blender.imp.gltf2_blender_gltf import * -from .blender.blender_version import Version - - -# Blender 2.79 has been shipped with openssl version 0.9.8 which uses a TLS protocol -# that is now blocked for security reasons on websites (github.com for example) -# In order to allow communication with github.com and other websites, the code will intend -# to use the updated openssl version distributed with the addon. -# Note: Blender 2.8 will have a more recent openssl version. This fix is only for 2.79 and olders -if bpy.app.version < (2, 80, 0) and not bpy.app.build_platform == b'Windows': - try: - sslib_path = None - if bpy.app.build_platform == b'Darwin': - sslib_path = os.path.join(os.path.dirname(__file__), 'dependencies/_ssl.cpython-35m-darwin.so') - elif bpy.app.build_platform == b'Linux': - sslib_path = os.path.join(os.path.dirname(__file__), '/io_sketchfab_plugin/_ssl.cpython-35m-x86_64-linux-gnu.so') - - import importlib.util - spec = importlib.util.spec_from_file_location("_ssl", sslib_path) - new_ssl = importlib.util.module_from_spec(spec) - spec.loader.exec_module(new_ssl) - - - from importlib import reload - import ssl - reload(ssl) - from requests.packages.urllib3.util import ssl_ - reload(ssl_) - print('SSL python module has been successfully overriden by Sketchfab addon') - print('It might fix other addons having the same refused TLS protocol issue') - except Exception as e: - print(e) - print("Failed to override SSL lib. The plugin will not be able to check for updates") - - bl_info = { 'name': 'Sketchfab Plugin', 'description': 'Browse and download free Sketchfab downloadable models', 'author': 'Sketchfab', 'license': 'APACHE2', 'deps': '', - 'version': (1, 5, 0), + 'version': (1, 6, 0), "blender": (2, 80, 0), 'location': 'View3D > Tools > Sketchfab', 'warning': '', @@ -105,8 +68,8 @@ class Config: SKETCHFAB_REPORT_URL = 'https://help.sketchfab.com/hc/en-us/requests/new?type=exporters&subject=Blender+Plugin' SKETCHFAB_URL = 'https://sketchfab.com' - DUMMY_CLIENTID = 'hGC7unF4BHyEB0s7Orz5E1mBd3LluEG0ILBiZvF9' - SKETCHFAB_OAUTH = SKETCHFAB_URL + '/oauth2/token/?grant_type=password&client_id=' + DUMMY_CLIENTID + CLIENTID = 'hGC7unF4BHyEB0s7Orz5E1mBd3LluEG0ILBiZvF9' + SKETCHFAB_OAUTH = SKETCHFAB_URL + '/oauth2/token/' SKETCHFAB_API = 'https://api.sketchfab.com' SKETCHFAB_SEARCH = SKETCHFAB_API + '/v3/search' SKETCHFAB_MODEL = SKETCHFAB_API + '/v3/models' @@ -244,12 +207,6 @@ def get_thumbnail_url(thumbnails_json): return min_thumbnail return best_thumbnail - def make_model_name(gltf_data): - if 'title' in gltf_data.asset.extras: - return gltf_data.asset.extras['title'] - - return 'GLTFModel' - def setup_plugin(): if not os.path.exists(Config.SKETCHFAB_THUMB_DIR): os.makedirs(Config.SKETCHFAB_THUMB_DIR) @@ -432,6 +389,7 @@ def __init__(self): self.next_results_url = None self.prev_results_url = None self.user_orgs = [] + self.user_has_orgs = False self.active_org = None self.use_org_profile = False @@ -473,6 +431,7 @@ def logout(self): #pprops.search_domain = "DEFAULT" self.user_orgs = [] + self.user_has_orgs = False self.active_org = None self.use_org_profile = False props.use_org_profile = False @@ -495,7 +454,7 @@ def parse_user_info(self, r, *args, **kargs): self.username = user_data['username'] self.display_name = user_data['displayName'] self.plan_type = user_data['account'] - requests.get(Config.SKETCHFAB_ME + "/orgs", headers=self.headers, hooks={'response': self.parse_orgs_info}) + requests.get(Config.SKETCHFAB_ME + "/orgs", headers=self.headers, hooks={'response': self.on_user_orgs_check}) else: print('\nInvalid access or API token\nYou can get your API token here:\nhttps://sketchfab.com/settings/password\n') set_login_status('ERROR', 'Failed to authenticate') @@ -504,6 +463,14 @@ def parse_user_info(self, r, *args, **kargs): self.api_token = '' self.headers = {} + def request_user_orgs(self): + if not self.active_org: + requests.get(Config.SKETCHFAB_ME + "/orgs", headers=self.headers, hooks={'response': self.parse_orgs_info}) + pass + + def on_user_orgs_check(self, r, *args, **kargs): + self.user_has_orgs = bool((r.status_code == 200) and len(r.json().get("results", []))) + def parse_orgs_info(self, r, *args, **kargs): """ Get and store information about user's orgs, and its orgs projects @@ -564,6 +531,7 @@ def parse_projects_info(r, *args, **kargs): # Set the first org as active if len(self.user_orgs): self.active_org = self.user_orgs[0] + self.user_has_orgs = True # Iterate on all orgs (not just the 24 first) if orgs_data["next"] is not None: @@ -716,9 +684,11 @@ def handle_download(self, r, *args, **kwargs): skfb_model.download_url = gltf['url'] skfb_model.time_url_requested = time.time() skfb_model.url_expires = gltf['expires'] - self.get_archive(gltf['url']) + self.get_archive(gltf['url'], skfb_model.title) + else: + ShowMessage("ERROR", "Cannot retrieve information for this model", "") - def get_archive(self, url): + def get_archive(self, url, title): if url is None: print('Url is None') return @@ -755,7 +725,7 @@ def get_archive(self, url): gltf_path, gltf_zip = unzip_archive(archive_path) if gltf_path: try: - import_model(gltf_path, uid) + import_model(gltf_path, uid, title) except Exception as e: import traceback print(traceback.format_exc()) @@ -826,6 +796,8 @@ def update_tr(self, context): def get_user_orgs(self, context): api = get_sketchfab_props().skfb_api + if not api.user_has_orgs: + api.request_user_orgs() return [(org["uid"], org["displayName"], "") for org in api.user_orgs] def get_org_projects(self, context): @@ -837,7 +809,7 @@ def get_available_search_domains(self, context): search_domains = [domain for domain in Config.SKETCHFAB_SEARCH_DOMAIN] - if len(api.user_orgs) and api.use_org_profile: + if api.user_has_orgs and api.use_org_profile: search_domains = [ ("ACTIVE_ORG", "Active Organization", api.active_org["displayName"], 0) ] @@ -856,6 +828,12 @@ def refresh_orgs(self, context): api = props.skfb_api api.use_org_profile = pprops.use_org_profile + + if api.user_has_orgs and not api.active_org : + bpy.context.window.cursor_set("WAIT") + api.request_user_orgs() + bpy.context.window.cursor_set("DEFAULT") + orgs = [org for org in api.user_orgs if org["uid"] == pprops.active_org] api.active_org = orgs[0] if len(orgs) else None @@ -875,7 +853,7 @@ def refresh_orgs(self, context): def get_sorting_options(self, context): api = get_sketchfab_props().skfb_api - if len(api.user_orgs) and api.use_org_profile: + if api.user_has_orgs and api.use_org_profile: return ( ('RELEVANCE', "Relevance", ""), ('RECENT', "Recent", "") @@ -1183,8 +1161,8 @@ def async_func(*args, **kwargs): return async_func -def import_model(gltf_path, uid): - bpy.ops.wm.import_modal('INVOKE_DEFAULT', gltf_path=gltf_path, uid=uid) +def import_model(gltf_path, uid, title): + bpy.ops.wm.import_modal('INVOKE_DEFAULT', gltf_path=gltf_path, uid=uid, title=title) def build_search_request(query, pbr, animated, staffpick, face_count, category, sort_by): @@ -1378,8 +1356,13 @@ def invoke(self, context, event): context.window_manager.modal_handler_add(self) login_props = get_sketchfab_login_props() if(login_props.use_mail): - url = '{}&username={}&password={}'.format(Config.SKETCHFAB_OAUTH, urllib.parse.quote_plus(login_props.email), urllib.parse.quote_plus(login_props.password)) - requests.post(url, hooks={'response': self.handle_mail_login}) + data = { + 'grant_type': 'password', + 'client_id': Config.CLIENTID, + 'username': login_props.email, + 'password': login_props.password, + } + requests.post(Config.SKETCHFAB_OAUTH, data=data, hooks={'response': self.handle_mail_login}) else: self.handle_token_login(login_props.api_token) except Exception as e: @@ -1397,6 +1380,7 @@ class ImportModalOperator(bpy.types.Operator): gltf_path : StringProperty() uid : StringProperty() + title: StringProperty() def execute(self, context): print('IMPORT') @@ -1404,17 +1388,13 @@ def execute(self, context): def modal(self, context, event): if bpy.context.scene.render.engine not in ["CYCLES", "BLENDER_EEVEE"]: - bpy.context.scene.render.engine = Version.ENGINE - gltf_importer = glTFImporter(self.gltf_path) - gltf_importer.read() - + bpy.context.scene.render.engine = "BLENDER_EEVEE" try: old_objects = [o.name for o in bpy.data.objects] # Get the current objects inorder to find the new node hierarchy - BlenderGlTF.create(gltf_importer) + bpy.ops.import_scene.gltf(filepath=self.gltf_path) set_import_status('') Utils.clean_downloaded_model_dir(self.uid) - root_name = Utils.make_model_name(gltf_importer.data) - Utils.clean_node_hierarchy([o for o in bpy.data.objects if o.name not in old_objects], root_name) + Utils.clean_node_hierarchy([o for o in bpy.data.objects if o.name not in old_objects], self.title) return {'FINISHED'} except Exception: import traceback @@ -1422,8 +1402,6 @@ def modal(self, context, event): set_import_status('') return {'FINISHED'} - return {'RUNNING_MODAL'} - def invoke(self, context, event): context.window_manager.modal_handler_add(self) set_import_status('Importing...') @@ -1524,7 +1502,7 @@ def draw(self, context): self.layout.enabled = get_plugin_enabled() and api.is_user_logged() - if not api.user_orgs: + if not api.user_has_orgs: self.layout.label(text="You are not part of an organization", icon='INFO') self.layout.operator("wm.url_open", text='Learn about Sketchfab for Teams').url = "https://sketchfab.com/features/teams" else: @@ -2134,7 +2112,7 @@ def upload(filepath, filename): else: # Org or not - if len(api.user_orgs) and api.use_org_profile: + if api.user_has_orgs and api.use_org_profile: uploadUrl = "%s/%s/models" % (Config.SKETCHFAB_ORGS, api.active_org["uid"]) _data["orgProject"] = props.active_project else: @@ -2395,7 +2373,7 @@ def check_plugin_version(request, *args, **kwargs): def register(): sketchfab_icon = bpy.utils.previews.new() - icons_dir = os.path.join(os.path.dirname(__file__), "resources") + icons_dir = os.path.dirname(__file__) sketchfab_icon.load("skfb", os.path.join(icons_dir, "logo.png"), 'IMAGE') sketchfab_icon.load("0", os.path.join(icons_dir, "placeholder.png"), 'IMAGE') diff --git a/addons/io_sketchfab_plugin/.gitignore b/addons/io_sketchfab_plugin/.gitignore deleted file mode 100644 index ffc9e23..0000000 --- a/addons/io_sketchfab_plugin/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -__pycache__/ -blender/__pycache__/ -blender/common/__pycache__/ -blender/export/__pycache__/ -blender/import/__pycache__/ -io/common/__pycache__/ -io/export/__pycache__/ -io/import/__pycache__/ -**.pyc -sketchfab/.cache \ No newline at end of file diff --git a/addons/io_sketchfab_plugin/blender/README.md b/addons/io_sketchfab_plugin/blender/README.md deleted file mode 100644 index ca0ec8a..0000000 --- a/addons/io_sketchfab_plugin/blender/README.md +++ /dev/null @@ -1,5 +0,0 @@ -Directories *com* and *imp* in this directory are copied - with very minor changes - from their respective counterparts on [Khronos repository](https://github.com/KhronosGroup/glTF-Blender-IO/tree/master/addons/io_scene_gltf2/blender), set at [commit 709630548cdc184af6ea50b2ff3ddc5450bc0af3](https://github.com/KhronosGroup/glTF-Blender-IO/commit/709630548cdc184af6ea50b2ff3ddc5450bc0af3) (Blender 2.93). - -Licensed under [Apache Version 2](https://www.apache.org/licenses/LICENSE-2.0), see [LICENSE.txt](https://github.com/sketchfab/blender-plugin/blob/master/LICENSE.txt). - - diff --git a/addons/io_sketchfab_plugin/blender/blender_version.py b/addons/io_sketchfab_plugin/blender/blender_version.py deleted file mode 100644 index 0306ffc..0000000 --- a/addons/io_sketchfab_plugin/blender/blender_version.py +++ /dev/null @@ -1,78 +0,0 @@ -import bpy - -class Version: - """Adjusts functions according to the differences between 2.79 and 2.8""" - - #Render engine - ENGINE = "CYCLES" if bpy.app.version < (2, 80, 0) else "BLENDER_EEVEE" - - # Selection / Deselection - def get_selected(obj): - if bpy.app.version < (2, 80, 0): - return obj.select - else: - return obj.select_get() - def select(obj): - if bpy.app.version < (2, 80, 0): - obj.select = True - else: - obj.select_set(True) - def deselect(obj): - if bpy.app.version < (2, 80, 0): - obj.select = False - else: - obj.select_set(False) - - # Visibility - def get_visible(obj): - if bpy.app.version < (2, 80, 0): - return (not obj.hide) - else: - return obj.visible_get() - def set_visible(obj, visible): - if bpy.app.version < (2, 80, 0): - obj.hide = not visible - else: - obj.hide_set(not visible) - - # Object linking - def link(scene, obj): - if bpy.app.version < (2, 80, 0): - bpy.data.scenes[scene].objects.link(obj) - else: - bpy.data.scenes[scene].collection.objects.link(obj) - - # Active object - def get_active_object(): - if bpy.app.version < (2, 80, 0): - return bpy.context.scene.objects.active - else: - return bpy.context.view_layer.objects.active - def set_active_object(obj): - if bpy.app.version < (2, 80, 0): - bpy.context.scene.objects.active = obj - #self.select(obj) - else: - bpy.context.view_layer.objects.active = obj - - # Matrix multiplication - def mat_mult(A, B): - if bpy.app.version < (2, 80, 0): - return A * B - else: - return A @ B - - # Setting colorspace - def set_colorspace(texture): - if bpy.app.version < (2, 80, 0): - texture.color_space = 'NONE' - else: - if texture.image: - texture.image.colorspace_settings.is_data = True - - # Setting the active scene - def set_scene(scene): - if bpy.app.version < (2, 80, 0): - bpy.context.screen.scene = scene - else: - bpy.context.window.scene = scene diff --git a/addons/io_sketchfab_plugin/blender/com/README.md b/addons/io_sketchfab_plugin/blender/com/README.md deleted file mode 100644 index adb72dc..0000000 --- a/addons/io_sketchfab_plugin/blender/com/README.md +++ /dev/null @@ -1 +0,0 @@ -Shared Blender importer and exporter code. \ No newline at end of file diff --git a/addons/io_sketchfab_plugin/blender/com/gltf2_blender_conversion.py b/addons/io_sketchfab_plugin/blender/com/gltf2_blender_conversion.py deleted file mode 100644 index 7f4e69f..0000000 --- a/addons/io_sketchfab_plugin/blender/com/gltf2_blender_conversion.py +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright 2018-2021 The glTF-Blender-IO authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from math import sin, cos - -def texture_transform_blender_to_gltf(mapping_transform): - """ - Converts the offset/rotation/scale from a Mapping node applied in Blender's - UV space to the equivalent KHR_texture_transform. - """ - offset = mapping_transform.get('offset', [0, 0]) - rotation = mapping_transform.get('rotation', 0) - scale = mapping_transform.get('scale', [1, 1]) - return { - 'offset': [ - offset[0] - scale[1] * sin(rotation), - 1 - offset[1] - scale[1] * cos(rotation), - ], - 'rotation': rotation, - 'scale': [scale[0], scale[1]], - } - -def texture_transform_gltf_to_blender(texture_transform): - """ - Converts a KHR_texture_transform into the equivalent offset/rotation/scale - for a Mapping node applied in Blender's UV space. - """ - offset = texture_transform.get('offset', [0, 0]) - rotation = texture_transform.get('rotation', 0) - scale = texture_transform.get('scale', [1, 1]) - return { - 'offset': [ - offset[0] + scale[1] * sin(rotation), - 1 - offset[1] - scale[1] * cos(rotation), - ], - 'rotation': rotation, - 'scale': [scale[0], scale[1]], - } - -def get_target(property): - return { - "delta_location": "translation", - "delta_rotation_euler": "rotation", - "location": "translation", - "rotation_axis_angle": "rotation", - "rotation_euler": "rotation", - "rotation_quaternion": "rotation", - "scale": "scale", - "value": "weights" - }.get(property) diff --git a/addons/io_sketchfab_plugin/blender/com/gltf2_blender_data_path.py b/addons/io_sketchfab_plugin/blender/com/gltf2_blender_data_path.py deleted file mode 100644 index 64e0fea..0000000 --- a/addons/io_sketchfab_plugin/blender/com/gltf2_blender_data_path.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright 2018-2021 The glTF-Blender-IO authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -def get_target_property_name(data_path: str) -> str: - """Retrieve target property.""" - return data_path.rsplit('.', 1)[-1] - - -def get_target_object_path(data_path: str) -> str: - """Retrieve target object data path without property""" - path_split = data_path.rsplit('.', 1) - self_targeting = len(path_split) < 2 - if self_targeting: - return "" - return path_split[0] - -def get_rotation_modes(target_property: str) -> str: - """Retrieve rotation modes based on target_property""" - if target_property == "rotation_euler": - return True, False, ["XYZ", "XZY", "YXZ", "YZX", "ZXY", "ZYX"] - elif target_property == "delta_rotation_euler": - return True, True, ["XYZ", "XZY", "YXZ", "YZX", "ZXY", "ZYX"] - elif target_property == "rotation_quaternion": - return True, False, ["QUATERNION"] - elif target_property == "delta_rotation_quaternion": - return True, True, ["QUATERNION"] - elif target_property in ["rotation_axis_angle"]: - return True, False, ["AXIS_ANGLE"] - else: - return False, False, [] diff --git a/addons/io_sketchfab_plugin/blender/com/gltf2_blender_extras.py b/addons/io_sketchfab_plugin/blender/com/gltf2_blender_extras.py deleted file mode 100755 index 6c93e7b..0000000 --- a/addons/io_sketchfab_plugin/blender/com/gltf2_blender_extras.py +++ /dev/null @@ -1,97 +0,0 @@ -# Copyright 2018-2021 The glTF-Blender-IO authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -import bpy -from .gltf2_blender_json import is_json_convertible - - -# Custom properties, which are in most cases present and should not be imported/exported. -BLACK_LIST = ['cycles', 'cycles_visibility', 'cycles_curves', 'glTF2ExportSettings'] - - -def generate_extras(blender_element): - """Filter and create a custom property, which is stored in the glTF extra field.""" - if not blender_element: - return None - - extras = {} - - for custom_property in blender_element.keys(): - if custom_property in BLACK_LIST: - continue - - value = __to_json_compatible(blender_element[custom_property]) - - if value is not None: - extras[custom_property] = value - - if not extras: - return None - - return extras - - -def __to_json_compatible(value): - """Make a value (usually a custom property) compatible with json""" - - if isinstance(value, bpy.types.ID): - return value - - elif isinstance(value, str): - return value - - elif isinstance(value, (int, float)): - return value - - # for list classes - elif isinstance(value, list): - value = list(value) - # make sure contents are json-compatible too - for index in range(len(value)): - value[index] = __to_json_compatible(value[index]) - return value - - # for IDPropertyArray classes - elif hasattr(value, "to_list"): - value = value.to_list() - return value - - elif hasattr(value, "to_dict"): - value = value.to_dict() - if is_json_convertible(value): - return value - - return None - - -def set_extras(blender_element, extras, exclude=[]): - """Copy extras onto a Blender object.""" - if not extras or not isinstance(extras, dict): - return - - for custom_property, value in extras.items(): - if custom_property in BLACK_LIST: - continue - if custom_property in exclude: - continue - - try: - blender_element[custom_property] = value - except Exception: - # Try to convert to string - try: - blender_element[custom_property] = str(value) - except Exception: - print('Error setting property %s to value of type %s' % (custom_property, type(value))) diff --git a/addons/io_sketchfab_plugin/blender/com/gltf2_blender_json.py b/addons/io_sketchfab_plugin/blender/com/gltf2_blender_json.py deleted file mode 100644 index 9b4ea75..0000000 --- a/addons/io_sketchfab_plugin/blender/com/gltf2_blender_json.py +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright 2018-2021 The glTF-Blender-IO authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import json -import bpy - - -class BlenderJSONEncoder(json.JSONEncoder): - """Blender JSON Encoder.""" - - def default(self, obj): - if isinstance(obj, bpy.types.ID): - return dict( - name=obj.name, - type=obj.__class__.__name__ - ) - return super(BlenderJSONEncoder, self).default(obj) - - -def is_json_convertible(data): - """Test, if a data set can be expressed as JSON.""" - try: - json.dumps(data, cls=BlenderJSONEncoder) - return True - except: - return False diff --git a/addons/io_sketchfab_plugin/blender/com/gltf2_blender_material_helpers.py b/addons/io_sketchfab_plugin/blender/com/gltf2_blender_material_helpers.py deleted file mode 100644 index e5499e3..0000000 --- a/addons/io_sketchfab_plugin/blender/com/gltf2_blender_material_helpers.py +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright 2018-2021 The glTF-Blender-IO authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -def get_gltf_node_name(): - return "glTF Settings" diff --git a/addons/io_sketchfab_plugin/blender/com/gltf2_blender_math.py b/addons/io_sketchfab_plugin/blender/com/gltf2_blender_math.py deleted file mode 100644 index 31349dd..0000000 --- a/addons/io_sketchfab_plugin/blender/com/gltf2_blender_math.py +++ /dev/null @@ -1,210 +0,0 @@ -# Copyright 2018-2021 The glTF-Blender-IO authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import typing -import math -from mathutils import Matrix, Vector, Quaternion, Euler - -from ...blender.com.gltf2_blender_data_path import get_target_property_name - - -def list_to_mathutils(values: typing.List[float], data_path: str) -> typing.Union[Vector, Quaternion, Euler]: - """Transform a list to blender py object.""" - target = get_target_property_name(data_path) - - if target == 'delta_location': - return Vector(values) # TODO Should be Vector(values) - Vector(something)? - elif target == 'delta_rotation_euler': - return Euler(values).to_quaternion() # TODO Should be Euler(values).to_quaternion() @ something? - elif target == 'location': - return Vector(values) - elif target == 'rotation_axis_angle': - angle = values[0] - axis = values[1:] - return Quaternion(axis, math.radians(angle)) - elif target == 'rotation_euler': - return Euler(values).to_quaternion() - elif target == 'rotation_quaternion': - return Quaternion(values) - elif target == 'scale': - return Vector(values) - elif target == 'value': - return Vector(values) - - return values - - -def mathutils_to_gltf(x: typing.Union[Vector, Quaternion]) -> typing.List[float]: - """Transform a py object to glTF list.""" - if isinstance(x, Vector): - return list(x) - if isinstance(x, Quaternion): - # Blender has w-first quaternion notation - return [x[1], x[2], x[3], x[0]] - else: - return list(x) - - -def to_yup() -> Matrix: - """Transform to Yup.""" - return Matrix( - ((1.0, 0.0, 0.0, 0.0), - (0.0, 0.0, 1.0, 0.0), - (0.0, -1.0, 0.0, 0.0), - (0.0, 0.0, 0.0, 1.0)) - ) - - -to_zup = to_yup - - -def swizzle_yup(v: typing.Union[Vector, Quaternion], data_path: str) -> typing.Union[Vector, Quaternion]: - """Manage Yup.""" - target = get_target_property_name(data_path) - swizzle_func = { - "delta_location": swizzle_yup_location, - "delta_rotation_euler": swizzle_yup_rotation, - "location": swizzle_yup_location, - "rotation_axis_angle": swizzle_yup_rotation, - "rotation_euler": swizzle_yup_rotation, - "rotation_quaternion": swizzle_yup_rotation, - "scale": swizzle_yup_scale, - "value": swizzle_yup_value - }.get(target) - - if swizzle_func is None: - raise RuntimeError("Cannot transform values at {}".format(data_path)) - - return swizzle_func(v) - - -def swizzle_yup_location(loc: Vector) -> Vector: - """Manage Yup location.""" - return Vector((loc[0], loc[2], -loc[1])) - - -def swizzle_yup_rotation(rot: Quaternion) -> Quaternion: - """Manage Yup rotation.""" - return Quaternion((rot[0], rot[1], rot[3], -rot[2])) - - -def swizzle_yup_scale(scale: Vector) -> Vector: - """Manage Yup scale.""" - return Vector((scale[0], scale[2], scale[1])) - - -def swizzle_yup_value(value: typing.Any) -> typing.Any: - """Manage Yup value.""" - return value - - -def transform(v: typing.Union[Vector, Quaternion], data_path: str, transform: Matrix = Matrix.Identity(4)) -> typing \ - .Union[Vector, Quaternion]: - """Manage transformations.""" - target = get_target_property_name(data_path) - transform_func = { - "delta_location": transform_location, - "delta_rotation_euler": transform_rotation, - "location": transform_location, - "rotation_axis_angle": transform_rotation, - "rotation_euler": transform_rotation, - "rotation_quaternion": transform_rotation, - "scale": transform_scale, - "value": transform_value - }.get(target) - - if transform_func is None: - raise RuntimeError("Cannot transform values at {}".format(data_path)) - - return transform_func(v, transform) - - -def transform_location(location: Vector, transform: Matrix = Matrix.Identity(4)) -> Vector: - """Transform location.""" - m = Matrix.Translation(location) - m = transform @ m - return m.to_translation() - - -def transform_rotation(rotation: Quaternion, transform: Matrix = Matrix.Identity(4)) -> Quaternion: - """Transform rotation.""" - rotation.normalize() - m = rotation.to_matrix().to_4x4() - m = transform @ m - return m.to_quaternion() - - -def transform_scale(scale: Vector, transform: Matrix = Matrix.Identity(4)) -> Vector: - """Transform scale.""" - m = Matrix.Identity(4) - m[0][0] = scale.x - m[1][1] = scale.y - m[2][2] = scale.z - m = transform @ m - - return m.to_scale() - - -def transform_value(value: Vector, _: Matrix = Matrix.Identity(4)) -> Vector: - """Transform value.""" - return value - - -def round_if_near(value: float, target: float) -> float: - """If value is very close to target, round to target.""" - return value if abs(value - target) > 2.0e-6 else target - - -def scale_rot_swap_matrix(rot): - """Returns a matrix m st. Scale[s] Rot[rot] = Rot[rot] Scale[m s]. - If rot.to_matrix() is a signed permutation matrix, works for any s. - Otherwise works only if s is a uniform scaling. - """ - m = nearby_signed_perm_matrix(rot) # snap to signed perm matrix - m.transpose() # invert permutation - for i in range(3): - for j in range(3): - m[i][j] = abs(m[i][j]) # discard sign - return m - - -def nearby_signed_perm_matrix(rot): - """Returns a signed permutation matrix close to rot.to_matrix(). - (A signed permutation matrix is like a permutation matrix, except - the non-zero entries can be ±1.) - """ - m = rot.to_matrix() - x, y, z = m[0], m[1], m[2] - - # Set the largest entry in the first row to ±1 - a, b, c = abs(x[0]), abs(x[1]), abs(x[2]) - i = 0 if a >= b and a >= c else 1 if b >= c else 2 - x[i] = 1 if x[i] > 0 else -1 - x[(i+1) % 3] = 0 - x[(i+2) % 3] = 0 - - # Same for second row: only two columns to consider now. - a, b = abs(y[(i+1) % 3]), abs(y[(i+2) % 3]) - j = (i+1) % 3 if a >= b else (i+2) % 3 - y[j] = 1 if y[j] > 0 else -1 - y[(j+1) % 3] = 0 - y[(j+2) % 3] = 0 - - # Same for third row: only one column left - k = (0 + 1 + 2) - i - j - z[k] = 1 if z[k] > 0 else -1 - z[(k+1) % 3] = 0 - z[(k+2) % 3] = 0 - - return m diff --git a/addons/io_sketchfab_plugin/blender/imp/README.md b/addons/io_sketchfab_plugin/blender/imp/README.md deleted file mode 100644 index c77ea4e..0000000 --- a/addons/io_sketchfab_plugin/blender/imp/README.md +++ /dev/null @@ -1 +0,0 @@ -Create Blender meshes, materials etc. from Python classes. \ No newline at end of file diff --git a/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_KHR_materials_clearcoat.py b/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_KHR_materials_clearcoat.py deleted file mode 100644 index 4629f3b..0000000 --- a/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_KHR_materials_clearcoat.py +++ /dev/null @@ -1,167 +0,0 @@ -# Copyright 2018-2021 The glTF-Blender-IO authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from ...io.com.gltf2_io import TextureInfo, MaterialNormalTextureInfoClass -from .gltf2_blender_texture import texture - - -# [Texture] => [Separate R] => [Clearcoat Factor] => -def clearcoat(mh, location, clearcoat_socket): - x, y = location - try: - ext = mh.pymat.extensions['KHR_materials_clearcoat'] - except Exception: - return - clearcoat_factor = ext.get('clearcoatFactor', 0) - tex_info = ext.get('clearcoatTexture') - if tex_info is not None: - tex_info = TextureInfo.from_dict(tex_info) - - if clearcoat_socket is None: - return - - if tex_info is None: - clearcoat_socket.default_value = clearcoat_factor - return - - # Mix clearcoat factor - if clearcoat_factor != 1: - node = mh.node_tree.nodes.new('ShaderNodeMath') - node.label = 'Clearcoat Factor' - node.location = x - 140, y - node.operation = 'MULTIPLY' - # Outputs - mh.node_tree.links.new(clearcoat_socket, node.outputs[0]) - # Inputs - clearcoat_socket = node.inputs[0] - node.inputs[1].default_value = clearcoat_factor - - x -= 200 - - # Separate RGB - node = mh.node_tree.nodes.new('ShaderNodeSeparateRGB') - node.location = x - 150, y - 75 - # Outputs - mh.node_tree.links.new(clearcoat_socket, node.outputs['R']) - # Inputs - clearcoat_socket = node.inputs[0] - - x -= 200 - - texture( - mh, - tex_info=tex_info, - label='CLEARCOAT', - location=(x, y), - is_data=True, - color_socket=clearcoat_socket, - ) - - -# [Texture] => [Separate G] => [Roughness Factor] => -def clearcoat_roughness(mh, location, roughness_socket): - x, y = location - try: - ext = mh.pymat.extensions['KHR_materials_clearcoat'] - except Exception: - return - roughness_factor = ext.get('clearcoatRoughnessFactor', 0) - tex_info = ext.get('clearcoatRoughnessTexture') - if tex_info is not None: - tex_info = TextureInfo.from_dict(tex_info) - - if roughness_socket is None: - return - - if tex_info is None: - roughness_socket.default_value = roughness_factor - return - - # Mix roughness factor - if roughness_factor != 1: - node = mh.node_tree.nodes.new('ShaderNodeMath') - node.label = 'Clearcoat Roughness Factor' - node.location = x - 140, y - node.operation = 'MULTIPLY' - # Outputs - mh.node_tree.links.new(roughness_socket, node.outputs[0]) - # Inputs - roughness_socket = node.inputs[0] - node.inputs[1].default_value = roughness_factor - - x -= 200 - - # Separate RGB (roughness is in G) - node = mh.node_tree.nodes.new('ShaderNodeSeparateRGB') - node.location = x - 150, y - 75 - # Outputs - mh.node_tree.links.new(roughness_socket, node.outputs['G']) - # Inputs - color_socket = node.inputs[0] - - x -= 200 - - texture( - mh, - tex_info=tex_info, - label='CLEARCOAT ROUGHNESS', - location=(x, y), - is_data=True, - color_socket=color_socket, - ) - - -# [Texture] => [Normal Map] => -def clearcoat_normal(mh, location, normal_socket): - x,y = location - try: - ext = mh.pymat.extensions['KHR_materials_clearcoat'] - except Exception: - return - tex_info = ext.get('clearcoatNormalTexture') - if tex_info is not None: - tex_info = MaterialNormalTextureInfoClass.from_dict(tex_info) - - if tex_info is None: - return - - # Normal map - node = mh.node_tree.nodes.new('ShaderNodeNormalMap') - node.location = x - 150, y - 40 - # Set UVMap - uv_idx = tex_info.tex_coord or 0 - try: - uv_idx = tex_info.extensions['KHR_texture_transform']['texCoord'] - except Exception: - pass - node.uv_map = 'UVMap' if uv_idx == 0 else 'UVMap.%03d' % uv_idx - # Set strength - scale = tex_info.scale - scale = scale if scale is not None else 1 - node.inputs['Strength'].default_value = scale - # Outputs - mh.node_tree.links.new(normal_socket, node.outputs['Normal']) - # Inputs - color_socket = node.inputs['Color'] - - x -= 200 - - texture( - mh, - tex_info=tex_info, - label='CLEARCOAT NORMAL', - location=(x, y), - is_data=True, - color_socket=color_socket, - ) diff --git a/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_KHR_materials_pbrSpecularGlossiness.py b/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_KHR_materials_pbrSpecularGlossiness.py deleted file mode 100644 index ed3aa78..0000000 --- a/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_KHR_materials_pbrSpecularGlossiness.py +++ /dev/null @@ -1,164 +0,0 @@ -# Copyright 2018-2021 The glTF-Blender-IO authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from ...io.com.gltf2_io import TextureInfo -from .gltf2_blender_pbrMetallicRoughness import \ - base_color, emission, normal, occlusion, make_output_nodes, make_settings_node -from .gltf2_blender_texture import texture - - -def pbr_specular_glossiness(mh): - """Creates node tree for pbrSpecularGlossiness materials.""" - # This does option #1 from - # https://github.com/KhronosGroup/glTF-Blender-IO/issues/303 - - # Sum a Glossy and Diffuse Shader - glossy_node = mh.node_tree.nodes.new('ShaderNodeBsdfGlossy') - diffuse_node = mh.node_tree.nodes.new('ShaderNodeBsdfDiffuse') - add_node = mh.node_tree.nodes.new('ShaderNodeAddShader') - glossy_node.location = 10, 220 - diffuse_node.location = 10, 0 - add_node.location = 230, 100 - mh.node_tree.links.new(add_node.inputs[0], glossy_node.outputs[0]) - mh.node_tree.links.new(add_node.inputs[1], diffuse_node.outputs[0]) - - emission_socket, alpha_socket = make_output_nodes( - mh, - location=(370, 250), - shader_socket=add_node.outputs[0], - make_emission_socket=mh.needs_emissive(), - make_alpha_socket=not mh.is_opaque(), - ) - - emission( - mh, - location=(-200, 860), - color_socket=emission_socket, - ) - - base_color( - mh, - is_diffuse=True, - location=(-200, 380), - color_socket=diffuse_node.inputs['Color'], - alpha_socket=alpha_socket, - ) - - specular_glossiness( - mh, - location=(-200, -100), - specular_socket=glossy_node.inputs['Color'], - roughness_socket=glossy_node.inputs['Roughness'], - ) - copy_socket( - mh, - copy_from=glossy_node.inputs['Roughness'], - copy_to=diffuse_node.inputs['Roughness'], - ) - - normal( - mh, - location=(-200, -580), - normal_socket=glossy_node.inputs['Normal'], - ) - copy_socket( - mh, - copy_from=glossy_node.inputs['Normal'], - copy_to=diffuse_node.inputs['Normal'], - ) - - if mh.pymat.occlusion_texture is not None: - node = make_settings_node(mh) - node.location = (610, -1060) - occlusion( - mh, - location=(510, -970), - occlusion_socket=node.inputs['Occlusion'], - ) - - -# [Texture] => [Spec/Gloss Factor] => [Gloss to Rough] => -def specular_glossiness(mh, location, specular_socket, roughness_socket): - x, y = location - spec_factor = mh.pymat.extensions \ - ['KHR_materials_pbrSpecularGlossiness'] \ - .get('specularFactor', [1, 1, 1]) - gloss_factor = mh.pymat.extensions \ - ['KHR_materials_pbrSpecularGlossiness'] \ - .get('glossinessFactor', 1) - spec_gloss_texture = mh.pymat.extensions \ - ['KHR_materials_pbrSpecularGlossiness'] \ - .get('specularGlossinessTexture', None) - if spec_gloss_texture is not None: - spec_gloss_texture = TextureInfo.from_dict(spec_gloss_texture) - - if spec_gloss_texture is None: - specular_socket.default_value = spec_factor + [1] - roughness_socket.default_value = 1 - gloss_factor - return - - # (1 - x) converts glossiness to roughness - node = mh.node_tree.nodes.new('ShaderNodeInvert') - node.label = 'Invert (Gloss to Rough)' - node.location = x - 140, y - 75 - # Outputs - mh.node_tree.links.new(roughness_socket, node.outputs[0]) - # Inputs - node.inputs['Fac'].default_value = 1 - glossiness_socket = node.inputs['Color'] - - x -= 250 - - # Mix in spec/gloss factor - if spec_factor != [1, 1, 1] or gloss_factor != 1: - if spec_factor != [1, 1, 1]: - node = mh.node_tree.nodes.new('ShaderNodeMixRGB') - node.label = 'Specular Factor' - node.location = x - 140, y - node.blend_type = 'MULTIPLY' - # Outputs - mh.node_tree.links.new(specular_socket, node.outputs[0]) - # Inputs - node.inputs['Fac'].default_value = 1.0 - specular_socket = node.inputs['Color1'] - node.inputs['Color2'].default_value = spec_factor + [1] - - if gloss_factor != 1: - node = mh.node_tree.nodes.new('ShaderNodeMath') - node.label = 'Glossiness Factor' - node.location = x - 140, y - 200 - node.operation = 'MULTIPLY' - # Outputs - mh.node_tree.links.new(glossiness_socket, node.outputs[0]) - # Inputs - glossiness_socket = node.inputs[0] - node.inputs[1].default_value = gloss_factor - - x -= 200 - - texture( - mh, - tex_info=spec_gloss_texture, - label='SPECULAR GLOSSINESS', - location=(x, y), - color_socket=specular_socket, - alpha_socket=glossiness_socket, - ) - - -def copy_socket(mh, copy_from, copy_to): - """Copy the links/default value from one socket to another.""" - copy_to.default_value = copy_from.default_value - for link in copy_from.links: - mh.node_tree.links.new(copy_to, link.from_socket) diff --git a/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_KHR_materials_unlit.py b/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_KHR_materials_unlit.py deleted file mode 100644 index 5b94936..0000000 --- a/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_KHR_materials_unlit.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright 2018-2021 The glTF-Blender-IO authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from .gltf2_blender_pbrMetallicRoughness import base_color, make_output_nodes - - -def unlit(mh): - """Creates node tree for unlit materials.""" - # Emission node for the base color - emission_node = mh.node_tree.nodes.new('ShaderNodeEmission') - emission_node.location = 10, 126 - - # Lightpath trick: makes Emission visible only to camera rays. - # [Is Camera Ray] => [Mix] => - # [Transparent] => [ ] - # [Emission] => [ ] - lightpath_node = mh.node_tree.nodes.new('ShaderNodeLightPath') - transparent_node = mh.node_tree.nodes.new('ShaderNodeBsdfTransparent') - mix_node = mh.node_tree.nodes.new('ShaderNodeMixShader') - lightpath_node.location = 10, 600 - transparent_node.location = 10, 240 - mix_node.location = 260, 320 - mh.node_tree.links.new(mix_node.inputs['Fac'], lightpath_node.outputs['Is Camera Ray']) - mh.node_tree.links.new(mix_node.inputs[1], transparent_node.outputs[0]) - mh.node_tree.links.new(mix_node.inputs[2], emission_node.outputs[0]) - - _emission_socket, alpha_socket = make_output_nodes( - mh, - location=(420, 280) if mh.is_opaque() else (150, 130), - shader_socket=mix_node.outputs[0], - make_emission_socket=False, - make_alpha_socket=not mh.is_opaque(), - ) - - base_color( - mh, - location=(-200, 380), - color_socket=emission_node.inputs['Color'], - alpha_socket=alpha_socket, - ) diff --git a/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_animation.py b/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_animation.py deleted file mode 100644 index ba030fa..0000000 --- a/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_animation.py +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright 2018-2021 The glTF-Blender-IO authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from .gltf2_blender_animation_node import BlenderNodeAnim -from .gltf2_blender_animation_weight import BlenderWeightAnim -from .gltf2_blender_animation_utils import simulate_stash, restore_animation_on_object -from .gltf2_blender_vnode import VNode - - -class BlenderAnimation(): - """Dispatch Animation to node or morph weights animation.""" - def __new__(cls, *args, **kwargs): - raise RuntimeError("%s should not be instantiated" % cls) - - @staticmethod - def anim(gltf, anim_idx): - """Create actions/tracks for one animation.""" - # Caches the action for each object (keyed by object name) - gltf.action_cache = {} - # Things we need to stash when we're done. - gltf.needs_stash = [] - - for vnode_id in gltf.vnodes: - if isinstance(vnode_id, int): - BlenderNodeAnim.anim(gltf, anim_idx, vnode_id) - BlenderWeightAnim.anim(gltf, anim_idx, vnode_id) - - # Push all actions onto NLA tracks with this animation's name - track_name = gltf.data.animations[anim_idx].track_name - for (obj, action) in gltf.needs_stash: - simulate_stash(obj, track_name, action) - - @staticmethod - def restore_animation(gltf, animation_name): - """Restores the actions for an animation by its track name.""" - for vnode_id in gltf.vnodes: - vnode = gltf.vnodes[vnode_id] - if vnode.type == VNode.Bone: - obj = gltf.vnodes[vnode.bone_arma].blender_object - elif vnode.type == VNode.Object: - obj = vnode.blender_object - else: - continue - - restore_animation_on_object(obj, animation_name) - if obj.data and hasattr(obj.data, 'shape_keys'): - restore_animation_on_object(obj.data.shape_keys, animation_name) diff --git a/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_animation_node.py b/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_animation_node.py deleted file mode 100644 index ac2c732..0000000 --- a/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_animation_node.py +++ /dev/null @@ -1,178 +0,0 @@ -# Copyright 2018-2021 The glTF-Blender-IO authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import bpy -from mathutils import Vector - -from ...io.imp.gltf2_io_binary import BinaryData -from .gltf2_blender_animation_utils import make_fcurve -from .gltf2_blender_vnode import VNode -from ...io.imp.gltf2_io_user_extensions import import_user_extensions - - -class BlenderNodeAnim(): - """Blender Object Animation.""" - def __new__(cls, *args, **kwargs): - raise RuntimeError("%s should not be instantiated" % cls) - - @staticmethod - def anim(gltf, anim_idx, node_idx): - """Manage animation targeting a node's TRS.""" - animation = gltf.data.animations[anim_idx] - node = gltf.data.nodes[node_idx] - if anim_idx not in node.animations.keys(): - return - - for channel_idx in node.animations[anim_idx]: - channel = animation.channels[channel_idx] - if channel.target.path not in ['translation', 'rotation', 'scale']: - continue - - BlenderNodeAnim.do_channel(gltf, anim_idx, node_idx, channel) - - @staticmethod - def do_channel(gltf, anim_idx, node_idx, channel): - animation = gltf.data.animations[anim_idx] - vnode = gltf.vnodes[node_idx] - path = channel.target.path - - import_user_extensions('gather_import_animation_channel_before_hook', gltf, animation, vnode, path, channel) - - action = BlenderNodeAnim.get_or_create_action(gltf, node_idx, animation.track_name) - - keys = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].input) - values = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].output) - - if animation.samplers[channel.sampler].interpolation == "CUBICSPLINE": - # TODO manage tangent? - values = values[1::3] - - # Convert the curve from glTF to Blender. - - if path == "translation": - blender_path = "location" - group_name = "Location" - num_components = 3 - values = [gltf.loc_gltf_to_blender(vals) for vals in values] - values = vnode.base_locs_to_final_locs(values) - - elif path == "rotation": - blender_path = "rotation_quaternion" - group_name = "Rotation" - num_components = 4 - values = [gltf.quaternion_gltf_to_blender(vals) for vals in values] - values = vnode.base_rots_to_final_rots(values) - - elif path == "scale": - blender_path = "scale" - group_name = "Scale" - num_components = 3 - values = [gltf.scale_gltf_to_blender(vals) for vals in values] - values = vnode.base_scales_to_final_scales(values) - - # Objects parented to a bone are translated to the bone tip by default. - # Correct for this by translating backwards from the tip to the root. - if vnode.type == VNode.Object and path == "translation": - if vnode.parent is not None and gltf.vnodes[vnode.parent].type == VNode.Bone: - bone_length = gltf.vnodes[vnode.parent].bone_length - off = Vector((0, -bone_length, 0)) - values = [vals + off for vals in values] - - if vnode.type == VNode.Bone: - # Need to animate the pose bone when the node is a bone. - group_name = vnode.blender_bone_name - blender_path = 'pose.bones["%s"].%s' % ( - bpy.utils.escape_identifier(vnode.blender_bone_name), - blender_path - ) - - # We have the final TRS of the bone in values. We need to give - # the TRS of the pose bone though, which is relative to the edit - # bone. - # - # Final = EditBone * PoseBone - # where - # Final = Trans[ft] Rot[fr] Scale[fs] - # EditBone = Trans[et] Rot[er] - # PoseBone = Trans[pt] Rot[pr] Scale[ps] - # - # Solving for PoseBone gives - # - # pt = Rot[er^{-1}] (ft - et) - # pr = er^{-1} fr - # ps = fs - - if path == 'translation': - edit_trans, edit_rot = vnode.editbone_trans, vnode.editbone_rot - edit_rot_inv = edit_rot.conjugated() - values = [ - edit_rot_inv @ (trans - edit_trans) - for trans in values - ] - - elif path == 'rotation': - edit_rot = vnode.editbone_rot - edit_rot_inv = edit_rot.conjugated() - values = [ - edit_rot_inv @ rot - for rot in values - ] - - elif path == 'scale': - pass # no change needed - - # To ensure rotations always take the shortest path, we flip - # adjacent antipodal quaternions. - if path == 'rotation': - for i in range(1, len(values)): - if values[i].dot(values[i-1]) < 0: - values[i] = -values[i] - - fps = bpy.context.scene.render.fps - - coords = [0] * (2 * len(keys)) - coords[::2] = (key[0] * fps for key in keys) - - for i in range(0, num_components): - coords[1::2] = (vals[i] for vals in values) - make_fcurve( - action, - coords, - data_path=blender_path, - index=i, - group_name=group_name, - interpolation=animation.samplers[channel.sampler].interpolation, - ) - - import_user_extensions('gather_import_animation_channel_after_hook', gltf, animation, vnode, path, channel, action) - - @staticmethod - def get_or_create_action(gltf, node_idx, anim_name): - vnode = gltf.vnodes[node_idx] - - if vnode.type == VNode.Bone: - # For bones, the action goes on the armature. - vnode = gltf.vnodes[vnode.bone_arma] - - obj = vnode.blender_object - - action = gltf.action_cache.get(obj.name) - if not action: - name = anim_name + "_" + obj.name - action = bpy.data.actions.new(name) - action.id_root = 'OBJECT' - gltf.needs_stash.append((obj, action)) - gltf.action_cache[obj.name] = action - - return action diff --git a/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_animation_utils.py b/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_animation_utils.py deleted file mode 100644 index c26b666..0000000 --- a/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_animation_utils.py +++ /dev/null @@ -1,80 +0,0 @@ -# Copyright 2018-2021 The glTF-Blender-IO authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import bpy - -def simulate_stash(obj, track_name, action, start_frame=None): - # Simulate stash : - # * add a track - # * add an action on track - # * lock & mute the track - if not obj.animation_data: - obj.animation_data_create() - tracks = obj.animation_data.nla_tracks - new_track = tracks.new(prev=None) - new_track.name = track_name - if start_frame is None: - start_frame = bpy.context.scene.frame_start - _strip = new_track.strips.new(action.name, start_frame, action) - new_track.lock = True - new_track.mute = True - -def restore_animation_on_object(obj, anim_name): - if not getattr(obj, 'animation_data', None): - return - - for track in obj.animation_data.nla_tracks: - if track.name != anim_name: - continue - if not track.strips: - continue - - obj.animation_data.action = track.strips[0].action - return - - obj.animation_data.action = None - -def make_fcurve(action, co, data_path, index=0, group_name='', interpolation=None): - try: - fcurve = action.fcurves.new(data_path=data_path, index=index, action_group=group_name) - except: - # Some non valid files can have multiple target path - return None - - fcurve.keyframe_points.add(len(co) // 2) - fcurve.keyframe_points.foreach_set('co', co) - - # Setting interpolation - ipoString = { - 'CUBICSPLINE': 'BEZIER', - 'LINEAR': 'LINEAR', - 'STEP': 'CONSTANT', - }[interpolation or 'LINEAR'] - ipoInt = bpy.types.Keyframe.bl_rna.properties['interpolation'].enum_items[ipoString].value - try: - fcurve.keyframe_points.foreach_set('interpolation', [ipoInt] * len(fcurve.keyframe_points)) - except: - # Expects a string in some versions < 2.93 - for i in range(len(fcurve.keyframe_points)): - fcurve.keyframe_points[i].interpolation = ipoString - - # For CUBICSPLINE, also set the handle types to AUTO - if interpolation == 'CUBICSPLINE': - ty = bpy.types.Keyframe.bl_rna.properties['handle_left_type'].enum_items['AUTO'].value - fcurve.keyframe_points.foreach_set('handle_left_type', [ty] * len(fcurve.keyframe_points)) - fcurve.keyframe_points.foreach_set('handle_right_type', [ty] * len(fcurve.keyframe_points)) - - fcurve.update() # force updating tangents (this may change when tangent will be managed) - - return fcurve diff --git a/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_animation_weight.py b/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_animation_weight.py deleted file mode 100644 index e14cee8..0000000 --- a/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_animation_weight.py +++ /dev/null @@ -1,98 +0,0 @@ -# Copyright 2018-2021 The glTF-Blender-IO authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import bpy - -from ...io.imp.gltf2_io_binary import BinaryData -from .gltf2_blender_animation_utils import make_fcurve -from ...io.imp.gltf2_io_user_extensions import import_user_extensions - - -class BlenderWeightAnim(): - """Blender ShapeKey Animation.""" - def __new__(cls, *args, **kwargs): - raise RuntimeError("%s should not be instantiated" % cls) - - @staticmethod - def anim(gltf, anim_idx, vnode_id): - """Manage animation.""" - vnode = gltf.vnodes[vnode_id] - - node_idx = vnode.mesh_node_idx - - import_user_extensions('gather_import_animation_weight_before_hook', gltf, vnode, gltf.data.animations[anim_idx]) - - if node_idx is None: - return - - node = gltf.data.nodes[node_idx] - obj = vnode.blender_object - fps = bpy.context.scene.render.fps - - animation = gltf.data.animations[anim_idx] - - if anim_idx not in node.animations.keys(): - return - - for channel_idx in node.animations[anim_idx]: - channel = animation.channels[channel_idx] - if channel.target.path == "weights": - break - else: - return - - name = animation.track_name + "_" + obj.name - action = bpy.data.actions.new(name) - action.id_root = "KEY" - gltf.needs_stash.append((obj.data.shape_keys, action)) - - keys = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].input) - values = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].output) - - # retrieve number of targets - pymesh = gltf.data.meshes[gltf.data.nodes[node_idx].mesh] - nb_targets = len(pymesh.shapekey_names) - - if animation.samplers[channel.sampler].interpolation == "CUBICSPLINE": - offset = nb_targets - stride = 3 * nb_targets - else: - offset = 0 - stride = nb_targets - - coords = [0] * (2 * len(keys)) - coords[::2] = (key[0] * fps for key in keys) - - for sk in range(nb_targets): - if pymesh.shapekey_names[sk] is not None: # Do not animate shapekeys not created - coords[1::2] = (values[offset + stride * i + sk][0] for i in range(len(keys))) - kb_name = pymesh.shapekey_names[sk] - data_path = 'key_blocks["%s"].value' % bpy.utils.escape_identifier(kb_name) - - make_fcurve( - action, - coords, - data_path=data_path, - group_name="ShapeKeys", - interpolation=animation.samplers[channel.sampler].interpolation, - ) - - # Expand weight range if needed - kb = obj.data.shape_keys.key_blocks[kb_name] - min_weight = min(coords[1:2]) - max_weight = max(coords[1:2]) - if min_weight < kb.slider_min: kb.slider_min = min_weight - if max_weight > kb.slider_max: kb.slider_max = max_weight - - import_user_extensions('gather_import_animation_weight_after_hook', gltf, vnode, animation) diff --git a/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_camera.py b/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_camera.py deleted file mode 100644 index a688a89..0000000 --- a/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_camera.py +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright 2018-2021 The glTF-Blender-IO authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import bpy -from ..com.gltf2_blender_extras import set_extras -from ...io.imp.gltf2_io_user_extensions import import_user_extensions - - -class BlenderCamera(): - """Blender Camera.""" - def __new__(cls, *args, **kwargs): - raise RuntimeError("%s should not be instantiated" % cls) - - @staticmethod - def create(gltf, vnode, camera_id): - """Camera creation.""" - pycamera = gltf.data.cameras[camera_id] - - import_user_extensions('gather_import_camera_before_hook', gltf, vnode, pycamera) - - if not pycamera.name: - pycamera.name = "Camera" - - cam = bpy.data.cameras.new(pycamera.name) - set_extras(cam, pycamera.extras) - - # Blender create a perspective camera by default - if pycamera.type == "orthographic": - cam.type = "ORTHO" - - # TODO: xmag/ymag - - cam.clip_start = pycamera.orthographic.znear - cam.clip_end = pycamera.orthographic.zfar - - else: - cam.angle_y = pycamera.perspective.yfov - cam.lens_unit = "FOV" - cam.sensor_fit = "VERTICAL" - - # TODO: fov/aspect ratio - - cam.clip_start = pycamera.perspective.znear - if pycamera.perspective.zfar is not None: - cam.clip_end = pycamera.perspective.zfar - else: - # Infinite projection - cam.clip_end = 1e12 # some big number - - return cam diff --git a/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_gltf.py b/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_gltf.py deleted file mode 100755 index 92f8f46..0000000 --- a/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_gltf.py +++ /dev/null @@ -1,214 +0,0 @@ -# Copyright 2018-2021 The glTF-Blender-IO authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import bpy -from mathutils import Vector, Quaternion, Matrix -from .gltf2_blender_scene import BlenderScene - - -class BlenderGlTF(): - """Main glTF import class.""" - def __new__(cls, *args, **kwargs): - raise RuntimeError("%s should not be instantiated" % cls) - - @staticmethod - def create(gltf): - """Create glTF main method, with optional profiling""" - profile = bpy.app.debug_value == 102 - if profile: - import cProfile, pstats, io - from pstats import SortKey - pr = cProfile.Profile() - pr.enable() - BlenderGlTF._create(gltf) - pr.disable() - s = io.StringIO() - sortby = SortKey.TIME - ps = pstats.Stats(pr, stream=s).sort_stats(sortby) - ps.print_stats() - print(s.getvalue()) - else: - BlenderGlTF._create(gltf) - - @staticmethod - def _create(gltf): - """Create glTF main worker method.""" - BlenderGlTF.set_convert_functions(gltf) - BlenderGlTF.pre_compute(gltf) - BlenderScene.create(gltf) - - @staticmethod - def set_convert_functions(gltf): - if bpy.app.debug_value != 100: - # Unit conversion factor in (Blender units) per meter - u = 1.0 / bpy.context.scene.unit_settings.scale_length - - # glTF Y-Up space --> Blender Z-up space - # X,Y,Z --> X,-Z,Y - def convert_loc(x): return u * Vector([x[0], -x[2], x[1]]) - def convert_quat(q): return Quaternion([q[3], q[0], -q[2], q[1]]) - def convert_scale(s): return Vector([s[0], s[2], s[1]]) - def convert_matrix(m): - return Matrix([ - [ m[0], -m[ 8], m[4], m[12]*u], - [ -m[2], m[10], -m[6], -m[14]*u], - [ m[1], -m[ 9], m[5], m[13]*u], - [ m[3]/u, -m[11]/u, m[7]/u, m[15]], - ]) - - # Batch versions operate in place on a numpy array - def convert_locs_batch(locs): - # x,y,z -> x,-z,y - locs[:, [1,2]] = locs[:, [2,1]] - locs[:, 1] *= -1 - # Unit conversion - if u != 1: locs *= u - def convert_normals_batch(ns): - ns[:, [1,2]] = ns[:, [2,1]] - ns[:, 1] *= -1 - - # Correction for cameras and lights. - # glTF: right = +X, forward = -Z, up = +Y - # glTF after Yup2Zup: right = +X, forward = +Y, up = +Z - # Blender: right = +X, forward = -Z, up = +Y - # Need to carry Blender --> glTF after Yup2Zup - gltf.camera_correction = Quaternion((2**0.5/2, 2**0.5/2, 0.0, 0.0)) - - else: - def convert_loc(x): return Vector(x) - def convert_quat(q): return Quaternion([q[3], q[0], q[1], q[2]]) - def convert_scale(s): return Vector(s) - def convert_matrix(m): - return Matrix([m[0::4], m[1::4], m[2::4], m[3::4]]) - - def convert_locs_batch(_locs): return - def convert_normals_batch(_ns): return - - # Same convention, no correction needed. - gltf.camera_correction = None - - gltf.loc_gltf_to_blender = convert_loc - gltf.locs_batch_gltf_to_blender = convert_locs_batch - gltf.quaternion_gltf_to_blender = convert_quat - gltf.normals_batch_gltf_to_blender = convert_normals_batch - gltf.scale_gltf_to_blender = convert_scale - gltf.matrix_gltf_to_blender = convert_matrix - - @staticmethod - def pre_compute(gltf): - """Pre compute, just before creation.""" - # default scene used - gltf.blender_scene = None - - # Check if there is animation on object - # Init is to False, and will be set to True during creation - gltf.animation_object = False - - # Blender material - if gltf.data.materials: - for material in gltf.data.materials: - material.blender_material = {} - - # images - if gltf.data.images is not None: - for img in gltf.data.images: - img.blender_image_name = None - - if gltf.data.nodes is None: - # Something is wrong in file, there is no nodes - return - - for node in gltf.data.nodes: - # Weight animation management - node.weight_animation = False - - # Dispatch animation - if gltf.data.animations: - for node in gltf.data.nodes: - node.animations = {} - - track_names = set() - for anim_idx, anim in enumerate(gltf.data.animations): - # Pick pair-wise unique name for each animation to use as a name - # for its NLA tracks. - desired_name = anim.name or "Anim_%d" % anim_idx - anim.track_name = BlenderGlTF.find_unused_name(track_names, desired_name) - track_names.add(anim.track_name) - - for channel_idx, channel in enumerate(anim.channels): - if channel.target.node is None: - continue - - if anim_idx not in gltf.data.nodes[channel.target.node].animations.keys(): - gltf.data.nodes[channel.target.node].animations[anim_idx] = [] - gltf.data.nodes[channel.target.node].animations[anim_idx].append(channel_idx) - # Manage node with animation on weights, that are animated in meshes in Blender (ShapeKeys) - if channel.target.path == "weights": - gltf.data.nodes[channel.target.node].weight_animation = True - - # Meshes - if gltf.data.meshes: - for mesh in gltf.data.meshes: - mesh.blender_name = {} # caches Blender mesh name - - # Calculate names for each mesh's shapekeys - for mesh in gltf.data.meshes or []: - mesh.shapekey_names = [] - used_names = set(['Basis']) #Be sure to not use 'Basis' name at import, this is a reserved name - - # Some invalid glTF files has empty primitive tab - if len(mesh.primitives) > 0: - for sk, target in enumerate(mesh.primitives[0].targets or []): - if 'POSITION' not in target: - mesh.shapekey_names.append(None) - continue - - # Check if glTF file has some extras with targetNames. Otherwise - # use the name of the POSITION accessor on the first primitive. - shapekey_name = None - if mesh.extras is not None: - if 'targetNames' in mesh.extras and sk < len(mesh.extras['targetNames']): - shapekey_name = mesh.extras['targetNames'][sk] - if shapekey_name is None: - if gltf.data.accessors[target['POSITION']].name is not None: - shapekey_name = gltf.data.accessors[target['POSITION']].name - if shapekey_name is None: - shapekey_name = "target_" + str(sk) - - shapekey_name = BlenderGlTF.find_unused_name(used_names, shapekey_name) - used_names.add(shapekey_name) - - mesh.shapekey_names.append(shapekey_name) - - @staticmethod - def find_unused_name(haystack, desired_name): - """Finds a name not in haystack and <= 63 UTF-8 bytes. - (the limit on the size of a Blender name.) - If a is taken, tries a.001, then a.002, etc. - """ - stem = desired_name[:63] - suffix = '' - cntr = 1 - while True: - name = stem + suffix - - if len(name.encode('utf-8')) > 63: - stem = stem[:-1] - continue - - if name not in haystack: - return name - - suffix = '.%03d' % cntr - cntr += 1 diff --git a/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_image.py b/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_image.py deleted file mode 100644 index fa8520d..0000000 --- a/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_image.py +++ /dev/null @@ -1,116 +0,0 @@ -# Copyright 2018-2021 The glTF-Blender-IO authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import bpy -import os -import tempfile -from os.path import dirname, join, isfile, basename, normpath -import urllib.parse -import re - -from ...io.imp.gltf2_io_binary import BinaryData -from ...io.imp.gltf2_io_user_extensions import import_user_extensions - - -# Note that Image is not a glTF2.0 object -class BlenderImage(): - """Manage Image.""" - def __new__(cls, *args, **kwargs): - raise RuntimeError("%s should not be instantiated" % cls) - - @staticmethod - def create(gltf, img_idx): - """Image creation.""" - img = gltf.data.images[img_idx] - - if img.blender_image_name is not None: - # Image is already used somewhere - return - - import_user_extensions('gather_import_image_before_hook', gltf, img) - - if img.uri is not None and not img.uri.startswith('data:'): - blender_image = create_from_file(gltf, img_idx) - else: - blender_image = create_from_data(gltf, img_idx) - - if blender_image: - img.blender_image_name = blender_image.name - - import_user_extensions('gather_import_image_after_hook', gltf, img, blender_image) - - -def create_from_file(gltf, img_idx): - # Image stored in a file - - num_images = len(bpy.data.images) - - img = gltf.data.images[img_idx] - - path = join(dirname(gltf.filename), _uri_to_path(img.uri)) - path = os.path.abspath(path) - if bpy.data.is_saved and bpy.context.preferences.filepaths.use_relative_paths: - try: - path = bpy.path.relpath(path) - except: - # May happen on Windows if on different drives, eg. C:\ and D:\ - pass - - img_name = img.name or basename(path) - - try: - blender_image = bpy.data.images.load( - path, - check_existing=True, - ) - - needs_pack = True - if needs_pack: - blender_image.pack() - - except RuntimeError: - gltf.log.error("Missing image file (index %d): %s" % (img_idx, path)) - blender_image = _placeholder_image(img_name, os.path.abspath(path)) - - if len(bpy.data.images) != num_images: # If created a new image - blender_image.name = img_name - - return blender_image - - -def create_from_data(gltf, img_idx): - # Image stored as data => pack - img_data = BinaryData.get_image_data(gltf, img_idx) - if img_data is None: - return - img_name = 'Image_%d' % img_idx - - # Create image, width and height are dummy values - blender_image = bpy.data.images.new(img_name, 8, 8) - # Set packed file data - blender_image.pack(data=img_data.tobytes(), data_len=len(img_data)) - blender_image.source = 'FILE' - - return blender_image - -def _placeholder_image(name, path): - image = bpy.data.images.new(name, 128, 128) - # allow the path to be resolved later - image.filepath = path - image.source = 'FILE' - return image - -def _uri_to_path(uri): - uri = urllib.parse.unquote(uri) - return normpath(uri) diff --git a/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_light.py b/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_light.py deleted file mode 100644 index 6f001e1..0000000 --- a/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_light.py +++ /dev/null @@ -1,93 +0,0 @@ -# Copyright 2018-2021 The glTF-Blender-IO authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import bpy -from math import pi - -from ..com.gltf2_blender_extras import set_extras -from ...io.imp.gltf2_io_user_extensions import import_user_extensions - - -class BlenderLight(): - """Blender Light.""" - def __new__(cls, *args, **kwargs): - raise RuntimeError("%s should not be instantiated" % cls) - - @staticmethod - def create(gltf, vnode, light_id): - """Light creation.""" - pylight = gltf.data.extensions['KHR_lights_punctual']['lights'][light_id] - - import_user_extensions('gather_import_light_before_hook', gltf, vnode, pylight) - - if pylight['type'] == "directional": - light = BlenderLight.create_directional(gltf, light_id) - elif pylight['type'] == "point": - light = BlenderLight.create_point(gltf, light_id) - elif pylight['type'] == "spot": - light = BlenderLight.create_spot(gltf, light_id) - - if 'color' in pylight.keys(): - light.color = pylight['color'] - - if 'intensity' in pylight.keys(): - light.energy = pylight['intensity'] - - # TODO range - - set_extras(light, pylight.get('extras')) - - return light - - @staticmethod - def create_directional(gltf, light_id): - pylight = gltf.data.extensions['KHR_lights_punctual']['lights'][light_id] - - if 'name' not in pylight.keys(): - pylight['name'] = "Sun" - - sun = bpy.data.lights.new(name=pylight['name'], type="SUN") - return sun - - @staticmethod - def create_point(gltf, light_id): - pylight = gltf.data.extensions['KHR_lights_punctual']['lights'][light_id] - - if 'name' not in pylight.keys(): - pylight['name'] = "Point" - - point = bpy.data.lights.new(name=pylight['name'], type="POINT") - return point - - @staticmethod - def create_spot(gltf, light_id): - pylight = gltf.data.extensions['KHR_lights_punctual']['lights'][light_id] - - if 'name' not in pylight.keys(): - pylight['name'] = "Spot" - - spot = bpy.data.lights.new(name=pylight['name'], type="SPOT") - - # Angles - if 'spot' in pylight.keys() and 'outerConeAngle' in pylight['spot']: - spot.spot_size = pylight['spot']['outerConeAngle'] * 2 - else: - spot.spot_size = pi / 2 - - if 'spot' in pylight.keys() and 'innerConeAngle' in pylight['spot']: - spot.spot_blend = 1 - ( pylight['spot']['innerConeAngle'] / pylight['spot']['outerConeAngle'] ) - else: - spot.spot_blend = 1.0 - - return spot diff --git a/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_material.py b/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_material.py deleted file mode 100644 index bbf8d39..0000000 --- a/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_material.py +++ /dev/null @@ -1,96 +0,0 @@ -# Copyright 2018-2021 The glTF-Blender-IO authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import bpy - -from ..com.gltf2_blender_extras import set_extras -from .gltf2_blender_pbrMetallicRoughness import MaterialHelper, pbr_metallic_roughness -from .gltf2_blender_KHR_materials_pbrSpecularGlossiness import pbr_specular_glossiness -from .gltf2_blender_KHR_materials_unlit import unlit -from ...io.imp.gltf2_io_user_extensions import import_user_extensions - - -class BlenderMaterial(): - """Blender Material.""" - def __new__(cls, *args, **kwargs): - raise RuntimeError("%s should not be instantiated" % cls) - - @staticmethod - def create(gltf, material_idx, vertex_color): - """Material creation.""" - pymaterial = gltf.data.materials[material_idx] - - import_user_extensions('gather_import_material_before_hook', gltf, pymaterial, vertex_color) - - name = pymaterial.name - if name is None: - name = "Material_" + str(material_idx) - - mat = bpy.data.materials.new(name) - pymaterial.blender_material[vertex_color] = mat.name - - set_extras(mat, pymaterial.extras) - BlenderMaterial.set_double_sided(pymaterial, mat) - BlenderMaterial.set_alpha_mode(pymaterial, mat) - BlenderMaterial.set_viewport_color(pymaterial, mat, vertex_color) - - mat.use_nodes = True - while mat.node_tree.nodes: # clear all nodes - mat.node_tree.nodes.remove(mat.node_tree.nodes[0]) - - mh = MaterialHelper(gltf, pymaterial, mat, vertex_color) - - exts = pymaterial.extensions or {} - if 'KHR_materials_unlit' in exts: - unlit(mh) - elif 'KHR_materials_pbrSpecularGlossiness' in exts: - pbr_specular_glossiness(mh) - else: - pbr_metallic_roughness(mh) - - import_user_extensions('gather_import_material_after_hook', gltf, pymaterial, vertex_color, mat) - - @staticmethod - def set_double_sided(pymaterial, mat): - mat.use_backface_culling = (pymaterial.double_sided != True) - - @staticmethod - def set_alpha_mode(pymaterial, mat): - alpha_mode = pymaterial.alpha_mode - if alpha_mode == 'BLEND': - mat.blend_method = 'BLEND' - elif alpha_mode == 'MASK': - mat.blend_method = 'CLIP' - alpha_cutoff = pymaterial.alpha_cutoff - alpha_cutoff = alpha_cutoff if alpha_cutoff is not None else 0.5 - mat.alpha_threshold = alpha_cutoff - - @staticmethod - def set_viewport_color(pymaterial, mat, vertex_color): - # If there is no texture and no vertex color, use the base color as - # the color for the Solid view. - if vertex_color: - return - - exts = pymaterial.extensions or {} - if 'KHR_materials_pbrSpecularGlossiness' in exts: - # TODO - return - else: - pbr = pymaterial.pbr_metallic_roughness - if pbr is None or pbr.base_color_texture is not None: - return - color = pbr.base_color_factor or [1, 1, 1, 1] - - mat.diffuse_color = color diff --git a/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_mesh.py b/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_mesh.py deleted file mode 100644 index 9cd2a1d..0000000 --- a/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_mesh.py +++ /dev/null @@ -1,686 +0,0 @@ -# Copyright 2018-2021 The glTF-Blender-IO authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import bpy -from mathutils import Matrix -import numpy as np - -from ...io.imp.gltf2_io_binary import BinaryData -from ..com.gltf2_blender_extras import set_extras -from .gltf2_blender_material import BlenderMaterial -from ...io.com.gltf2_io_debug import print_console -from .gltf2_io_draco_compression_extension import decode_primitive -from ...io.imp.gltf2_io_user_extensions import import_user_extensions - - -class BlenderMesh(): - """Blender Mesh.""" - def __new__(cls, *args, **kwargs): - raise RuntimeError("%s should not be instantiated" % cls) - - @staticmethod - def create(gltf, mesh_idx, skin_idx): - """Mesh creation.""" - return create_mesh(gltf, mesh_idx, skin_idx) - - -# Maximum number of TEXCOORD_n/COLOR_n sets to import -UV_MAX = 8 -COLOR_MAX = 8 - - -def create_mesh(gltf, mesh_idx, skin_idx): - pymesh = gltf.data.meshes[mesh_idx] - - import_user_extensions('gather_import_mesh_before_hook', gltf, pymesh) - - name = pymesh.name or 'Mesh_%d' % mesh_idx - mesh = bpy.data.meshes.new(name) - - # Temporarily parent the mesh to an object. - # This is used to set skin weights and shapekeys. - tmp_ob = None - try: - tmp_ob = bpy.data.objects.new('##gltf-import:tmp-object##', mesh) - do_primitives(gltf, mesh_idx, skin_idx, mesh, tmp_ob) - set_extras(mesh, gltf.data.meshes[mesh_idx].extras, exclude=['targetNames']) - - finally: - if tmp_ob: - bpy.data.objects.remove(tmp_ob) - - import_user_extensions('gather_import_mesh_after_hook', gltf, pymesh, mesh) - - return mesh - - -def do_primitives(gltf, mesh_idx, skin_idx, mesh, ob): - """Put all primitive data into the mesh.""" - pymesh = gltf.data.meshes[mesh_idx] - - # Scan the primitives to find out what we need to create - - has_normals = False - num_uvs = 0 - num_cols = 0 - num_joint_sets = 0 - for prim in pymesh.primitives: - if 'POSITION' not in prim.attributes: - continue - - if True: #gltf.import_settings['import_shading'] == "NORMALS": - if 'NORMAL' in prim.attributes: - has_normals = True - - if skin_idx is not None: - i = 0 - while ('JOINTS_%d' % i) in prim.attributes and \ - ('WEIGHTS_%d' % i) in prim.attributes: - i += 1 - num_joint_sets = max(i, num_joint_sets) - - i = 0 - while i < UV_MAX and ('TEXCOORD_%d' % i) in prim.attributes: i += 1 - num_uvs = max(i, num_uvs) - - i = 0 - while i < COLOR_MAX and ('COLOR_%d' % i) in prim.attributes: i += 1 - num_cols = max(i, num_cols) - - num_shapekeys = 0 - if len(pymesh.primitives) > 0: # Empty primitive tab is not allowed, but some invalid files... - for morph_i, _ in enumerate(pymesh.primitives[0].targets or []): - if pymesh.shapekey_names[morph_i] is not None: - num_shapekeys += 1 - - # ------------- - # We'll process all the primitives gathering arrays to feed into the - # various foreach_set function that create the mesh data. - - num_faces = 0 # total number of faces - vert_locs = np.empty(dtype=np.float32, shape=(0,3)) # coordinate for each vert - vert_normals = np.empty(dtype=np.float32, shape=(0,3)) # normal for each vert - edge_vidxs = np.array([], dtype=np.uint32) # vertex_index for each loose edge - loop_vidxs = np.array([], dtype=np.uint32) # vertex_index for each loop - loop_uvs = [ - np.empty(dtype=np.float32, shape=(0,2)) # UV for each loop for each layer - for _ in range(num_uvs) - ] - loop_cols = [ - np.empty(dtype=np.float32, shape=(0,4)) # color for each loop for each layer - for _ in range(num_cols) - ] - vert_joints = [ - np.empty(dtype=np.uint32, shape=(0,4)) # 4 joints for each vert for each set - for _ in range(num_joint_sets) - ] - vert_weights = [ - np.empty(dtype=np.float32, shape=(0,4)) # 4 weights for each vert for each set - for _ in range(num_joint_sets) - ] - sk_vert_locs = [ - np.empty(dtype=np.float32, shape=(0,3)) # coordinate for each vert for each shapekey - for _ in range(num_shapekeys) - ] - - for prim in pymesh.primitives: - prim.num_faces = 0 - - if 'POSITION' not in prim.attributes: - continue - - vert_index_base = len(vert_locs) - - if prim.extensions is not None and 'KHR_draco_mesh_compression' in prim.extensions: - print_console('INFO', 'Draco Decoder: Decode primitive {}'.format(pymesh.name or '[unnamed]')) - decode_primitive(gltf, prim) - - if prim.indices is not None: - indices = BinaryData.decode_accessor(gltf, prim.indices) - indices = indices.reshape(len(indices)) - else: - num_verts = gltf.data.accessors[prim.attributes['POSITION']].count - indices = np.arange(0, num_verts, dtype=np.uint32) - - mode = 4 if prim.mode is None else prim.mode - points, edges, tris = points_edges_tris(mode, indices) - if points is not None: - indices = points - elif edges is not None: - indices = edges - else: - indices = tris - - # We'll add one vert to the arrays for each index used in indices - unique_indices, inv_indices = np.unique(indices, return_inverse=True) - - vs = BinaryData.decode_accessor(gltf, prim.attributes['POSITION'], cache=True) - vert_locs = np.concatenate((vert_locs, vs[unique_indices])) - - if has_normals: - if 'NORMAL' in prim.attributes: - ns = BinaryData.decode_accessor(gltf, prim.attributes['NORMAL'], cache=True) - ns = ns[unique_indices] - else: - ns = np.zeros((len(unique_indices), 3), dtype=np.float32) - vert_normals = np.concatenate((vert_normals, ns)) - - for i in range(num_joint_sets): - if ('JOINTS_%d' % i) in prim.attributes and ('WEIGHTS_%d' % i) in prim.attributes: - js = BinaryData.decode_accessor(gltf, prim.attributes['JOINTS_%d' % i], cache=True) - ws = BinaryData.decode_accessor(gltf, prim.attributes['WEIGHTS_%d' % i], cache=True) - js = js[unique_indices] - ws = ws[unique_indices] - else: - js = np.zeros((len(unique_indices), 4), dtype=np.uint32) - ws = np.zeros((len(unique_indices), 4), dtype=np.float32) - vert_joints[i] = np.concatenate((vert_joints[i], js)) - vert_weights[i] = np.concatenate((vert_weights[i], ws)) - - for morph_i, target in enumerate(prim.targets or []): - if pymesh.shapekey_names[morph_i] is None: - continue - morph_vs = BinaryData.decode_accessor(gltf, target['POSITION'], cache=True) - morph_vs = morph_vs[unique_indices] - sk_vert_locs[morph_i] = np.concatenate((sk_vert_locs[morph_i], morph_vs)) - - # inv_indices are the indices into the verts just for this prim; - # calculate indices into the overall verts array - prim_vidxs = inv_indices.astype(np.uint32, copy=False) - prim_vidxs += vert_index_base # offset for verts from previous prims - - if edges is not None: - edge_vidxs = np.concatenate((edge_vidxs, prim_vidxs)) - - if tris is not None: - prim.num_faces = len(indices) // 3 - num_faces += prim.num_faces - - loop_vidxs = np.concatenate((loop_vidxs, prim_vidxs)) - - for uv_i in range(num_uvs): - if ('TEXCOORD_%d' % uv_i) in prim.attributes: - uvs = BinaryData.decode_accessor(gltf, prim.attributes['TEXCOORD_%d' % uv_i], cache=True) - uvs = uvs[indices] - else: - uvs = np.zeros((len(indices), 2), dtype=np.float32) - loop_uvs[uv_i] = np.concatenate((loop_uvs[uv_i], uvs)) - - for col_i in range(num_cols): - if ('COLOR_%d' % col_i) in prim.attributes: - cols = BinaryData.decode_accessor(gltf, prim.attributes['COLOR_%d' % col_i], cache=True) - cols = cols[indices] - if cols.shape[1] == 3: - cols = colors_rgb_to_rgba(cols) - else: - cols = np.ones((len(indices), 4), dtype=np.float32) - loop_cols[col_i] = np.concatenate((loop_cols[col_i], cols)) - - # Accessors are cached in case they are shared between primitives; clear - # the cache now that all prims are done. - gltf.decode_accessor_cache = {} - - if True: #gltf.import_settings['merge_vertices']: - vert_locs, vert_normals, vert_joints, vert_weights, \ - sk_vert_locs, loop_vidxs, edge_vidxs = \ - merge_duplicate_verts( - vert_locs, vert_normals, vert_joints, vert_weights, \ - sk_vert_locs, loop_vidxs, edge_vidxs\ - ) - - # --------------- - # Convert all the arrays glTF -> Blender - - # Change from relative to absolute positions for morph locs - for sk_locs in sk_vert_locs: - sk_locs += vert_locs - - gltf.locs_batch_gltf_to_blender(vert_locs) - gltf.normals_batch_gltf_to_blender(vert_normals) - for sk_locs in sk_vert_locs: - gltf.locs_batch_gltf_to_blender(sk_locs) - - if num_joint_sets: - skin_into_bind_pose( - gltf, skin_idx, vert_joints, vert_weights, - locs=[vert_locs] + sk_vert_locs, - vert_normals=vert_normals, - ) - - for uvs in loop_uvs: - uvs_gltf_to_blender(uvs) - - for cols in loop_cols: - colors_linear_to_srgb(cols[:, :-1]) - - # --------------- - # Start creating things - - mesh.vertices.add(len(vert_locs)) - mesh.vertices.foreach_set('co', squish(vert_locs)) - - mesh.loops.add(len(loop_vidxs)) - mesh.loops.foreach_set('vertex_index', loop_vidxs) - - mesh.edges.add(len(edge_vidxs) // 2) - mesh.edges.foreach_set('vertices', edge_vidxs) - - mesh.polygons.add(num_faces) - - # All polys are tris - loop_starts = np.arange(0, 3 * num_faces, step=3) - loop_totals = np.full(num_faces, 3) - mesh.polygons.foreach_set('loop_start', loop_starts) - mesh.polygons.foreach_set('loop_total', loop_totals) - - for uv_i in range(num_uvs): - name = 'UVMap' if uv_i == 0 else 'UVMap.%03d' % uv_i - layer = mesh.uv_layers.new(name=name) - - if layer is None: - print("WARNING: UV map is ignored because the maximum number of UV layers has been reached.") - break - - layer.data.foreach_set('uv', squish(loop_uvs[uv_i])) - - for col_i in range(num_cols): - name = 'Col' if col_i == 0 else 'Col.%03d' % col_i - layer = mesh.vertex_colors.new(name=name) - - if layer is None: - print("WARNING: Vertex colors are ignored because the maximum number of vertex color layers has been " - "reached.") - break - - layer.data.foreach_set('color', squish(loop_cols[col_i])) - - # Skinning - # TODO: this is slow :/ - if num_joint_sets: - pyskin = gltf.data.skins[skin_idx] - for i, node_idx in enumerate(pyskin.joints): - bone = gltf.vnodes[node_idx] - ob.vertex_groups.new(name=bone.blender_bone_name) - - vgs = list(ob.vertex_groups) - - for i in range(num_joint_sets): - js = vert_joints[i].tolist() # tolist() is faster - ws = vert_weights[i].tolist() - for vi in range(len(vert_locs)): - w0, w1, w2, w3 = ws[vi] - j0, j1, j2, j3 = js[vi] - if w0 != 0: vgs[j0].add((vi,), w0, 'REPLACE') - if w1 != 0: vgs[j1].add((vi,), w1, 'REPLACE') - if w2 != 0: vgs[j2].add((vi,), w2, 'REPLACE') - if w3 != 0: vgs[j3].add((vi,), w3, 'REPLACE') - - # Shapekeys - if num_shapekeys: - ob.shape_key_add(name='Basis') - mesh.shape_keys.name = mesh.name - - sk_i = 0 - for sk_name in pymesh.shapekey_names: - if sk_name is None: - continue - - ob.shape_key_add(name=sk_name) - key_block = mesh.shape_keys.key_blocks[sk_name] - key_block.data.foreach_set('co', squish(sk_vert_locs[sk_i])) - - sk_i += 1 - - # ---- - # Assign materials to faces - has_materials = any(prim.material is not None for prim in pymesh.primitives) - if has_materials: - material_indices = np.empty(num_faces, dtype=np.uint32) - empty_material_slot_index = None - f = 0 - - for prim in pymesh.primitives: - if prim.material is not None: - # Get the material - pymaterial = gltf.data.materials[prim.material] - vertex_color = 'COLOR_0' if 'COLOR_0' in prim.attributes else None - if vertex_color not in pymaterial.blender_material: - BlenderMaterial.create(gltf, prim.material, vertex_color) - material_name = pymaterial.blender_material[vertex_color] - - # Put material in slot (if not there) - if material_name not in mesh.materials: - mesh.materials.append(bpy.data.materials[material_name]) - material_index = mesh.materials.find(material_name) - else: - if empty_material_slot_index is None: - mesh.materials.append(None) - empty_material_slot_index = len(mesh.materials) - 1 - material_index = empty_material_slot_index - - material_indices[f:f + prim.num_faces].fill(material_index) - - f += prim.num_faces - - mesh.polygons.foreach_set('material_index', material_indices) - - # ---- - # Normals - - # Set polys smooth/flat - set_poly_smoothing(gltf, pymesh, mesh, vert_normals, loop_vidxs) - - mesh.validate() - has_loose_edges = len(edge_vidxs) != 0 # need to calc_loose_edges for them to show up - mesh.update(calc_edges_loose=has_loose_edges) - - if has_normals: - mesh.create_normals_split() - mesh.normals_split_custom_set_from_vertices(vert_normals) - mesh.use_auto_smooth = True - - -def points_edges_tris(mode, indices): - points = None - edges = None - tris = None - - if mode == 0: - # POINTS - points = indices - - elif mode == 1: - # LINES - # 1 3 - # / / - # 0 2 - edges = indices - - elif mode == 2: - # LINE LOOP - # 1---2 - # / \ - # 0-------3 - # in: 0123 - # out: 01122330 - edges = np.empty(2 * len(indices), dtype=np.uint32) - edges[[0, -1]] = indices[[0, 0]] # 0______0 - edges[1:-1] = np.repeat(indices[1:], 2) # 01122330 - - elif mode == 3: - # LINE STRIP - # 1---2 - # / \ - # 0 3 - # in: 0123 - # out: 011223 - edges = np.empty(2 * len(indices) - 2, dtype=np.uint32) - edges[[0, -1]] = indices[[0, -1]] # 0____3 - edges[1:-1] = np.repeat(indices[1:-1], 2) # 011223 - - elif mode == 4: - # TRIANGLES - # 2 3 - # / \ / \ - # 0---1 4---5 - tris = indices - - elif mode == 5: - # TRIANGLE STRIP - # 0---2---4 - # \ / \ / - # 1---3 - # TODO: numpyify - def alternate(i, xs): - even = i % 2 == 0 - return xs if even else (xs[0], xs[2], xs[1]) - tris = np.array([ - alternate(i, (indices[i], indices[i + 1], indices[i + 2])) - for i in range(0, len(indices) - 2) - ]) - tris = squish(tris) - - elif mode == 6: - # TRIANGLE FAN - # 3---2 - # / \ / \ - # 4---0---1 - # TODO: numpyify - tris = np.array([ - (indices[0], indices[i], indices[i + 1]) - for i in range(1, len(indices) - 1) - ]) - tris = squish(tris) - - else: - raise Exception('primitive mode unimplemented: %d' % mode) - - return points, edges, tris - - -def squish(array): - """Squish nD array into 1D array (required by foreach_set).""" - return array.reshape(array.size) - - -def colors_rgb_to_rgba(rgb): - rgba = np.ones((len(rgb), 4), dtype=np.float32) - rgba[:, :3] = rgb - return rgba - - -def colors_linear_to_srgb(color): - assert color.shape[1] == 3 # only change RGB, not A - - not_small = color >= 0.0031308 - small_result = np.where(color < 0.0, 0.0, color * 12.92) - large_result = 1.055 * np.power(color, 1.0 / 2.4, where=not_small) - 0.055 - color[:] = np.where(not_small, large_result, small_result) - - -def uvs_gltf_to_blender(uvs): - # u,v -> u,1-v - uvs[:, 1] *= -1 - uvs[:, 1] += 1 - - -def skin_into_bind_pose(gltf, skin_idx, vert_joints, vert_weights, locs, vert_normals): - # Skin each position/normal using the bind pose. - # Skinning equation: vert' = sum_(j,w) w * joint_mat[j] * vert - # where the sum is over all (joint,weight) pairs. - - # Calculate joint matrices - joint_mats = [] - pyskin = gltf.data.skins[skin_idx] - if pyskin.inverse_bind_matrices is not None: - inv_binds = BinaryData.get_data_from_accessor(gltf, pyskin.inverse_bind_matrices) - inv_binds = [gltf.matrix_gltf_to_blender(m) for m in inv_binds] - else: - inv_binds = [Matrix.Identity(4) for i in range(len(pyskin.joints))] - bind_mats = [gltf.vnodes[joint].bind_arma_mat for joint in pyskin.joints] - joint_mats = [bind_mat @ inv_bind for bind_mat, inv_bind in zip(bind_mats, inv_binds)] - - # TODO: check if joint_mats are all (approximately) 1, and skip skinning - - joint_mats = np.array(joint_mats, dtype=np.float32) - - # Compute the skinning matrices for every vert - num_verts = len(locs[0]) - skinning_mats = np.zeros((num_verts, 4, 4), dtype=np.float32) - weight_sums = np.zeros(num_verts, dtype=np.float32) - for js, ws in zip(vert_joints, vert_weights): - for i in range(4): - skinning_mats += ws[:, i].reshape(len(ws), 1, 1) * joint_mats[js[:, i]] - weight_sums += ws[:, i] - # Normalize weights to one; necessary for old files / quantized weights - skinning_mats /= weight_sums.reshape(num_verts, 1, 1) - - skinning_mats_3x3 = skinning_mats[:, :3, :3] - skinning_trans = skinning_mats[:, :3, 3] - - for vs in locs: - vs[:] = mul_mats_vecs(skinning_mats_3x3, vs) - vs[:] += skinning_trans - - if len(vert_normals) != 0: - vert_normals[:] = mul_mats_vecs(skinning_mats_3x3, vert_normals) - # Don't translate normals! - normalize_vecs(vert_normals) - - -def mul_mats_vecs(mats, vecs): - """Given [m1,m2,...] and [v1,v2,...], returns [m1@v1,m2@v2,...]. 3D only.""" - return np.matmul(mats, vecs.reshape(len(vecs), 3, 1)).reshape(len(vecs), 3) - - -def normalize_vecs(vectors): - norms = np.linalg.norm(vectors, axis=1, keepdims=True) - np.divide(vectors, norms, out=vectors, where=norms != 0) - - -def set_poly_smoothing(gltf, pymesh, mesh, vert_normals, loop_vidxs): - num_polys = len(mesh.polygons) - - # assert gltf.import_settings['import_shading'] == "NORMALS" - - # Try to guess which polys should be flat based on the fact that all the - # loop normals for a flat poly are = the poly's normal. - - poly_smooths = np.empty(num_polys, dtype=np.bool) - - poly_normals = np.empty(num_polys * 3, dtype=np.float32) - mesh.polygons.foreach_get('normal', poly_normals) - poly_normals = poly_normals.reshape(num_polys, 3) - - f = 0 - for prim in pymesh.primitives: - if 'NORMAL' not in prim.attributes: - # Primitives with no NORMALs should use flat shading - poly_smooths[f:f + prim.num_faces].fill(False) - f += prim.num_faces - continue - - # Check the normals at the three corners against the poly normal. - # Two normals are equal iff their dot product is 1. - - poly_ns = poly_normals[f:f + prim.num_faces] - - # Dot product against the first vertex normal in the tri - vert_ns = vert_normals[loop_vidxs[3*f:3*(f + prim.num_faces):3]] - dot_prods = np.sum(vert_ns * poly_ns, axis=1) # dot product - smooth = (dot_prods <= 0.9999999) - - # Same for the second vertex, etc. - vert_ns = vert_normals[loop_vidxs[3*f+1:3*(f + prim.num_faces):3]] - dot_prods = np.sum(vert_ns * poly_ns, axis=1) - np.logical_or(smooth, dot_prods <= 0.9999999, out=smooth) - - vert_ns = vert_normals[loop_vidxs[3*f+2:3*(f + prim.num_faces):3]] - dot_prods = np.sum(vert_ns * poly_ns, axis=1) - np.logical_or(smooth, dot_prods <= 0.9999999, out=smooth) - - poly_smooths[f:f + prim.num_faces] = smooth - - f += prim.num_faces - - mesh.polygons.foreach_set('use_smooth', poly_smooths) - - -def merge_duplicate_verts(vert_locs, vert_normals, vert_joints, vert_weights, sk_vert_locs, loop_vidxs, edge_vidxs): - # This function attempts to invert the splitting done when exporting to - # glTF. Welds together verts with the same per-vert data (but possibly - # different per-loop data). - # - # Ideally normals would be treated as per-loop data, but that has problems, - # so we currently treat the normal as per-vert. - # - # Strategy is simple: put all the per-vert data into an array of structs - # ("dots"), dedupe with np.unique, then take all the data back out. - - # Very often two verts that "morally" should be merged will have normals - # with very small differences. Round off the normals to smooth this over. - if len(vert_normals) != 0: - vert_normals *= 50000 - vert_normals[:] = np.trunc(vert_normals) - vert_normals *= (1/50000) - - dot_fields = [('x', np.float32), ('y', np.float32), ('z', np.float32)] - if len(vert_normals) != 0: - dot_fields += [('nx', np.float32), ('ny', np.float32), ('nz', np.float32)] - for i, _ in enumerate(vert_joints): - dot_fields += [ - ('joint%dx' % i, np.uint32), ('joint%dy' % i, np.uint32), - ('joint%dz' % i, np.uint32), ('joint%dw' % i, np.uint32), - ('weight%dx' % i, np.float32), ('weight%dy' % i, np.float32), - ('weight%dz' % i, np.float32), ('weight%dw' % i, np.float32), - ] - for i, _ in enumerate(sk_vert_locs): - dot_fields += [ - ('sk%dx' % i, np.float32), ('sk%dy' % i, np.float32), ('sk%dz' % i, np.float32), - ] - dots = np.empty(len(vert_locs), dtype=np.dtype(dot_fields)) - - dots['x'] = vert_locs[:, 0] - dots['y'] = vert_locs[:, 1] - dots['z'] = vert_locs[:, 2] - if len(vert_normals) != 0: - dots['nx'] = vert_normals[:, 0] - dots['ny'] = vert_normals[:, 1] - dots['nz'] = vert_normals[:, 2] - for i, (joints, weights) in enumerate(zip(vert_joints, vert_weights)): - dots['joint%dx' % i] = joints[:, 0] - dots['joint%dy' % i] = joints[:, 1] - dots['joint%dz' % i] = joints[:, 2] - dots['joint%dw' % i] = joints[:, 3] - dots['weight%dx' % i] = weights[:, 0] - dots['weight%dy' % i] = weights[:, 1] - dots['weight%dz' % i] = weights[:, 2] - dots['weight%dw' % i] = weights[:, 3] - for i, locs in enumerate(sk_vert_locs): - dots['sk%dx' % i] = locs[:, 0] - dots['sk%dy' % i] = locs[:, 1] - dots['sk%dz' % i] = locs[:, 2] - - unique_dots, inv_indices = np.unique(dots, return_inverse=True) - - loop_vidxs = inv_indices[loop_vidxs] - edge_vidxs = inv_indices[edge_vidxs] - - vert_locs = np.empty((len(unique_dots), 3), dtype=np.float32) - vert_locs[:, 0] = unique_dots['x'] - vert_locs[:, 1] = unique_dots['y'] - vert_locs[:, 2] = unique_dots['z'] - if len(vert_normals) != 0: - vert_normals = np.empty((len(unique_dots), 3), dtype=np.float32) - vert_normals[:, 0] = unique_dots['nx'] - vert_normals[:, 1] = unique_dots['ny'] - vert_normals[:, 2] = unique_dots['nz'] - for i in range(len(vert_joints)): - vert_joints[i] = np.empty((len(unique_dots), 4), dtype=np.uint32) - vert_joints[i][:, 0] = unique_dots['joint%dx' % i] - vert_joints[i][:, 1] = unique_dots['joint%dy' % i] - vert_joints[i][:, 2] = unique_dots['joint%dz' % i] - vert_joints[i][:, 3] = unique_dots['joint%dw' % i] - vert_weights[i] = np.empty((len(unique_dots), 4), dtype=np.float32) - vert_weights[i][:, 0] = unique_dots['weight%dx' % i] - vert_weights[i][:, 1] = unique_dots['weight%dy' % i] - vert_weights[i][:, 2] = unique_dots['weight%dz' % i] - vert_weights[i][:, 3] = unique_dots['weight%dw' % i] - for i in range(len(sk_vert_locs)): - sk_vert_locs[i] = np.empty((len(unique_dots), 3), dtype=np.float32) - sk_vert_locs[i][:, 0] = unique_dots['sk%dx' % i] - sk_vert_locs[i][:, 1] = unique_dots['sk%dy' % i] - sk_vert_locs[i][:, 2] = unique_dots['sk%dz' % i] - - return vert_locs, vert_normals, vert_joints, vert_weights, sk_vert_locs, loop_vidxs, edge_vidxs diff --git a/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_node.py b/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_node.py deleted file mode 100644 index cc9394f..0000000 --- a/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_node.py +++ /dev/null @@ -1,271 +0,0 @@ -# Copyright 2018-2021 The glTF-Blender-IO authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import bpy -from mathutils import Vector -from ..com.gltf2_blender_extras import set_extras -from .gltf2_blender_mesh import BlenderMesh -from .gltf2_blender_camera import BlenderCamera -from .gltf2_blender_light import BlenderLight -from .gltf2_blender_vnode import VNode -from ...io.imp.gltf2_io_user_extensions import import_user_extensions - -class BlenderNode(): - """Blender Node.""" - def __new__(cls, *args, **kwargs): - raise RuntimeError("%s should not be instantiated" % cls) - - @staticmethod - def create_vnode(gltf, vnode_id): - """Create VNode and all its descendants.""" - vnode = gltf.vnodes[vnode_id] - - gltf.display_current_node += 1 - if bpy.app.debug_value == 101: - gltf.log.critical("Node %d of %d (id %s)", gltf.display_current_node, len(gltf.vnodes), vnode_id) - - if vnode.type == VNode.Object: - gltf_node = gltf.data.nodes[vnode_id] if isinstance(vnode_id, int) else None - import_user_extensions('gather_import_node_before_hook', gltf, vnode, gltf_node) - obj = BlenderNode.create_object(gltf, vnode_id) - import_user_extensions('gather_import_node_after_hook', gltf, vnode, gltf_node, obj) - if vnode.is_arma: - BlenderNode.create_bones(gltf, vnode_id) - - elif vnode.type == VNode.Bone: - # These are created with their armature - pass - - elif vnode.type == VNode.DummyRoot: - # Don't actually create this - vnode.blender_object = None - - for child in vnode.children: - BlenderNode.create_vnode(gltf, child) - - @staticmethod - def create_object(gltf, vnode_id): - vnode = gltf.vnodes[vnode_id] - - if vnode.mesh_node_idx is not None: - obj = BlenderNode.create_mesh_object(gltf, vnode) - - elif vnode.camera_node_idx is not None: - pynode = gltf.data.nodes[vnode.camera_node_idx] - cam = BlenderCamera.create(gltf, vnode, pynode.camera) - name = vnode.name or cam.name - obj = bpy.data.objects.new(name, cam) - - # Since we create the actual Blender object after the create call, we call the hook here - import_user_extensions('gather_import_camera_after_hook', gltf, vnode, obj, cam) - - elif vnode.light_node_idx is not None: - pynode = gltf.data.nodes[vnode.light_node_idx] - light = BlenderLight.create(gltf, vnode, pynode.extensions['KHR_lights_punctual']['light']) - name = vnode.name or light.name - obj = bpy.data.objects.new(name, light) - - # Since we create the actual Blender object after the create call, we call the hook here - import_user_extensions('gather_import_light_after_hook', gltf, vnode, obj, light) - - elif vnode.is_arma: - armature = bpy.data.armatures.new(vnode.arma_name) - name = vnode.name or armature.name - obj = bpy.data.objects.new(name, armature) - - else: - # Empty - name = vnode.name or vnode.default_name - obj = bpy.data.objects.new(name, None) - obj.empty_display_size = BlenderNode.calc_empty_display_size(gltf, vnode_id) - - vnode.blender_object = obj - - # Set extras (if came from a glTF node) - if isinstance(vnode_id, int): - pynode = gltf.data.nodes[vnode_id] - set_extras(obj, pynode.extras) - - # Set transform - trans, rot, scale = vnode.trs() - obj.location = trans - obj.rotation_mode = 'QUATERNION' - obj.rotation_quaternion = rot - obj.scale = scale - - # Set parent - if vnode.parent is not None: - parent_vnode = gltf.vnodes[vnode.parent] - if parent_vnode.type == VNode.Object: - obj.parent = parent_vnode.blender_object - elif parent_vnode.type == VNode.Bone: - arma_vnode = gltf.vnodes[parent_vnode.bone_arma] - obj.parent = arma_vnode.blender_object - obj.parent_type = 'BONE' - obj.parent_bone = parent_vnode.blender_bone_name - - # Nodes with a bone parent need to be translated - # backwards from the tip to the root - obj.location += Vector((0, -parent_vnode.bone_length, 0)) - - bpy.data.scenes[gltf.blender_scene].collection.objects.link(obj) - - return obj - - @staticmethod - def calc_empty_display_size(gltf, vnode_id): - # Use min distance to parent/children to guess size - sizes = [] - vids = [vnode_id] + gltf.vnodes[vnode_id].children - for vid in vids: - vnode = gltf.vnodes[vid] - dist = vnode.trs()[0].length - sizes.append(dist * 0.4) - return max(min(sizes, default=1), 0.001) - - @staticmethod - def create_bones(gltf, arma_id): - arma = gltf.vnodes[arma_id] - blender_arma = arma.blender_object - armature = blender_arma.data - - # Find all bones for this arma - bone_ids = [] - def visit(id): # Depth-first walk - if gltf.vnodes[id].type == VNode.Bone: - bone_ids.append(id) - for child in gltf.vnodes[id].children: - visit(child) - for child in arma.children: - visit(child) - - # Switch into edit mode to create all edit bones - - if bpy.context.mode != 'OBJECT': - bpy.ops.object.mode_set(mode='OBJECT') - bpy.context.window.scene = bpy.data.scenes[gltf.blender_scene] - bpy.context.view_layer.objects.active = blender_arma - bpy.ops.object.mode_set(mode="EDIT") - - for id in bone_ids: - vnode = gltf.vnodes[id] - editbone = armature.edit_bones.new(vnode.name or vnode.default_name) - vnode.blender_bone_name = editbone.name - editbone.use_connect = False # TODO? - - # Give the position of the bone in armature space - arma_mat = vnode.editbone_arma_mat - editbone.head = arma_mat @ Vector((0, 0, 0)) - editbone.tail = arma_mat @ Vector((0, 1, 0)) - editbone.length = vnode.bone_length - editbone.align_roll(arma_mat @ Vector((0, 0, 1)) - editbone.head) - - if isinstance(id, int): - pynode = gltf.data.nodes[id] - set_extras(editbone, pynode.extras) - - # Set all bone parents - for id in bone_ids: - vnode = gltf.vnodes[id] - parent_vnode = gltf.vnodes[vnode.parent] - if parent_vnode.type == VNode.Bone: - editbone = armature.edit_bones[vnode.blender_bone_name] - parent_editbone = armature.edit_bones[parent_vnode.blender_bone_name] - editbone.parent = parent_editbone - - # Switch back to object mode and do pose bones - bpy.ops.object.mode_set(mode="OBJECT") - - for id in bone_ids: - vnode = gltf.vnodes[id] - pose_bone = blender_arma.pose.bones[vnode.blender_bone_name] - - # BoneTRS = EditBone * PoseBone - # Set PoseBone to make BoneTRS = vnode.trs. - t, r, s = vnode.trs() - et, er = vnode.editbone_trans, vnode.editbone_rot - pose_bone.location = er.conjugated() @ (t - et) - pose_bone.rotation_mode = 'QUATERNION' - pose_bone.rotation_quaternion = er.conjugated() @ r - pose_bone.scale = s - - if isinstance(id, int): - pynode = gltf.data.nodes[id] - set_extras(pose_bone, pynode.extras) - - @staticmethod - def create_mesh_object(gltf, vnode): - pynode = gltf.data.nodes[vnode.mesh_node_idx] - if not (0 <= pynode.mesh < len(gltf.data.meshes)): - # Avoid traceback for invalid gltf file: invalid reference to meshes array - # So return an empty blender object) - return bpy.data.objects.new(vnode.name or "Invalid Mesh Index", None) - pymesh = gltf.data.meshes[pynode.mesh] - - # Key to cache the Blender mesh by. - # Same cache key = instances of the same Blender mesh. - cache_key = None - if not pymesh.shapekey_names: - cache_key = (pynode.skin,) - else: - # Unlike glTF, all instances of a Blender mesh share shapekeys. - # So two instances that might have different morph weights need - # different cache keys. - if pynode.weight_animation is False: - cache_key = (pynode.skin, tuple(pynode.weights or [])) - else: - cache_key = None # don't use the cache at all - - if cache_key is not None and cache_key in pymesh.blender_name: - mesh = bpy.data.meshes[pymesh.blender_name[cache_key]] - else: - gltf.log.info("Blender create Mesh node %s", pymesh.name or pynode.mesh) - mesh = BlenderMesh.create(gltf, pynode.mesh, pynode.skin) - if cache_key is not None: - pymesh.blender_name[cache_key] = mesh.name - - name = vnode.name or mesh.name - obj = bpy.data.objects.new(name, mesh) - - if pymesh.shapekey_names: - BlenderNode.set_morph_weights(gltf, pynode, obj) - - if pynode.skin is not None: - BlenderNode.setup_skinning(gltf, pynode, obj) - - return obj - - @staticmethod - def set_morph_weights(gltf, pynode, obj): - pymesh = gltf.data.meshes[pynode.mesh] - weights = pynode.weights or pymesh.weights or [] - for i, weight in enumerate(weights): - if pymesh.shapekey_names[i] is not None: - kb = obj.data.shape_keys.key_blocks[pymesh.shapekey_names[i]] - # extend range if needed - if weight < kb.slider_min: kb.slider_min = weight - if weight > kb.slider_max: kb.slider_max = weight - kb.value = weight - - @staticmethod - def setup_skinning(gltf, pynode, obj): - pyskin = gltf.data.skins[pynode.skin] - - # Armature/bones should have already been created. - - # Create an Armature modifier - first_bone = gltf.vnodes[pyskin.joints[0]] - arma = gltf.vnodes[first_bone.bone_arma] - mod = obj.modifiers.new(name="Armature", type="ARMATURE") - mod.object = arma.blender_object diff --git a/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_pbrMetallicRoughness.py b/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_pbrMetallicRoughness.py deleted file mode 100644 index b119a1a..0000000 --- a/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_pbrMetallicRoughness.py +++ /dev/null @@ -1,578 +0,0 @@ -# Copyright 2018-2021 The glTF-Blender-IO authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import bpy -from ...io.com.gltf2_io import TextureInfo, MaterialPBRMetallicRoughness -from ..com.gltf2_blender_material_helpers import get_gltf_node_name -from .gltf2_blender_texture import texture -from .gltf2_blender_KHR_materials_clearcoat import \ - clearcoat, clearcoat_roughness, clearcoat_normal - - -class MaterialHelper: - """Helper class. Stores material stuff to be passed around everywhere.""" - def __init__(self, gltf, pymat, mat, vertex_color): - self.gltf = gltf - self.pymat = pymat - self.mat = mat - self.node_tree = mat.node_tree - self.vertex_color = vertex_color - if pymat.pbr_metallic_roughness is None: - pymat.pbr_metallic_roughness = \ - MaterialPBRMetallicRoughness.from_dict({}) - - def is_opaque(self): - alpha_mode = self.pymat.alpha_mode - return alpha_mode is None or alpha_mode == 'OPAQUE' - - def needs_emissive(self): - return ( - self.pymat.emissive_texture is not None or - (self.pymat.emissive_factor or [0, 0, 0]) != [0, 0, 0] - ) - - -def pbr_metallic_roughness(mh: MaterialHelper): - """Creates node tree for pbrMetallicRoughness materials.""" - pbr_node = mh.node_tree.nodes.new('ShaderNodeBsdfPrincipled') - pbr_node.location = 10, 300 - - make_output_nodes( - mh, - location=(250, 260), - shader_socket=pbr_node.outputs[0], - make_emission_socket=False, - make_alpha_socket=False, - ) - - locs = calc_locations(mh) - - emission( - mh, - location=locs['emission'], - color_socket=pbr_node.inputs['Emission'], - strength_socket=pbr_node.inputs.get('Emission Strength', None) - ) - - base_color( - mh, - location=locs['base_color'], - color_socket=pbr_node.inputs['Base Color'], - alpha_socket=pbr_node.inputs['Alpha'] if not mh.is_opaque() else None, - ) - - metallic_roughness( - mh, - location=locs['metallic_roughness'], - metallic_socket=pbr_node.inputs['Metallic'], - roughness_socket=pbr_node.inputs['Roughness'], - ) - - normal( - mh, - location=locs['normal'], - normal_socket=pbr_node.inputs['Normal'], - ) - - if mh.pymat.occlusion_texture is not None: - node = make_settings_node(mh) - node.location = 40, -370 - node.width = 180 - occlusion( - mh, - location=locs['occlusion'], - occlusion_socket=node.inputs['Occlusion'], - ) - - clearcoat( - mh, - location=locs['clearcoat'], - clearcoat_socket=pbr_node.inputs['Clearcoat'], - ) - - clearcoat_roughness( - mh, - location=locs['clearcoat_roughness'], - roughness_socket=pbr_node.inputs['Clearcoat Roughness'], - ) - - clearcoat_normal( - mh, - location=locs['clearcoat_normal'], - normal_socket=pbr_node.inputs['Clearcoat Normal'], - ) - - -def calc_locations(mh): - """Calculate locations to place each bit of the node graph at.""" - # Lay the blocks out top-to-bottom, aligned on the right - x = -200 - y = 0 - height = 460 # height of each block - locs = {} - - try: - clearcoat_ext = mh.pymat.extensions['KHR_materials_clearcoat'] - except Exception: - clearcoat_ext = {} - - locs['base_color'] = (x, y) - if mh.pymat.pbr_metallic_roughness.base_color_texture is not None or mh.vertex_color: - y -= height - locs['metallic_roughness'] = (x, y) - if mh.pymat.pbr_metallic_roughness.metallic_roughness_texture is not None: - y -= height - locs['clearcoat'] = (x, y) - if 'clearcoatTexture' in clearcoat_ext: - y -= height - locs['clearcoat_roughness'] = (x, y) - if 'clearcoatRoughnessTexture' in clearcoat_ext: - y -= height - locs['emission'] = (x, y) - if mh.pymat.emissive_texture is not None: - y -= height - locs['normal'] = (x, y) - if mh.pymat.normal_texture is not None: - y -= height - locs['clearcoat_normal'] = (x, y) - if 'clearcoatNormalTexture' in clearcoat_ext: - y -= height - locs['occlusion'] = (x, y) - if mh.pymat.occlusion_texture is not None: - y -= height - - # Center things - total_height = -y - y_offset = total_height / 2 - 20 - for key in locs: - x, y = locs[key] - locs[key] = (x, y + y_offset) - - return locs - - -# These functions each create one piece of the node graph, slotting -# their outputs into the given socket, or setting its default value. -# location is roughly the upper-right corner of where to put nodes. - - -# [Texture] => [Emissive Factor] => -def emission(mh: MaterialHelper, location, color_socket, strength_socket=None): - x, y = location - emissive_factor = mh.pymat.emissive_factor or [0, 0, 0] - - if color_socket is None: - return - - if mh.pymat.emissive_texture is None: - color_socket.default_value = emissive_factor + [1] - return - - # Put grayscale emissive factors into the Emission Strength - e0, e1, e2 = emissive_factor - if strength_socket and e0 == e1 == e2: - strength_socket.default_value = e0 - - # Otherwise, use a multiply node for it - else: - if emissive_factor != [1, 1, 1]: - node = mh.node_tree.nodes.new('ShaderNodeMixRGB') - node.label = 'Emissive Factor' - node.location = x - 140, y - node.blend_type = 'MULTIPLY' - # Outputs - mh.node_tree.links.new(color_socket, node.outputs[0]) - # Inputs - node.inputs['Fac'].default_value = 1.0 - color_socket = node.inputs['Color1'] - node.inputs['Color2'].default_value = emissive_factor + [1] - - x -= 200 - - texture( - mh, - tex_info=mh.pymat.emissive_texture, - label='EMISSIVE', - location=(x, y), - color_socket=color_socket, - ) - - -# [Texture] => [Mix Colors] => [Color Factor] => -# [Vertex Color] => [Mix Alphas] => [Alpha Factor] => -def base_color( - mh: MaterialHelper, - location, - color_socket, - alpha_socket=None, - is_diffuse=False, -): - """Handle base color (= baseColorTexture * vertexColor * baseColorFactor).""" - x, y = location - pbr = mh.pymat.pbr_metallic_roughness - if not is_diffuse: - base_color_factor = pbr.base_color_factor - base_color_texture = pbr.base_color_texture - else: - # Handle pbrSpecularGlossiness's diffuse with this function too, - # since it's almost exactly the same as base color. - base_color_factor = \ - mh.pymat.extensions['KHR_materials_pbrSpecularGlossiness'] \ - .get('diffuseFactor', [1, 1, 1, 1]) - base_color_texture = \ - mh.pymat.extensions['KHR_materials_pbrSpecularGlossiness'] \ - .get('diffuseTexture', None) - if base_color_texture is not None: - base_color_texture = TextureInfo.from_dict(base_color_texture) - - if base_color_factor is None: - base_color_factor = [1, 1, 1, 1] - - if base_color_texture is None and not mh.vertex_color: - color_socket.default_value = base_color_factor[:3] + [1] - if alpha_socket is not None: - alpha_socket.default_value = base_color_factor[3] - return - - # Mix in base color factor - needs_color_factor = base_color_factor[:3] != [1, 1, 1] - needs_alpha_factor = base_color_factor[3] != 1.0 and alpha_socket is not None - if needs_color_factor or needs_alpha_factor: - if needs_color_factor: - node = mh.node_tree.nodes.new('ShaderNodeMixRGB') - node.label = 'Color Factor' - node.location = x - 140, y - node.blend_type = 'MULTIPLY' - # Outputs - mh.node_tree.links.new(color_socket, node.outputs[0]) - # Inputs - node.inputs['Fac'].default_value = 1.0 - color_socket = node.inputs['Color1'] - node.inputs['Color2'].default_value = base_color_factor[:3] + [1] - - if needs_alpha_factor: - node = mh.node_tree.nodes.new('ShaderNodeMath') - node.label = 'Alpha Factor' - node.location = x - 140, y - 200 - # Outputs - mh.node_tree.links.new(alpha_socket, node.outputs[0]) - # Inputs - node.operation = 'MULTIPLY' - alpha_socket = node.inputs[0] - node.inputs[1].default_value = base_color_factor[3] - - x -= 200 - - # These are where the texture/vertex color node will put its output. - texture_color_socket = color_socket - texture_alpha_socket = alpha_socket - vcolor_color_socket = color_socket - vcolor_alpha_socket = alpha_socket - - # Mix texture and vertex color together - if base_color_texture is not None and mh.vertex_color: - node = mh.node_tree.nodes.new('ShaderNodeMixRGB') - node.label = 'Mix Vertex Color' - node.location = x - 140, y - node.blend_type = 'MULTIPLY' - # Outputs - mh.node_tree.links.new(color_socket, node.outputs[0]) - # Inputs - node.inputs['Fac'].default_value = 1.0 - texture_color_socket = node.inputs['Color1'] - vcolor_color_socket = node.inputs['Color2'] - - if alpha_socket is not None: - node = mh.node_tree.nodes.new('ShaderNodeMath') - node.label = 'Mix Vertex Alpha' - node.location = x - 140, y - 200 - node.operation = 'MULTIPLY' - # Outputs - mh.node_tree.links.new(alpha_socket, node.outputs[0]) - # Inputs - texture_alpha_socket = node.inputs[0] - vcolor_alpha_socket = node.inputs[1] - - x -= 200 - - # Vertex Color - if mh.vertex_color: - node = mh.node_tree.nodes.new('ShaderNodeVertexColor') - node.layer_name = 'Col' - node.location = x - 250, y - 240 - # Outputs - mh.node_tree.links.new(vcolor_color_socket, node.outputs['Color']) - if vcolor_alpha_socket is not None: - mh.node_tree.links.new(vcolor_alpha_socket, node.outputs['Alpha']) - - x -= 280 - - # Texture - if base_color_texture is not None: - texture( - mh, - tex_info=base_color_texture, - label='BASE COLOR' if not is_diffuse else 'DIFFUSE', - location=(x, y), - color_socket=texture_color_socket, - alpha_socket=texture_alpha_socket, - ) - - -# [Texture] => [Separate GB] => [Metal/Rough Factor] => -def metallic_roughness(mh: MaterialHelper, location, metallic_socket, roughness_socket): - x, y = location - pbr = mh.pymat.pbr_metallic_roughness - metal_factor = pbr.metallic_factor - rough_factor = pbr.roughness_factor - if metal_factor is None: - metal_factor = 1.0 - if rough_factor is None: - rough_factor = 1.0 - - if pbr.metallic_roughness_texture is None: - metallic_socket.default_value = metal_factor - roughness_socket.default_value = rough_factor - return - - if metal_factor != 1.0 or rough_factor != 1.0: - # Mix metal factor - if metal_factor != 1.0: - node = mh.node_tree.nodes.new('ShaderNodeMath') - node.label = 'Metallic Factor' - node.location = x - 140, y - node.operation = 'MULTIPLY' - # Outputs - mh.node_tree.links.new(metallic_socket, node.outputs[0]) - # Inputs - metallic_socket = node.inputs[0] - node.inputs[1].default_value = metal_factor - - # Mix rough factor - if rough_factor != 1.0: - node = mh.node_tree.nodes.new('ShaderNodeMath') - node.label = 'Roughness Factor' - node.location = x - 140, y - 200 - node.operation = 'MULTIPLY' - # Outputs - mh.node_tree.links.new(roughness_socket, node.outputs[0]) - # Inputs - roughness_socket = node.inputs[0] - node.inputs[1].default_value = rough_factor - - x -= 200 - - # Separate RGB - node = mh.node_tree.nodes.new('ShaderNodeSeparateRGB') - node.location = x - 150, y - 75 - # Outputs - mh.node_tree.links.new(metallic_socket, node.outputs['B']) - mh.node_tree.links.new(roughness_socket, node.outputs['G']) - # Inputs - color_socket = node.inputs[0] - - x -= 200 - - texture( - mh, - tex_info=pbr.metallic_roughness_texture, - label='METALLIC ROUGHNESS', - location=(x, y), - is_data=True, - color_socket=color_socket, - ) - - -# [Texture] => [Normal Map] => -def normal(mh: MaterialHelper, location, normal_socket): - x,y = location - tex_info = mh.pymat.normal_texture - - if tex_info is None: - return - - # Normal map - node = mh.node_tree.nodes.new('ShaderNodeNormalMap') - node.location = x - 150, y - 40 - # Set UVMap - uv_idx = tex_info.tex_coord or 0 - try: - uv_idx = tex_info.extensions['KHR_texture_transform']['texCoord'] - except Exception: - pass - node.uv_map = 'UVMap' if uv_idx == 0 else 'UVMap.%03d' % uv_idx - # Set strength - scale = tex_info.scale - scale = scale if scale is not None else 1 - node.inputs['Strength'].default_value = scale - # Outputs - mh.node_tree.links.new(normal_socket, node.outputs['Normal']) - # Inputs - color_socket = node.inputs['Color'] - - x -= 200 - - texture( - mh, - tex_info=tex_info, - label='NORMALMAP', - location=(x, y), - is_data=True, - color_socket=color_socket, - ) - - -# [Texture] => [Separate R] => [Mix Strength] => -def occlusion(mh: MaterialHelper, location, occlusion_socket): - x, y = location - - if mh.pymat.occlusion_texture is None: - return - - strength = mh.pymat.occlusion_texture.strength - if strength is None: strength = 1.0 - if strength != 1.0: - # Mix with white - node = mh.node_tree.nodes.new('ShaderNodeMixRGB') - node.label = 'Occlusion Strength' - node.location = x - 140, y - node.blend_type = 'MIX' - # Outputs - mh.node_tree.links.new(occlusion_socket, node.outputs[0]) - # Inputs - node.inputs['Fac'].default_value = strength - node.inputs['Color1'].default_value = [1, 1, 1, 1] - occlusion_socket = node.inputs['Color2'] - - x -= 200 - - # Separate RGB - node = mh.node_tree.nodes.new('ShaderNodeSeparateRGB') - node.location = x - 150, y - 75 - # Outputs - mh.node_tree.links.new(occlusion_socket, node.outputs['R']) - # Inputs - color_socket = node.inputs[0] - - x -= 200 - - texture( - mh, - tex_info=mh.pymat.occlusion_texture, - label='OCCLUSION', - location=(x, y), - is_data=True, - color_socket=color_socket, - ) - - -# => [Add Emission] => [Mix Alpha] => [Material Output] -def make_output_nodes( - mh: MaterialHelper, - location, - shader_socket, - make_emission_socket, - make_alpha_socket, -): - """ - Creates the Material Output node and connects shader_socket to it. - If requested, it can also create places to hookup the emission/alpha - in between shader_socket and the Output node too. - - :return: a pair containing the sockets you should put emission and alpha - in (None if not requested). - """ - x, y = location - emission_socket = None - alpha_socket = None - - # Create an Emission node and add it to the shader. - if make_emission_socket: - # Emission - node = mh.node_tree.nodes.new('ShaderNodeEmission') - node.location = x + 50, y + 250 - # Inputs - emission_socket = node.inputs[0] - # Outputs - emission_output = node.outputs[0] - - # Add - node = mh.node_tree.nodes.new('ShaderNodeAddShader') - node.location = x + 250, y + 160 - # Inputs - mh.node_tree.links.new(node.inputs[0], emission_output) - mh.node_tree.links.new(node.inputs[1], shader_socket) - # Outputs - shader_socket = node.outputs[0] - - if make_alpha_socket: - x += 200 - y += 175 - else: - x += 380 - y += 125 - - # Mix with a Transparent BSDF. Mixing factor is the alpha value. - if make_alpha_socket: - # Transparent BSDF - node = mh.node_tree.nodes.new('ShaderNodeBsdfTransparent') - node.location = x + 100, y - 350 - # Outputs - transparent_out = node.outputs[0] - - # Mix - node = mh.node_tree.nodes.new('ShaderNodeMixShader') - node.location = x + 340, y - 180 - # Inputs - alpha_socket = node.inputs[0] - mh.node_tree.links.new(node.inputs[1], transparent_out) - mh.node_tree.links.new(node.inputs[2], shader_socket) - # Outputs - shader_socket = node.outputs[0] - - - x += 480 - y -= 210 - - # Material output - node = mh.node_tree.nodes.new('ShaderNodeOutputMaterial') - node.location = x + 70, y + 10 - # Outputs - mh.node_tree.links.new(node.inputs[0], shader_socket) - - return emission_socket, alpha_socket - - -def make_settings_node(mh): - """ - Make a Group node with a hookup for Occlusion. No effect in Blender, but - used to tell the exporter what the occlusion map should be. - """ - node = mh.node_tree.nodes.new('ShaderNodeGroup') - node.node_tree = get_settings_group() - return node - - -def get_settings_group(): - gltf_node_group_name = get_gltf_node_name() - if gltf_node_group_name in bpy.data.node_groups: - gltf_node_group = bpy.data.node_groups[gltf_node_group_name] - else: - # Create a new node group - gltf_node_group = bpy.data.node_groups.new(gltf_node_group_name, 'ShaderNodeTree') - gltf_node_group.inputs.new("NodeSocketFloat", "Occlusion") - gltf_node_group.nodes.new('NodeGroupOutput') - gltf_node_group_input = gltf_node_group.nodes.new('NodeGroupInput') - gltf_node_group_input.location = -200, 0 - return gltf_node_group diff --git a/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_scene.py b/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_scene.py deleted file mode 100644 index 327f6eb..0000000 --- a/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_scene.py +++ /dev/null @@ -1,113 +0,0 @@ -# Copyright 2018-2021 The glTF-Blender-IO authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import bpy - -from .gltf2_blender_node import BlenderNode -from .gltf2_blender_animation import BlenderAnimation -from .gltf2_blender_vnode import VNode, compute_vnodes -from ..com.gltf2_blender_extras import set_extras -from ...io.imp.gltf2_io_user_extensions import import_user_extensions - - -class BlenderScene(): - """Blender Scene.""" - def __new__(cls, *args, **kwargs): - raise RuntimeError("%s should not be instantiated" % cls) - - @staticmethod - def create(gltf): - """Scene creation.""" - scene = bpy.context.scene - gltf.blender_scene = scene.name - if bpy.context.collection.name in bpy.data.collections: # avoid master collection - gltf.blender_active_collection = bpy.context.collection.name - if scene.render.engine not in ['CYCLES', 'BLENDER_EEVEE']: - scene.render.engine = "BLENDER_EEVEE" - - if gltf.data.scene is not None: - import_user_extensions('gather_import_scene_before_hook', gltf, gltf.data.scenes[gltf.data.scene], scene) - pyscene = gltf.data.scenes[gltf.data.scene] - set_extras(scene, pyscene.extras) - - compute_vnodes(gltf) - - gltf.display_current_node = 0 # for debugging - BlenderNode.create_vnode(gltf, 'root') - - # User extensions before scene creation - import_user_extensions('gather_import_scene_after_nodes_hook', gltf, gltf.data.scenes[gltf.data.scene], scene) - - # User extensions after scene creation - BlenderScene.create_animations(gltf) - - import_user_extensions('gather_import_scene_after_animation_hook', gltf, gltf.data.scenes[gltf.data.scene], scene) - - if bpy.context.mode != 'OBJECT': - bpy.ops.object.mode_set(mode='OBJECT') - BlenderScene.select_imported_objects(gltf) - BlenderScene.set_active_object(gltf) - - @staticmethod - def create_animations(gltf): - """Create animations.""" - if gltf.data.animations: - # NLA tracks are added bottom to top, so create animations in - # reverse so the first winds up on top - for anim_idx in reversed(range(len(gltf.data.animations))): - BlenderAnimation.anim(gltf, anim_idx) - - # Restore first animation - anim_name = gltf.data.animations[0].track_name - BlenderAnimation.restore_animation(gltf, anim_name) - - @staticmethod - def select_imported_objects(gltf): - """Select all (and only) the imported objects.""" - if bpy.ops.object.select_all.poll(): - bpy.ops.object.select_all(action='DESELECT') - - for vnode in gltf.vnodes.values(): - if vnode.type == VNode.Object: - vnode.blender_object.select_set(state=True) - - @staticmethod - def set_active_object(gltf): - """Make the first root object from the default glTF scene active. - If no default scene, use the first scene, or just any root object. - """ - vnode = None - - if gltf.data.scene is not None: - pyscene = gltf.data.scenes[gltf.data.scene] - if pyscene.nodes: - vnode = gltf.vnodes[pyscene.nodes[0]] - - if not vnode: - for pyscene in gltf.data.scenes or []: - if pyscene.nodes: - vnode = gltf.vnodes[pyscene.nodes[0]] - break - - if not vnode: - vnode = gltf.vnodes['root'] - if vnode.type == VNode.DummyRoot: - if not vnode.children: - return # no nodes - vnode = gltf.vnodes[vnode.children[0]] - - if vnode.type == VNode.Bone: - vnode = gltf.vnodes[vnode.bone_arma] - - bpy.context.view_layer.objects.active = vnode.blender_object diff --git a/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_texture.py b/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_texture.py deleted file mode 100644 index d2590f0..0000000 --- a/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_texture.py +++ /dev/null @@ -1,194 +0,0 @@ -# Copyright 2018-2021 The glTF-Blender-IO authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import bpy - -from .gltf2_blender_image import BlenderImage -from ..com.gltf2_blender_conversion import texture_transform_gltf_to_blender -from ...io.com.gltf2_io import Sampler -from ...io.com.gltf2_io_constants import TextureFilter, TextureWrap -from ...io.imp.gltf2_io_user_extensions import import_user_extensions - -def texture( - mh, - tex_info, - location, # Upper-right corner of the TexImage node - label, # Label for the TexImg node - color_socket, - alpha_socket=None, - is_data=False, -): - """Creates nodes for a TextureInfo and hooks up the color/alpha outputs.""" - x, y = location - pytexture = mh.gltf.data.textures[tex_info.index] - - import_user_extensions('gather_import_texture_before_hook', mh.gltf, pytexture, mh, tex_info, location, label, color_socket, alpha_socket, is_data) - - if pytexture.sampler is not None: - pysampler = mh.gltf.data.samplers[pytexture.sampler] - else: - pysampler = Sampler.from_dict({}) - - needs_uv_map = False # whether to create UVMap node - - # Image Texture - tex_img = mh.node_tree.nodes.new('ShaderNodeTexImage') - tex_img.location = x - 240, y - tex_img.label = label - # Get image - if pytexture.source is not None: - BlenderImage.create(mh.gltf, pytexture.source) - pyimg = mh.gltf.data.images[pytexture.source] - blender_image_name = pyimg.blender_image_name - if blender_image_name: - tex_img.image = bpy.data.images[blender_image_name] - # Set colorspace for data images - if is_data: - if tex_img.image: - tex_img.image.colorspace_settings.is_data = True - # Set filtering - set_filtering(tex_img, pysampler) - # Outputs - mh.node_tree.links.new(color_socket, tex_img.outputs['Color']) - if alpha_socket is not None: - mh.node_tree.links.new(alpha_socket, tex_img.outputs['Alpha']) - # Inputs - uv_socket = tex_img.inputs[0] - - x -= 340 - - # Do wrapping - wrap_s = pysampler.wrap_s - wrap_t = pysampler.wrap_t - if wrap_s is None: - wrap_s = TextureWrap.Repeat - if wrap_t is None: - wrap_t = TextureWrap.Repeat - # If wrapping is REPEATxREPEAT or CLAMPxCLAMP, just set tex_img.extension - if (wrap_s, wrap_t) == (TextureWrap.Repeat, TextureWrap.Repeat): - tex_img.extension = 'REPEAT' - elif (wrap_s, wrap_t) == (TextureWrap.ClampToEdge, TextureWrap.ClampToEdge): - tex_img.extension = 'EXTEND' - else: - # Otherwise separate the UV components and use math nodes to compute - # the wrapped UV coordinates - # => [Separate XYZ] => [Wrap for S] => [Combine XYZ] => - # => [Wrap for T] => - - tex_img.extension = 'EXTEND' # slightly better errors near the edge than REPEAT - - # Combine XYZ - com_uv = mh.node_tree.nodes.new('ShaderNodeCombineXYZ') - com_uv.location = x - 140, y - 100 - mh.node_tree.links.new(uv_socket, com_uv.outputs[0]) - u_socket = com_uv.inputs[0] - v_socket = com_uv.inputs[1] - x -= 200 - - for i in [0, 1]: - wrap = [wrap_s, wrap_t][i] - socket = [u_socket, v_socket][i] - if wrap == TextureWrap.Repeat: - # WRAP node for REPEAT - math = mh.node_tree.nodes.new('ShaderNodeMath') - math.location = x - 140, y + 30 - i*200 - math.operation = 'WRAP' - math.inputs[1].default_value = 0 - math.inputs[2].default_value = 1 - mh.node_tree.links.new(socket, math.outputs[0]) - socket = math.inputs[0] - elif wrap == TextureWrap.MirroredRepeat: - # PINGPONG node for MIRRORED_REPEAT - math = mh.node_tree.nodes.new('ShaderNodeMath') - math.location = x - 140, y + 30 - i*200 - math.operation = 'PINGPONG' - math.inputs[1].default_value = 1 - mh.node_tree.links.new(socket, math.outputs[0]) - socket = math.inputs[0] - else: - # Pass-through CLAMP since the tex_img node is set to EXTEND - pass - if i == 0: - u_socket = socket - else: - v_socket = socket - x -= 200 - - # Separate XYZ - sep_uv = mh.node_tree.nodes.new('ShaderNodeSeparateXYZ') - sep_uv.location = x - 140, y - 100 - mh.node_tree.links.new(u_socket, sep_uv.outputs[0]) - mh.node_tree.links.new(v_socket, sep_uv.outputs[1]) - uv_socket = sep_uv.inputs[0] - x -= 200 - - needs_uv_map = True - - # UV Transform (for KHR_texture_transform) - needs_tex_transform = 'KHR_texture_transform' in (tex_info.extensions or {}) - if needs_tex_transform: - mapping = mh.node_tree.nodes.new('ShaderNodeMapping') - mapping.location = x - 160, y + 30 - mapping.vector_type = 'POINT' - # Outputs - mh.node_tree.links.new(uv_socket, mapping.outputs[0]) - # Inputs - uv_socket = mapping.inputs[0] - - transform = tex_info.extensions['KHR_texture_transform'] - transform = texture_transform_gltf_to_blender(transform) - mapping.inputs['Location'].default_value[0] = transform['offset'][0] - mapping.inputs['Location'].default_value[1] = transform['offset'][1] - mapping.inputs['Rotation'].default_value[2] = transform['rotation'] - mapping.inputs['Scale'].default_value[0] = transform['scale'][0] - mapping.inputs['Scale'].default_value[1] = transform['scale'][1] - - x -= 260 - needs_uv_map = True - - # UV Map - uv_idx = tex_info.tex_coord or 0 - try: - uv_idx = tex_info.extensions['KHR_texture_transform']['texCoord'] - except Exception: - pass - if uv_idx != 0 or needs_uv_map: - uv_map = mh.node_tree.nodes.new('ShaderNodeUVMap') - uv_map.location = x - 160, y - 70 - uv_map.uv_map = 'UVMap' if uv_idx == 0 else 'UVMap.%03d' % uv_idx - # Outputs - mh.node_tree.links.new(uv_socket, uv_map.outputs[0]) - - import_user_extensions('gather_import_texture_after_hook', mh.gltf, pytexture, mh.node_tree, mh, tex_info, location, label, color_socket, alpha_socket, is_data) - -def set_filtering(tex_img, pysampler): - """Set the filtering/interpolation on an Image Texture from the glTf sampler.""" - minf = pysampler.min_filter - magf = pysampler.mag_filter - - # Ignore mipmapping - if minf in [TextureFilter.NearestMipmapNearest, TextureFilter.NearestMipmapLinear]: - minf = TextureFilter.Nearest - elif minf in [TextureFilter.LinearMipmapNearest, TextureFilter.LinearMipmapLinear]: - minf = TextureFilter.Linear - - # If both are nearest or the only specified one was nearest, use nearest. - if (minf, magf) in [ - (TextureFilter.Nearest, TextureFilter.Nearest), - (TextureFilter.Nearest, None), - (None, TextureFilter.Nearest), - ]: - tex_img.interpolation = 'Closest' - else: - tex_img.interpolation = 'Linear' diff --git a/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_vnode.py b/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_vnode.py deleted file mode 100644 index ebbced7..0000000 --- a/addons/io_sketchfab_plugin/blender/imp/gltf2_blender_vnode.py +++ /dev/null @@ -1,522 +0,0 @@ -# Copyright 2018-2021 The glTF-Blender-IO authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import bpy -from mathutils import Vector, Quaternion, Matrix -from ...io.imp.gltf2_io_binary import BinaryData - -from ..com.gltf2_blender_math import scale_rot_swap_matrix, nearby_signed_perm_matrix - -def compute_vnodes(gltf): - """Computes the tree of virtual nodes. - Copies the glTF nodes into a tree of VNodes, then performs a series of - passes to transform it into a form that we can import into Blender. - """ - init_vnodes(gltf) - mark_bones_and_armas(gltf) - move_skinned_meshes(gltf) - fixup_multitype_nodes(gltf) - correct_cameras_and_lights(gltf) - pick_bind_pose(gltf) - prettify_bones(gltf) - calc_bone_matrices(gltf) - - -class VNode: - """A "virtual" node. - These are what eventually get turned into nodes - in the Blender scene. - """ - # Types - Object = 0 - Bone = 1 - DummyRoot = 2 - - def __init__(self): - self.name = None - self.default_name = 'Node' # fallback when no name - self.children = [] - self.parent = None - self.type = VNode.Object - self.is_arma = False - self.base_trs = ( - Vector((0, 0, 0)), - Quaternion((1, 0, 0, 0)), - Vector((1, 1, 1)), - ) - # Additional rotations before/after the base TRS. - # Allows per-vnode axis adjustment. See local_rotation. - self.rotation_after = Quaternion((1, 0, 0, 0)) - self.rotation_before = Quaternion((1, 0, 0, 0)) - - # Indices of the glTF node where the mesh, etc. came from. - # (They can get moved around.) - self.mesh_node_idx = None - self.camera_node_idx = None - self.light_node_idx = None - - def trs(self): - # (final TRS) = (rotation after) (base TRS) (rotation before) - t, r, s = self.base_trs - m = scale_rot_swap_matrix(self.rotation_before) - return ( - self.rotation_after @ t, - self.rotation_after @ r @ self.rotation_before, - m @ s, - ) - - def base_locs_to_final_locs(self, base_locs): - ra = self.rotation_after - return [ra @ loc for loc in base_locs] - - def base_rots_to_final_rots(self, base_rots): - ra, rb = self.rotation_after, self.rotation_before - return [ra @ rot @ rb for rot in base_rots] - - def base_scales_to_final_scales(self, base_scales): - m = scale_rot_swap_matrix(self.rotation_before) - return [m @ scale for scale in base_scales] - -def local_rotation(gltf, vnode_id, rot): - """Appends a local rotation to vnode's world transform: - (new world transform) = (old world transform) @ (rot) - without changing the world transform of vnode's children. - - For correctness, rot must be a signed permutation of the axes - (eg. (X Y Z)->(X -Z Y)) OR vnode's scale must always be uniform. - """ - gltf.vnodes[vnode_id].rotation_before @= rot - - # Append the inverse rotation after children's TRS to cancel it out. - rot_inv = rot.conjugated() - for child in gltf.vnodes[vnode_id].children: - gltf.vnodes[child].rotation_after = \ - rot_inv @ gltf.vnodes[child].rotation_after - - -def init_vnodes(gltf): - # Map of all VNodes. The keys are arbitrary IDs. - # Nodes coming from glTF use the index into gltf.data.nodes for an ID. - gltf.vnodes = {} - - for i, pynode in enumerate(gltf.data.nodes or []): - vnode = VNode() - gltf.vnodes[i] = vnode - vnode.name = pynode.name - vnode.default_name = 'Node_%d' % i - vnode.children = list(pynode.children or []) - vnode.base_trs = get_node_trs(gltf, pynode) - if pynode.mesh is not None: - vnode.mesh_node_idx = i - if pynode.camera is not None: - vnode.camera_node_idx = i - if 'KHR_lights_punctual' in (pynode.extensions or {}): - vnode.light_node_idx = i - - for id in gltf.vnodes: - for child in gltf.vnodes[id].children: - assert gltf.vnodes[child].parent is None - gltf.vnodes[child].parent = id - - # Inserting a root node will simplify things. - roots = [id for id in gltf.vnodes if gltf.vnodes[id].parent is None] - gltf.vnodes['root'] = VNode() - gltf.vnodes['root'].type = VNode.DummyRoot - gltf.vnodes['root'].default_name = 'Root' - gltf.vnodes['root'].children = roots - for root in roots: - gltf.vnodes[root].parent = 'root' - -def get_node_trs(gltf, pynode): - if pynode.matrix is not None: - m = gltf.matrix_gltf_to_blender(pynode.matrix) - return m.decompose() - - t = gltf.loc_gltf_to_blender(pynode.translation or [0, 0, 0]) - r = gltf.quaternion_gltf_to_blender(pynode.rotation or [0, 0, 0, 1]) - s = gltf.scale_gltf_to_blender(pynode.scale or [1, 1, 1]) - return t, r, s - - -def mark_bones_and_armas(gltf): - """ - Mark nodes as armatures so that every node that is used as joint is a - descendant of an armature. Mark everything between an armature and a - joint as a bone. - """ - for skin in gltf.data.skins or []: - descendants = list(skin.joints) - if skin.skeleton is not None: - descendants.append(skin.skeleton) - arma_id = deepest_common_ancestor(gltf, descendants) - - if arma_id in skin.joints: - arma_id = gltf.vnodes[arma_id].parent - - if gltf.vnodes[arma_id].type != VNode.Bone: - gltf.vnodes[arma_id].type = VNode.Object - gltf.vnodes[arma_id].is_arma = True - gltf.vnodes[arma_id].arma_name = skin.name or 'Armature' - - for joint in skin.joints: - while joint != arma_id: - gltf.vnodes[joint].type = VNode.Bone - gltf.vnodes[joint].is_arma = False - joint = gltf.vnodes[joint].parent - - # Mark the armature each bone is a descendant of. - - def visit(vnode_id, cur_arma): # Depth-first walk - vnode = gltf.vnodes[vnode_id] - - if vnode.is_arma: - cur_arma = vnode_id - elif vnode.type == VNode.Bone: - vnode.bone_arma = cur_arma - else: - cur_arma = None - - for child in vnode.children: - visit(child, cur_arma) - - visit('root', cur_arma=None) - -def deepest_common_ancestor(gltf, vnode_ids): - """Find the deepest (improper) ancestor of a set of vnodes.""" - path_to_ancestor = [] # path to deepest ancestor so far - for vnode_id in vnode_ids: - path = path_from_root(gltf, vnode_id) - if not path_to_ancestor: - path_to_ancestor = path - else: - path_to_ancestor = longest_common_prefix(path, path_to_ancestor) - return path_to_ancestor[-1] - -def path_from_root(gltf, vnode_id): - """Returns the ids of all vnodes from the root to vnode_id.""" - path = [] - while vnode_id is not None: - path.append(vnode_id) - vnode_id = gltf.vnodes[vnode_id].parent - path.reverse() - return path - -def longest_common_prefix(list1, list2): - i = 0 - while i != min(len(list1), len(list2)): - if list1[i] != list2[i]: - break - i += 1 - return list1[:i] - - -def move_skinned_meshes(gltf): - """ - In glTF, where in the node hierarchy a skinned mesh is instantiated has - no effect on its world space position: only the world transforms of the - joints in its skin affect it. - - To do this in Blender: - * Move a skinned mesh to become a child of the armature that skins it. - Have to ensure the mesh and arma have the same world transform. - * When we do mesh creation, we will also need to put all the verts in - the bind pose in arma space. - """ - ids = list(gltf.vnodes.keys()) - for id in ids: - vnode = gltf.vnodes[id] - - if vnode.mesh_node_idx is None: - continue - - skin = gltf.data.nodes[vnode.mesh_node_idx].skin - if skin is None: - continue - - pyskin = gltf.data.skins[skin] - arma = gltf.vnodes[pyskin.joints[0]].bone_arma - - # First try moving the whole node if we can do it without - # messing anything up. - is_animated = ( - gltf.data.animations and - isinstance(id, int) and - gltf.data.nodes[id].animations - ) - ok_to_move = ( - not is_animated and - vnode.type == VNode.Object and - not vnode.is_arma and - not vnode.children and - vnode.camera_node_idx is None and - vnode.light_node_idx is None - ) - if ok_to_move: - reparent(gltf, id, new_parent=arma) - vnode.base_trs = ( - Vector((0, 0, 0)), - Quaternion((1, 0, 0, 0)), - Vector((1, 1, 1)), - ) - continue - - # Otherwise, create a new child of the arma and move - # the mesh instance there, leaving the node behind. - new_id = str(id) + '.skinned' - gltf.vnodes[new_id] = VNode() - gltf.vnodes[new_id].parent = arma - gltf.vnodes[arma].children.append(new_id) - gltf.vnodes[new_id].mesh_node_idx = vnode.mesh_node_idx - vnode.mesh_node_idx = None - -def reparent(gltf, vnode_id, new_parent): - """Moves a VNode to a new parent.""" - vnode = gltf.vnodes[vnode_id] - if vnode.parent == new_parent: - return - if vnode.parent is not None: - parent_vnode = gltf.vnodes[vnode.parent] - index = parent_vnode.children.index(vnode_id) - del parent_vnode.children[index] - vnode.parent = new_parent - gltf.vnodes[new_parent].children.append(vnode_id) - - - -def fixup_multitype_nodes(gltf): - """ - Blender only lets each object have one of: an armature, a mesh, a - camera, a light. Also bones cannot have any of these either. Find any - nodes like this and move the mesh/camera/light onto new children. - """ - ids = list(gltf.vnodes.keys()) - for id in ids: - vnode = gltf.vnodes[id] - - needs_move = False - - if vnode.is_arma or vnode.type == VNode.Bone: - needs_move = True - - if vnode.mesh_node_idx is not None: - if needs_move: - new_id = str(id) + '.mesh' - gltf.vnodes[new_id] = VNode() - gltf.vnodes[new_id].mesh_node_idx = vnode.mesh_node_idx - gltf.vnodes[new_id].parent = id - vnode.children.append(new_id) - vnode.mesh_node_idx = None - needs_move = True - - if vnode.camera_node_idx is not None: - if needs_move: - new_id = str(id) + '.camera' - gltf.vnodes[new_id] = VNode() - gltf.vnodes[new_id].camera_node_idx = vnode.camera_node_idx - gltf.vnodes[new_id].parent = id - vnode.children.append(new_id) - vnode.camera_node_idx = None - needs_move = True - - if vnode.light_node_idx is not None: - if needs_move: - new_id = str(id) + '.light' - gltf.vnodes[new_id] = VNode() - gltf.vnodes[new_id].light_node_idx = vnode.light_node_idx - gltf.vnodes[new_id].parent = id - vnode.children.append(new_id) - vnode.light_node_idx = None - needs_move = True - - -def correct_cameras_and_lights(gltf): - """ - Depending on the coordinate change, lights and cameras might need to be - rotated to match Blender conventions for which axes they point along. - """ - if gltf.camera_correction is None: - return - - for id, vnode in gltf.vnodes.items(): - needs_correction = \ - vnode.camera_node_idx is not None or \ - vnode.light_node_idx is not None - - if needs_correction: - local_rotation(gltf, id, gltf.camera_correction) - - -def pick_bind_pose(gltf): - """ - Pick the bind pose for all bones. Skinned meshes will be retargeted onto - this bind pose during mesh creation. - """ - if True: # gltf.import_settings['guess_original_bind_pose'] - # Record inverse bind matrices. We're going to milk them for information - # about the original bind pose. - inv_binds = {'root': Matrix.Identity(4)} - for skin in gltf.data.skins or []: - if skin.inverse_bind_matrices is None: - continue - - # Assume inverse bind matrices are calculated relative to the skeleton - skel = skin.skeleton - if skel is not None: - if skel in skin.joints: - skel = gltf.vnodes[skel].parent - if skel not in inv_binds: - inv_binds[skel] = Matrix.Identity(4) - - skin_inv_binds = BinaryData.get_data_from_accessor(gltf, skin.inverse_bind_matrices) - skin_inv_binds = [gltf.matrix_gltf_to_blender(m) for m in skin_inv_binds] - for i, joint in enumerate(skin.joints): - inv_binds[joint] = skin_inv_binds[i] - - for vnode_id in gltf.vnodes: - vnode = gltf.vnodes[vnode_id] - if vnode.type == VNode.Bone: - # Initialize bind pose to default pose (from gltf.data.nodes) - vnode.bind_trans = Vector(vnode.base_trs[0]) - vnode.bind_rot = Quaternion(vnode.base_trs[1]) - - if True: #gltf.import_settings['guess_original_bind_pose']: - # Try to guess bind pose from inverse bind matrices - if vnode_id in inv_binds and vnode.parent in inv_binds: - # (bind matrix) = (parent bind matrix) (bind local). Solve for bind local... - bind_local = inv_binds[vnode.parent] @ inv_binds[vnode_id].inverted_safe() - t, r, _s = bind_local.decompose() - vnode.bind_trans = t - vnode.bind_rot = r - - # Initialize editbones to match bind pose - vnode.editbone_trans = Vector(vnode.bind_trans) - vnode.editbone_rot = Quaternion(vnode.bind_rot) - - -def prettify_bones(gltf): - """ - Prettify bone lengths/directions. - """ - def visit(vnode_id, parent_rot=None): # Depth-first walk - vnode = gltf.vnodes[vnode_id] - rot = None - - if vnode.type == VNode.Bone: - vnode.bone_length = pick_bone_length(gltf, vnode_id) - rot = pick_bone_rotation(gltf, vnode_id, parent_rot) - if rot is not None: - rotate_edit_bone(gltf, vnode_id, rot) - - for child in vnode.children: - visit(child, parent_rot=rot) - - visit('root') - -MIN_BONE_LENGTH = 0.004 # too small and bones get deleted - -def pick_bone_length(gltf, bone_id): - """Heuristic for bone length.""" - vnode = gltf.vnodes[bone_id] - - child_locs = [ - gltf.vnodes[child].editbone_trans - for child in vnode.children - if gltf.vnodes[child].type == VNode.Bone - ] - child_locs = [loc for loc in child_locs if loc.length > MIN_BONE_LENGTH] - if child_locs: - return min(loc.length for loc in child_locs) - - if gltf.vnodes[vnode.parent].type == VNode.Bone: - return gltf.vnodes[vnode.parent].bone_length - - if vnode.editbone_trans.length > MIN_BONE_LENGTH: - return vnode.editbone_trans.length - - return 1 - -def pick_bone_rotation(gltf, bone_id, parent_rot): - """Heuristic for bone rotation. - A bone's tip lies on its local +Y axis so rotating a bone let's us - adjust the bone direction. - """ - if bpy.app.debug_value == 100: - return None - - return temperance(gltf, bone_id, parent_rot) - -def temperance(gltf, bone_id, parent_rot): - vnode = gltf.vnodes[bone_id] - - # Try to put our tip at the centroid of our children - child_locs = [ - gltf.vnodes[child].editbone_trans - for child in vnode.children - if gltf.vnodes[child].type == VNode.Bone - ] - child_locs = [loc for loc in child_locs if loc.length > MIN_BONE_LENGTH] - if child_locs: - centroid = sum(child_locs, Vector((0, 0, 0))) - rot = Vector((0, 1, 0)).rotation_difference(centroid) - if True: #gltf.import_settings['bone_heuristic'] == 'TEMPERANCE': - # Snap to the local axes; required for local_rotation to be - # accurate when vnode has a non-uniform scaling. - # FORTUNE skips this, so it may look better, but may have errors. - rot = nearby_signed_perm_matrix(rot).to_quaternion() - return rot - - return parent_rot - -def rotate_edit_bone(gltf, bone_id, rot): - """Rotate one edit bone without affecting anything else.""" - gltf.vnodes[bone_id].editbone_rot @= rot - # Cancel out the rotation so children aren't affected. - rot_inv = rot.conjugated() - for child_id in gltf.vnodes[bone_id].children: - child = gltf.vnodes[child_id] - if child.type == VNode.Bone: - child.editbone_trans = rot_inv @ child.editbone_trans - child.editbone_rot = rot_inv @ child.editbone_rot - # Need to rotate the bone's final TRS by the same amount so skinning - # isn't affected. - local_rotation(gltf, bone_id, rot) - - -def calc_bone_matrices(gltf): - """ - Calculate the transformations from bone space to arma space in the bind - pose and in the edit bone pose. - """ - def visit(vnode_id): # Depth-first walk - vnode = gltf.vnodes[vnode_id] - if vnode.type == VNode.Bone: - if gltf.vnodes[vnode.parent].type == VNode.Bone: - parent_bind_mat = gltf.vnodes[vnode.parent].bind_arma_mat - parent_editbone_mat = gltf.vnodes[vnode.parent].editbone_arma_mat - else: - parent_bind_mat = Matrix.Identity(4) - parent_editbone_mat = Matrix.Identity(4) - - t, r = vnode.bind_trans, vnode.bind_rot - local_to_parent = Matrix.Translation(t) @ Quaternion(r).to_matrix().to_4x4() - vnode.bind_arma_mat = parent_bind_mat @ local_to_parent - - t, r = vnode.editbone_trans, vnode.editbone_rot - local_to_parent = Matrix.Translation(t) @ Quaternion(r).to_matrix().to_4x4() - vnode.editbone_arma_mat = parent_editbone_mat @ local_to_parent - - for child in vnode.children: - visit(child) - - visit('root') diff --git a/addons/io_sketchfab_plugin/blender/imp/gltf2_io_draco_compression_extension.py b/addons/io_sketchfab_plugin/blender/imp/gltf2_io_draco_compression_extension.py deleted file mode 100644 index 47ac32e..0000000 --- a/addons/io_sketchfab_plugin/blender/imp/gltf2_io_draco_compression_extension.py +++ /dev/null @@ -1,143 +0,0 @@ -# Copyright 2018-2021 The glTF-Blender-IO authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from ctypes import * - -from ...io.com.gltf2_io import BufferView -from ...io.imp.gltf2_io_binary import BinaryData -from ...io.com.gltf2_io_debug import print_console -from ...io.com.gltf2_io_draco_compression_extension import dll_path - - -def decode_primitive(gltf, prim): - """ - Handles draco compression. - Moves decoded data into new buffers and buffer views held by the accessors of the given primitive. - """ - - # Load DLL and setup function signatures. - dll = cdll.LoadLibrary(str(dll_path().resolve())) - - dll.decoderCreate.restype = c_void_p - dll.decoderCreate.argtypes = [] - - dll.decoderRelease.restype = None - dll.decoderRelease.argtypes = [c_void_p] - - dll.decoderDecode.restype = c_bool - dll.decoderDecode.argtypes = [c_void_p, c_void_p, c_size_t] - - dll.decoderReadAttribute.restype = c_bool - dll.decoderReadAttribute.argtypes = [c_void_p, c_uint32, c_size_t, c_char_p] - - dll.decoderGetVertexCount.restype = c_uint32 - dll.decoderGetVertexCount.argtypes = [c_void_p] - - dll.decoderGetIndexCount.restype = c_uint32 - dll.decoderGetIndexCount.argtypes = [c_void_p] - - dll.decoderAttributeIsNormalized.restype = c_bool - dll.decoderAttributeIsNormalized.argtypes = [c_void_p, c_uint32] - - dll.decoderGetAttributeByteLength.restype = c_size_t - dll.decoderGetAttributeByteLength.argtypes = [c_void_p, c_uint32] - - dll.decoderCopyAttribute.restype = None - dll.decoderCopyAttribute.argtypes = [c_void_p, c_uint32, c_void_p] - - dll.decoderReadIndices.restype = c_bool - dll.decoderReadIndices.argtypes = [c_void_p, c_size_t] - - dll.decoderGetIndicesByteLength.restype = c_size_t - dll.decoderGetIndicesByteLength.argtypes = [c_void_p] - - dll.decoderCopyIndices.restype = None - dll.decoderCopyIndices.argtypes = [c_void_p, c_void_p] - - decoder = dll.decoderCreate() - extension = prim.extensions['KHR_draco_mesh_compression'] - - name = prim.name if hasattr(prim, 'name') else '[unnamed]' - - # Create Draco decoder. - draco_buffer = bytes(BinaryData.get_buffer_view(gltf, extension['bufferView'])) - if not dll.decoderDecode(decoder, draco_buffer, len(draco_buffer)): - print_console('ERROR', 'Draco Decoder: Unable to decode. Skipping primitive {}.'.format(name)) - return - - # Choose a buffer index which does not yet exist, skipping over existing glTF buffers yet to be loaded - # and buffers which were generated and did not exist in the initial glTF file, like this decoder does. - base_buffer_idx = len(gltf.data.buffers) - for existing_buffer_idx in gltf.buffers: - if base_buffer_idx <= existing_buffer_idx: - base_buffer_idx = existing_buffer_idx + 1 - - # Read indices. - index_accessor = gltf.data.accessors[prim.indices] - if dll.decoderGetIndexCount(decoder) != index_accessor.count: - print_console('WARNING', 'Draco Decoder: Index count of accessor and decoded index count does not match. Updating accessor.') - index_accessor.count = dll.decoderGetIndexCount(decoder) - if not dll.decoderReadIndices(decoder, index_accessor.component_type): - print_console('ERROR', 'Draco Decoder: Unable to decode indices. Skipping primitive {}.'.format(name)) - return - - indices_byte_length = dll.decoderGetIndicesByteLength(decoder) - decoded_data = bytes(indices_byte_length) - dll.decoderCopyIndices(decoder, decoded_data) - - # Generate a new buffer holding the decoded indices. - gltf.buffers[base_buffer_idx] = decoded_data - - # Create a buffer view referencing the new buffer. - gltf.data.buffer_views.append(BufferView.from_dict({ - 'buffer': base_buffer_idx, - 'byteLength': indices_byte_length - })) - - # Update accessor to point to the new buffer view. - index_accessor.buffer_view = len(gltf.data.buffer_views) - 1 - - # Read each attribute. - for attr_idx, attr in enumerate(extension['attributes']): - dracoId = extension['attributes'][attr] - if attr not in prim.attributes: - print_console('ERROR', 'Draco Decoder: Draco attribute {} not in primitive attributes. Skipping primitive {}.'.format(attr, name)) - return - - accessor = gltf.data.accessors[prim.attributes[attr]] - if dll.decoderGetVertexCount(decoder) != accessor.count: - print_console('WARNING', 'Draco Decoder: Vertex count of accessor and decoded vertex count does not match for attribute {}. Updating accessor.'.format(attr, name)) - accessor.count = dll.decoderGetVertexCount(decoder) - if not dll.decoderReadAttribute(decoder, dracoId, accessor.component_type, accessor.type.encode()): - print_console('ERROR', 'Draco Decoder: Could not decode attribute {}. Skipping primitive {}.'.format(attr, name)) - return - - byte_length = dll.decoderGetAttributeByteLength(decoder, dracoId) - decoded_data = bytes(byte_length) - dll.decoderCopyAttribute(decoder, dracoId, decoded_data) - - # Generate a new buffer holding the decoded vertex data. - buffer_idx = base_buffer_idx + 1 + attr_idx - gltf.buffers[buffer_idx] = decoded_data - - # Create a buffer view referencing the new buffer. - gltf.data.buffer_views.append(BufferView.from_dict({ - 'buffer': buffer_idx, - 'byteLength': byte_length - })) - - # Update accessor to point to the new buffer view. - accessor.buffer_view = len(gltf.data.buffer_views) - 1 - - dll.decoderRelease(decoder) diff --git a/addons/io_sketchfab_plugin/dependencies/_ssl.cpython-35m-darwin.so b/addons/io_sketchfab_plugin/dependencies/_ssl.cpython-35m-darwin.so deleted file mode 100644 index dabec6c..0000000 Binary files a/addons/io_sketchfab_plugin/dependencies/_ssl.cpython-35m-darwin.so and /dev/null differ diff --git a/addons/io_sketchfab_plugin/dependencies/_ssl.cpython-35m-x86_64-linux-gnu.so b/addons/io_sketchfab_plugin/dependencies/_ssl.cpython-35m-x86_64-linux-gnu.so deleted file mode 100644 index 2866c4c..0000000 Binary files a/addons/io_sketchfab_plugin/dependencies/_ssl.cpython-35m-x86_64-linux-gnu.so and /dev/null differ diff --git a/addons/io_sketchfab_plugin/resources/placeholder.png b/addons/io_sketchfab_plugin/resources/placeholder.png deleted file mode 100644 index 6648def..0000000 Binary files a/addons/io_sketchfab_plugin/resources/placeholder.png and /dev/null differ diff --git a/build.sh b/build.sh deleted file mode 100755 index 30a0215..0000000 --- a/build.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/bash -# Creates dev and release (.zip) versions of the Blender plugin -# -# Usage: -# git clone -b chore/gltf-code-uniformization_D3D-4952 --recursive git@github.com:sketchfab/blender-plugin.git -# cd blender-plugin/ -# ./build.sh --patch (AFTER CLONING ONLY) -# or -# ./build.sh (FOR SUBSEQUENT RELEASES) -# -# Make a symlink in blender from Powershell: -# cmd /c mklink /d 'C:/Users/Norgeotloic/AppData/Roaming/Blender Foundation/Blender/2.79/scripts/addons/io_sketchfab_plugin' C:\Users\Norgeotloic\Documents\blender-plugin/ -# -# Check the patch -# git apply -v --reject --whitespace=fix --check ../khronos-gltf.patch - -# Get the plugin version -version=$(cat addons/io_sketchfab_plugin/__init__.py | grep "'version': " | grep -o '(.*)' | tr -d '() ' | sed 's/,/-/g') - -# If requested, apply the patch on Khronos' submodule (glTF-Blender-IO/) -if [[ $* == *--patch* ]] -then - echo "Trying to apply khronos-gltf.patch" - cd glTF-Blender-IO/ - git apply ../khronos-gltf.patch - cp -r ./addons/io_scene_gltf2/io/ ../addons/io_sketchfab_plugin/ - cd ../addons/io_sketchfab_plugin/io/ - sed -i 's/io_scene_gltf2.io/./g' ./*/*.py - cd ../../../ -else - # Create the ZIP files for release - mkdir -p releases - rm -rf releases/* - cp -r addons/io_sketchfab_plugin/ releases/sketchfab-plugin-$version/ - cd releases/ - zip -r -q sketchfab-plugin-$version.zip sketchfab-plugin-$version/ - cd .. - echo "Releases available in $(pwd)/releases/" -fi - diff --git a/docs/io_process.png b/docs/io_process.png deleted file mode 100644 index 8de5550..0000000 Binary files a/docs/io_process.png and /dev/null differ diff --git a/docs/io_process.pptx b/docs/io_process.pptx deleted file mode 100644 index 9319245..0000000 Binary files a/docs/io_process.pptx and /dev/null differ diff --git a/docs/packages.png b/docs/packages.png deleted file mode 100644 index f337d9e..0000000 Binary files a/docs/packages.png and /dev/null differ diff --git a/docs/packages.uxf b/docs/packages.uxf deleted file mode 100644 index 7b7fa9b..0000000 --- a/docs/packages.uxf +++ /dev/null @@ -1,25 +0,0 @@ - - - 10 - - UMLHierarchy - - 590 - 220 - 190 - 300 - - type=Package -io_scene_gltf2 - blender - com - exp - imp - io - com - exp - imp - - - - diff --git a/glTF-Blender-IO b/glTF-Blender-IO deleted file mode 160000 index 6f9d0d9..0000000 --- a/glTF-Blender-IO +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6f9d0d9fc1bb30e2b0bb019342ffe86bd67358fc diff --git a/khronos-gltf.patch b/khronos-gltf.patch deleted file mode 100644 index c93f0a1..0000000 --- a/khronos-gltf.patch +++ /dev/null @@ -1,75 +0,0 @@ -diff --git a/addons/io_scene_gltf2/io/com/gltf2_io.py b/addons/io_scene_gltf2/io/com/gltf2_io.py -index d00a914d..a51ff4e3 100644 ---- a/addons/io_scene_gltf2/io/com/gltf2_io.py -+++ b/addons/io_scene_gltf2/io/com/gltf2_io.py -@@ -26,7 +26,7 @@ - import sys - import traceback - --from io_scene_gltf2.io.com import gltf2_io_debug -+from . import gltf2_io_debug - - - def from_int(x): -diff --git a/addons/io_scene_gltf2/io/com/gltf2_io_debug.py b/addons/io_scene_gltf2/io/com/gltf2_io_debug.py -index 290616cc..f987d9e3 100644 ---- a/addons/io_scene_gltf2/io/com/gltf2_io_debug.py -+++ b/addons/io_scene_gltf2/io/com/gltf2_io_debug.py -@@ -124,3 +124,17 @@ class Log: - self.hdlr.setFormatter(formatter) - self.logger.addHandler(self.hdlr) - self.logger.setLevel(int(loglevel)) -+ -+ def getLevels(): -+ levels = [ -+ (str(logging.CRITICAL), "Critical", "", logging.CRITICAL), -+ (str(logging.ERROR), "Error", "", logging.ERROR), -+ (str(logging.WARNING), "Warning", "", logging.WARNING), -+ (str(logging.INFO), "Info", "", logging.INFO), -+ (str(logging.NOTSET), "NotSet", "", logging.NOTSET) -+ ] -+ -+ return levels -+ -+ def default(): -+ return str(logging.ERROR) -\ No newline at end of file -diff --git a/addons/io_scene_gltf2/io/imp/__init__.py b/addons/io_scene_gltf2/io/imp/__init__.py -index 666fdf3f..81f88469 100644 ---- a/addons/io_scene_gltf2/io/imp/__init__.py -+++ b/addons/io_scene_gltf2/io/imp/__init__.py -@@ -12,4 +12,4 @@ - # See the License for the specific language governing permissions and - # limitations under the License. - --"""IO imp package.""" -+from .gltf2_io_gltf import * -\ No newline at end of file -diff --git a/addons/io_scene_gltf2/io/imp/gltf2_io_gltf.py b/addons/io_scene_gltf2/io/imp/gltf2_io_gltf.py -index 407afccd..d776d353 100644 ---- a/addons/io_scene_gltf2/io/imp/gltf2_io_gltf.py -+++ b/addons/io_scene_gltf2/io/imp/gltf2_io_gltf.py -@@ -30,20 +30,15 @@ class ImportError(RuntimeError): - class glTFImporter(): - """glTF Importer class.""" - -- def __init__(self, filename, import_settings): -+ def __init__(self, filename, loglevel=logging.ERROR): - """initialization.""" - self.filename = filename -- self.import_settings = import_settings - self.glb_buffer = None - self.buffers = {} - self.accessor_cache = {} - self.decode_accessor_cache = {} -- self.import_user_extensions = import_settings['import_user_extensions'] -- -- if 'loglevel' not in self.import_settings.keys(): -- self.import_settings['loglevel'] = logging.ERROR -- -- log = Log(import_settings['loglevel']) -+ self.import_user_extensions = [] -+ log = Log(loglevel) - self.log = log.logger - self.log_handler = log.hdlr - diff --git a/addons/io_sketchfab_plugin/resources/logo.png b/logo.png similarity index 100% rename from addons/io_sketchfab_plugin/resources/logo.png rename to logo.png diff --git a/misc/Blender_logo.png b/misc/Blender_logo.png deleted file mode 100644 index 5a7cc96..0000000 Binary files a/misc/Blender_logo.png and /dev/null differ diff --git a/misc/glTF_logo.png b/misc/glTF_logo.png deleted file mode 100644 index c813ce5..0000000 Binary files a/misc/glTF_logo.png and /dev/null differ diff --git a/addons/io_sketchfab_plugin/pack_for_export.py b/pack_for_export.py similarity index 94% rename from addons/io_sketchfab_plugin/pack_for_export.py rename to pack_for_export.py index 055a3fe..b23ce47 100644 --- a/addons/io_sketchfab_plugin/pack_for_export.py +++ b/pack_for_export.py @@ -20,7 +20,6 @@ import sys sys.path.append(os.path.dirname(os.path.abspath(__file__))) -from blender.blender_version import Version SKETCHFAB_EXPORT_TEMP_DIR = sys.argv[7] SKETCHFAB_EXPORT_DATA_FILE = os.path.join(SKETCHFAB_EXPORT_TEMP_DIR, "export-sketchfab.json") @@ -72,10 +71,10 @@ def prepare_assets(export_settings): if export_settings['selection'] and ob.type == 'MESH': # Add relevant objects to the list of objects to remove - if not Version.get_visible(ob): # Not visible + if not ob.visible_get(): # Not visible hidden.add(ob) - elif not Version.get_selected(ob): # Visible but not selected - Version.set_visible(ob, False) + elif not ob.select_get(): # Visible but not selected + ob.hide_set(True) hidden.add(ob) for img in images: diff --git a/placeholder.png b/placeholder.png new file mode 100644 index 0000000..cff59fc Binary files /dev/null and b/placeholder.png differ