Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCampbell Barton <ideasman42@gmail.com>2014-03-05 12:14:49 +0400
committerCampbell Barton <ideasman42@gmail.com>2014-03-05 12:31:14 +0400
commit8ad356e3324cddef42d41f9b9b588ef1ebd2f8bf (patch)
tree06083c559cf8042d4fa0220af32075fa26bacf97
parent20a4a567f961dfd1aaae083ca5aea3d48bfbf4b7 (diff)
Sketchfab integration, D321v2.70-rc
Lets you use your sketchfab account from within Blender to upload models online.
-rw-r--r--io_online_sketchfab/__init__.py466
-rw-r--r--io_online_sketchfab/pack_for_export.py124
2 files changed, 590 insertions, 0 deletions
diff --git a/io_online_sketchfab/__init__.py b/io_online_sketchfab/__init__.py
new file mode 100644
index 00000000..3e5d7472
--- /dev/null
+++ b/io_online_sketchfab/__init__.py
@@ -0,0 +1,466 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+bl_info = {
+ "name": "Sketchfab Exporter",
+ "author": "Bart Crouch",
+ "version": (1, 2, 2),
+ "blender": (2, 7, 0),
+ "location": "Tools > Upload tab",
+ "description": "Upload your model to Sketchfab",
+ "warning": "",
+ "wiki_url": "",
+ "tracker_url": "",
+ "category": "Import-Export"
+}
+
+if "bpy" in locals():
+ pass
+else:
+ # uuid module causes an error messagebox on windows
+ # - https://developer.blender.org/T38364
+ # - https://developer.blender.org/T27666
+ # using a dirty workaround to preload uuid without ctypes, until blender gets compiled with vs2012
+ import platform
+ if platform.system() == "Windows":
+ import ctypes
+ CDLL = ctypes.CDLL
+ ctypes.CDLL = None
+ import uuid
+ ctypes.CDLL = CDLL
+ del ctypes, CDLL
+
+import bpy
+import os
+import threading
+import subprocess
+
+from bpy.app.handlers import persistent
+from bpy.props import (StringProperty,
+ EnumProperty,
+ BoolProperty,
+ PointerProperty,
+ )
+
+SKETCHFAB_API_URL = "https://api.sketchfab.com"
+SKETCHFAB_API_MODELS_URL = SKETCHFAB_API_URL + "/v1/models"
+SKETCHFAB_API_TOKEN_URL = SKETCHFAB_API_URL + "/v1/users/claim-token"
+SKETCHFAB_MODEL_URL = "https://sketchfab.com/show/"
+SKETCHFAB_EXPORT_FILENAME = "sketchfab-export.blend"
+
+_presets = os.path.join(bpy.utils.user_resource('SCRIPTS'), "presets")
+SKETCHFAB_PRESET_FILENAME = os.path.join(_presets, "sketchfab.txt")
+SKETCHFAB_EXPORT_DATA_FILE = os.path.join(_presets, "sketchfab-export-data.json")
+del _presets
+
+
+# Singleton for storing global state
+class _SketchfabState:
+ __slots__ = (
+ "uploading",
+ "token_reload",
+ "size_label",
+ "model_url",
+
+ # store report args
+ "report_message",
+ "report_type",
+ )
+
+ def __init__(self):
+ self.uploading = False
+ self.token_reload = True
+ self.size_label = ""
+ self.model_url = ""
+
+ self.report_message = ""
+ self.report_type = ''
+
+sf_state = _SketchfabState()
+del _SketchfabState
+
+# if True, no contact is made with the webserver
+DEBUG_MODE = False
+
+
+# change a bytes int into a properly formatted string
+def format_size(size):
+ size /= 1024
+ size_suffix = "kB"
+ if size > 1024:
+ size /= 1024
+ size_suffix = "mB"
+ if size >= 100:
+ size = "%d" % int(size)
+ else:
+ size = "%.1f" % size
+ size += " " + size_suffix
+
+ return size
+
+
+# attempt to load token from presets
+@persistent
+def load_token(dummy=False):
+ filepath = SKETCHFAB_PRESET_FILENAME
+ if not os.path.exists(filepath):
+ return
+
+ token = ""
+ try:
+ with open(filepath, 'r', encoding='utf-8') as f:
+ token = f.readline()
+ except:
+ import traceback
+ traceback.print_exc()
+
+ wm = bpy.context.window_manager
+ wm.sketchfab.token = token
+
+
+# save token to file
+def update_token(self, context):
+ token = context.window_manager.sketchfab.token
+ filepath = SKETCHFAB_PRESET_FILENAME
+
+ path = os.path.dirname(filepath)
+ if not os.path.exists(path):
+ os.makedirs(path)
+
+ with open(filepath, 'w', encoding='utf-8') as f:
+ f.write(token)
+
+
+def upload_report(report_message, report_type):
+ sf_state.report_message = report_message
+ sf_state.report_type = report_type
+
+
+# upload the blend-file to sketchfab
+def upload(filepath, filename):
+ import requests
+
+ wm = bpy.context.window_manager
+ props = wm.sketchfab
+
+ title = props.title
+ if not title:
+ title = os.path.splitext(os.path.basename(bpy.data.filepath))[0]
+
+ data = {
+ "title": title,
+ "description": props.description,
+ "filename": filename,
+ "tags": props.tags,
+ "private": props.private,
+ "token": props.token,
+ "source": "blender-exporter",
+ }
+
+ if props.private and props.password != "":
+ data["password"] = props.password
+
+ files = {
+ "fileModel": open(filepath, 'rb'),
+ }
+
+ try:
+ r = requests.post(SKETCHFAB_API_MODELS_URL, data=data, files=files, verify=False)
+ except requests.exceptions.RequestException as e:
+ return upload_report("Upload failed. Error: %s" % str(e), 'WARNING')
+
+ result = r.json()
+ if r.status_code != requests.codes.ok:
+ return upload_report("Upload failed. Error: %s" % result["error"], 'WARNING')
+
+ sf_state.model_url = SKETCHFAB_MODEL_URL + result["result"]["id"]
+ return upload_report("Upload complete. Available on your sketchfab.com dashboard.", 'INFO')
+
+
+# operator to export model to sketchfab
+class ExportSketchfab(bpy.types.Operator):
+ """Upload your model to Sketchfab"""
+ bl_idname = "export.sketchfab"
+ bl_label = "Upload"
+
+ _timer = None
+ _thread = None
+
+ def modal(self, context, event):
+ if event.type == 'TIMER':
+ if not self._thread.is_alive():
+ wm = context.window_manager
+ props = wm.sketchfab
+ terminate(props.filepath)
+ if context.area:
+ context.area.tag_redraw()
+
+ # forward message from upload thread
+ if not sf_state.report_type:
+ sf_state.report_type = 'ERROR'
+ self.report({sf_state.report_type}, sf_state.report_message)
+
+ wm.event_timer_remove(self._timer)
+ self._thread.join()
+ sf_state.uploading = False
+ return {'FINISHED'}
+
+ return {'PASS_THROUGH'}
+
+ def execute(self, context):
+ import json
+
+ if sf_state.uploading:
+ self.report({'WARNING'}, "Please wait till current upload is finished")
+ return {'CANCELLED'}
+
+ wm = context.window_manager
+ sf_state.model_url = ""
+ props = wm.sketchfab
+ if not props.token:
+ self.report({'ERROR'}, "Token is missing")
+ return {'CANCELLED'}
+
+ # Prepare to save the file
+ binary_path = bpy.app.binary_path
+ script_path = os.path.dirname(os.path.realpath(__file__))
+ basename, ext = os.path.splitext(bpy.data.filepath)
+ if not basename:
+ basename = os.path.join(basename, "temp")
+ if not ext:
+ ext = ".blend"
+ filepath = basename + "-export-sketchfab" + ext
+
+ try:
+ # save a copy of actual scene but don't interfere with the users models
+ bpy.ops.wm.save_as_mainfile(filepath=filepath, compress=True, copy=True)
+
+ with open(SKETCHFAB_EXPORT_DATA_FILE, 'w') as s:
+ json.dump({
+ "models": props.models,
+ "lamps": props.lamps,
+ }, s)
+
+ subprocess.check_call([
+ binary_path,
+ "--background",
+ "-noaudio",
+ filepath,
+ "--python", os.path.join(script_path, "pack_for_export.py"),
+ ])
+
+ os.remove(filepath)
+
+ # read subprocess call results
+ with open(SKETCHFAB_EXPORT_DATA_FILE, 'r') as s:
+ r = json.load(s)
+ size = r["size"]
+ props.filepath = r["filepath"]
+ filename = r["filename"]
+
+ except Exception as e:
+ self.report({'WARNING'}, "Error occured while preparing your file: %s" % str(e))
+ return {'FINISHED'}
+
+ sf_state.uploading = True
+ sf_state.size_label = format_size(size)
+ self._thread = threading.Thread(
+ target=upload,
+ args=(props.filepath, filename),
+ )
+ self._thread.start()
+
+ wm.modal_handler_add(self)
+ self._timer = wm.event_timer_add(1.0, context.window)
+
+ return {'RUNNING_MODAL'}
+
+ def cancel(self, context):
+ wm = context.window_manager
+ wm.event_timer_remove(self._timer)
+ self._thread.join()
+
+
+# user interface
+class VIEW3D_PT_sketchfab(bpy.types.Panel):
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'TOOLS'
+ bl_category = "Upload"
+ bl_context = "objectmode"
+ bl_label = "Sketchfab"
+
+ def draw(self, context):
+ wm = context.window_manager
+ props = wm.sketchfab
+ if sf_state.token_reload:
+ sf_state.token_reload = False
+ if not props.token:
+ load_token()
+ layout = self.layout
+
+ layout.label("Export:")
+ col = layout.box().column(align=True)
+ col.prop(props, "models")
+ col.prop(props, "lamps")
+
+ layout.label("Model info:")
+ col = layout.box().column(align=True)
+ col.prop(props, "title")
+ col.prop(props, "description")
+ col.prop(props, "tags")
+ col.prop(props, "private")
+ if props.private:
+ col.prop(props, "password")
+
+ layout.label("Sketchfab account:")
+ col = layout.box().column(align=True)
+ col.prop(props, "token")
+ row = col.row()
+ row.operator("wm.sketchfab_email_token", text="Claim Your Token")
+ row.alignment = 'RIGHT'
+ if sf_state.uploading:
+ layout.operator("export.sketchfab", text="Uploading %s" % sf_state.size_label)
+ else:
+ layout.operator("export.sketchfab")
+
+ model_url = sf_state.model_url
+ if model_url:
+ layout.operator("wm.url_open", text="View Online Model", icon='URL').url = model_url
+
+
+# property group containing all properties for the user interface
+class SketchfabProps(bpy.types.PropertyGroup):
+ description = StringProperty(
+ name="Description",
+ description="Description of the model (optional)",
+ default="")
+ filepath = StringProperty(
+ name="Filepath",
+ description="internal use",
+ default="",
+ )
+ lamps = EnumProperty(
+ name="Lamps",
+ items=(('ALL', "All", "Export all lamps in the file"),
+ ('NONE', "None", "Don't export any lamps"),
+ ('SELECTION', "Selection", "Only export selected lamps")),
+ description="Determines which lamps are exported",
+ default='ALL',
+ )
+ models = EnumProperty(
+ name="Models",
+ items=(('ALL', "All", "Export all meshes in the file"),
+ ('SELECTION', "Selection", "Only export selected meshes")),
+ description="Determines which meshes are exported",
+ default='SELECTION',
+ )
+ private = BoolProperty(
+ name="Private",
+ description="Upload as private (requires a pro account)",
+ default=False,
+ )
+ password = StringProperty(
+ name="Password",
+ description="Password-protect your model (requires a pro account)",
+ default="",
+ )
+ tags = StringProperty(
+ name="Tags",
+ description="List of tags, separated by spaces (optional)",
+ default="",
+ )
+ title = StringProperty(
+ name="Title",
+ description="Title of the model (determined automatically if left empty)",
+ default="",
+ )
+ token = StringProperty(
+ name="Api Key",
+ description="You can find this on your dashboard at the Sketchfab website",
+ default="",
+ update=update_token,
+ )
+
+
+class SketchfabEmailToken(bpy.types.Operator):
+ bl_idname = "wm.sketchfab_email_token"
+ bl_label = "Enter your email to get a sketchfab token"
+
+ email = StringProperty(
+ name="Email",
+ default="you@example.com",
+ )
+
+ def execute(self, context):
+ import re
+ import requests
+
+ EMAIL_RE = re.compile(r'[^@]+@[^@]+\.[^@]+')
+ if not EMAIL_RE.match(self.email):
+ self.report({'ERROR'}, "Wrong email format")
+ try:
+ r = requests.get(SKETCHFAB_API_TOKEN_URL + "?source=blender-exporter&email=" + self.email, verify=False)
+ except requests.exceptions.RequestException as e:
+ self.report({'ERROR'}, str(e))
+ return {'FINISHED'}
+
+ if r.status_code != requests.codes.ok:
+ self.report({'ERROR'}, "An error occured. Check the format of your email")
+ else:
+ self.report({'INFO'}, "Your email was sent at your email address")
+
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ wm = context.window_manager
+ return wm.invoke_props_dialog(self, width=550)
+
+
+# remove file copy
+def terminate(filepath):
+ os.remove(filepath)
+
+# registration
+classes = (
+ ExportSketchfab,
+ SketchfabProps,
+ SketchfabEmailToken,
+ VIEW3D_PT_sketchfab,
+ )
+
+
+def register():
+ for cls in classes:
+ bpy.utils.register_class(cls)
+
+ bpy.types.WindowManager.sketchfab = PointerProperty(
+ type=SketchfabProps)
+
+ load_token()
+ bpy.app.handlers.load_post.append(load_token)
+
+
+def unregister():
+ for cls in classes:
+ bpy.utils.unregister_class(cls)
+
+ del bpy.types.WindowManager.sketchfab
+
+
+if __name__ == "__main__":
+ register()
diff --git a/io_online_sketchfab/pack_for_export.py b/io_online_sketchfab/pack_for_export.py
new file mode 100644
index 00000000..a8e7fc9b
--- /dev/null
+++ b/io_online_sketchfab/pack_for_export.py
@@ -0,0 +1,124 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# This script is called from the sketchfab addon directly
+# to pack and save the file from a blender instance
+# so that the users file is left untouched.
+
+import os
+import bpy
+import json
+
+
+SKETCHFAB_EXPORT_DATA_FILENAME = 'sketchfab-export-data.json'
+
+SKETCHFAB_EXPORT_DATA_FILE = os.path.join(
+ bpy.utils.user_resource('SCRIPTS'),
+ "presets",
+ SKETCHFAB_EXPORT_DATA_FILENAME,
+ )
+
+
+# save a copy of the current blendfile
+def save_blend_copy():
+ import time
+
+ filepath = os.path.dirname(bpy.data.filepath)
+ filename = time.strftime("Sketchfab_%Y_%m_%d_%H_%M_%S.blend",
+ time.localtime(time.time()))
+ filepath = os.path.join(filepath, filename)
+ bpy.ops.wm.save_as_mainfile(filepath=filepath,
+ compress=True,
+ copy=True)
+ size = os.path.getsize(filepath)
+
+ return (filepath, filename, size)
+
+
+# change visibility statuses and pack images
+def prepare_assets(export_settings):
+ hidden = set()
+ images = set()
+ if (export_settings['models'] == 'SELECTION' or
+ export_settings['lamps'] != 'ALL'):
+
+ for ob in bpy.data.objects:
+ if ob.type == 'MESH':
+ for mat_slot in ob.material_slots:
+ if not mat_slot.material:
+ continue
+ for tex_slot in mat_slot.material.texture_slots:
+ if not tex_slot:
+ continue
+ tex = tex_slot.texture
+ if tex.type == 'IMAGE':
+ image = tex.image
+ if image is not None:
+ images.add(image)
+ if ((export_settings['models'] == 'SELECTION' and ob.type == 'MESH') or
+ (export_settings['lamps'] == 'SELECTION' and ob.type == 'LAMP')):
+
+ if not ob.select and not ob.hide:
+ ob.hide = True
+ hidden.add(ob)
+ elif export_settings['lamps'] == 'NONE' and ob.type == 'LAMP':
+ if not ob.hide:
+ ob.hide = True
+ hidden.add(ob)
+
+ for img in images:
+ if not img.packed_file:
+ try:
+ img.pack()
+ except:
+ # can fail in rare cases
+ import traceback
+ traceback.print_exc()
+
+
+def prepare_file(export_settings):
+ prepare_assets(export_settings)
+ return save_blend_copy()
+
+
+def read_settings():
+ with open(SKETCHFAB_EXPORT_DATA_FILE, 'r') as s:
+ return json.load(s)
+
+
+def write_result(filepath, filename, size):
+ with open(SKETCHFAB_EXPORT_DATA_FILE, 'w') as s:
+ json.dump({
+ 'filepath': filepath,
+ 'filename': filename,
+ 'size': size,
+ }, s)
+
+
+if __name__ == "__main__":
+ try:
+ export_settings = read_settings()
+ filepath, filename, size = prepare_file(export_settings)
+ write_result(filepath, filename, size)
+ except:
+ import traceback
+ traceback.print_exc()
+
+ import sys
+ sys.exit(1)
+