# -*- coding: utf-8 -*-
# AUTHOR Glossi
# VERSION 0.1.2
# Export the open scene as GLB (baked PBR textures) and upload it to your Glossi workspace.
import base64 as _b64
import sys as _sys
import types as _types

# Single-file build generated by scripts/package_keyshot.py — do not edit;
# edit the repo sources. Install: copy into KeyShot's Scripts folder as
# "Glossi Uploader.py" (Window > Scripting Console > Scripts tab).
glossi_client = _types.ModuleType("glossi_client")
exec(_b64.b64decode("IiIiR2xvc3NpIFJFU1QgY2xpZW50IHNoYXJlZCBieSBhbGwgRENDIGV4cG9ydGVyIHBsdWdpbnMgKE1heCwgQmxlbmRlciwgQ0xPKS4KClN0YW5kYXJkIGxpYnJhcnkgb25seSAodXJsbGliKSBzbyBpdCBydW5zIHVubW9kaWZpZWQgaW4gZXZlcnkgaG9zdCBhcHAncwplbWJlZGRlZCBQeXRob24gKDNkcyBNYXggc2hpcHMgQ1B5dGhvbiB3aXRob3V0IGByZXF1ZXN0c2A7IEJsZW5kZXIgYW5kIENMTwpidW5kbGUgdGhlaXIgb3duIGludGVycHJldGVycykuIFB5dGhvbiAzLjcrIGNvbXBhdGlibGUuCgpUaGlzIGZpbGUgaXMgdGhlIHNpbmdsZSBzb3VyY2Ugb2YgdHJ1dGggaW4gYHNoYXJlZC9gLiBQbHVnaW5zIHNoaXAgYSB2ZW5kb3JlZApjb3B5IGNyZWF0ZWQgYnkgYHNjcmlwdHMvc3luY19jbGllbnQucHlgOyBDSSBmYWlscyBpZiBhIGNvcHkgZHJpZnRzLiBFZGl0IHRoZQpgc2hhcmVkL2AgY29weSBvbmx5LgoKVHlwaWNhbCBwbHVnaW4gdXNhZ2U6CgogICAgZnJvbSAuIGltcG9ydCBnbG9zc2lfY2xpZW50CgogICAgaW5mbyA9IGdsb3NzaV9jbGllbnQudmFsaWRhdGVfa2V5KGFwaV9rZXkpICAgICAgICAgICMgLT4gd29ya3NwYWNlIG5hbWUKICAgIG1vZGVsID0gZ2xvc3NpX2NsaWVudC51cGxvYWRfbW9kZWwoCiAgICAgICAgIi9wYXRoL3RvL2V4cG9ydC5nbGIiLCBhcGlfa2V5LAogICAgICAgIG5hbWU9Ik15IFByb2R1Y3QiLCBwcm9ncmVzcz1wcmludCwKICAgICkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBibG9ja3MgdW50aWwgUkVBRFkKIiIiCgppbXBvcnQganNvbgppbXBvcnQgb3MKaW1wb3J0IHN1YnByb2Nlc3MKaW1wb3J0IHN5cwppbXBvcnQgdGltZQppbXBvcnQgdXJsbGliLmVycm9yCmltcG9ydCB1cmxsaWIucmVxdWVzdAoKX192ZXJzaW9uX18gPSAiMC4xLjIiCgpERUZBVUxUX0JBU0VfVVJMID0gImh0dHBzOi8vYXBpLmdsb3NzaS5pby9hcGkvdjEiCgojIENvbnRlbnQgdHlwZXMgZm9yIHRoZSBTMyBQVVQuIEFueXRoaW5nIG5vdCBsaXN0ZWQgdXBsb2FkcyBhcyBvY3RldC1zdHJlYW07CiMgdGhlIGJhY2tlbmQga2V5cyBwcm9jZXNzaW5nIG9mZiB0aGUgZmlsZSBleHRlbnNpb24sIG5vdCB0aGUgY29udGVudCB0eXBlLgpfQ09OVEVOVF9UWVBFUyA9IHsKICAgICJnbGIiOiAibW9kZWwvZ2x0Zi1iaW5hcnkiLAogICAgImdsdGYiOiAibW9kZWwvZ2x0Zitqc29uIiwKICAgICJ1c2QiOiAibW9kZWwvdm5kLnVzZCIsCiAgICAidXNkeiI6ICJtb2RlbC92bmQudXNkeit6aXAiLAogICAgImZieCI6ICJhcHBsaWNhdGlvbi9vY3RldC1zdHJlYW0iLAogICAgIm9iaiI6ICJhcHBsaWNhdGlvbi9vY3RldC1zdHJlYW0iLAp9CgpfVVNFUl9BR0VOVCA9ICJnbG9zc2ktZXhwb3J0ZXItdG9vbHMvJXMgcHl0aG9uLyVzIiAlICgKICAgIF9fdmVyc2lvbl9fLAogICAgIi4iLmpvaW4oc3RyKHYpIGZvciB2IGluIHN5cy52ZXJzaW9uX2luZm9bOjNdKSwKKQoKCmNsYXNzIEdsb3NzaUFwaUVycm9yKEV4Y2VwdGlvbik6CiAgICAiIiJBbnkgZmFpbHVyZSB0YWxraW5nIHRvIHRoZSBHbG9zc2kgQVBJLgoKICAgIGBjb2RlYCBpcyB0aGUgQVBJJ3MgbWFjaGluZS1yZWFkYWJsZSBlcnJvciBjb2RlIChlLmcuICJJTlZBTElEX0FQSV9LRVkiLAogICAgIlBMQU5fTElNSVRfUkVBQ0hFRCIsICJGSUxFX05PVF9VUExPQURFRCIpIG9yIE5vbmUgZm9yIHRyYW5zcG9ydC1sZXZlbAogICAgZmFpbHVyZXMuIGBodHRwX3N0YXR1c2AgaXMgdGhlIEhUVFAgc3RhdHVzIG9yIE5vbmUuCiAgICAiIiIKCiAgICBkZWYgX19pbml0X18oc2VsZiwgbWVzc2FnZSwgY29kZT1Ob25lLCBodHRwX3N0YXR1cz1Ob25lKToKICAgICAgICBzdXBlcihHbG9zc2lBcGlFcnJvciwgc2VsZikuX19pbml0X18obWVzc2FnZSkKICAgICAgICBzZWxmLmNvZGUgPSBjb2RlCiAgICAgICAgc2VsZi5odHRwX3N0YXR1cyA9IGh0dHBfc3RhdHVzCgoKIyBXaGV0aGVyIHRoaXMgaW50ZXJwcmV0ZXIncyB1cmxsaWIgY2FuIHNwZWFrIEhUVFBTLiBTb21lIGhvc3QgYXBwcyBlbWJlZCBhCiMgUHl0aG9uIHdpdGggYSBicm9rZW4vbWlzc2luZyBzc2wgbW9kdWxlIChlLmcuIEtleVNob3Qgb24gbWFjT1MgcmVzb2x2ZXMgaXRzCiMgc3RkbGliIGFnYWluc3QgYSB3cm9uZy1hcmNoaXRlY3R1cmUgX3NzbCkg4oCUIHVybGxpYiB0aGVuIHJlamVjdHMgaHR0cHMgVVJMcwojIG91dHJpZ2h0ICgidW5rbm93biB1cmwgdHlwZTogaHR0cHMiKS4gQ2FjaGVkIG9uIGZpcnN0IHVzZS4KX0hUVFBTX1NVUFBPUlRFRCA9IE5vbmUKCgpkZWYgX2h0dHBzX3N1cHBvcnRlZCgpOgogICAgZ2xvYmFsIF9IVFRQU19TVVBQT1JURUQKICAgIGlmIF9IVFRQU19TVVBQT1JURUQgaXMgTm9uZToKICAgICAgICB0cnk6CiAgICAgICAgICAgIGltcG9ydCBzc2wgICMgbm9xYTogRjQwMQogICAgICAgICAgICBfSFRUUFNfU1VQUE9SVEVEID0gVHJ1ZQogICAgICAgIGV4Y2VwdCBJbXBvcnRFcnJvcjoKICAgICAgICAgICAgX0hUVFBTX1NVUFBPUlRFRCA9IEZhbHNlCiAgICByZXR1cm4gX0hUVFBTX1NVUFBPUlRFRAoKCmRlZiBfZXJyb3JfZnJvbV9yZXNwb25zZShzdGF0dXMsIGRldGFpbCk6CiAgICBjb2RlID0gTm9uZQogICAgdHJ5OgogICAgICAgIGVyciA9IGpzb24ubG9hZHMoZGV0YWlsKVsiZXJyb3IiXQogICAgICAgIGNvZGUgPSBlcnIuZ2V0KCJjb2RlIikKICAgICAgICBkZXRhaWwgPSAiJXM6ICVzIiAlIChjb2RlLCBlcnIuZ2V0KCJtZXNzYWdlIikpCiAgICBleGNlcHQgRXhjZXB0aW9uOgogICAgICAgIHBhc3MKICAgIHJldHVybiBHbG9zc2lBcGlFcnJvcigiSFRUUCAlcyAtICVzIiAlIChzdGF0dXMsIGRldGFpbCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgY29kZT1jb2RlLCBodHRwX3N0YXR1cz1zdGF0dXMpCgoKZGVmIF9yZXF1ZXN0KG1ldGhvZCwgdXJsLCBhcGlfa2V5PU5vbmUsIGpzb25fYm9keT1Ob25lLCByYXdfYm9keT1Ob25lLAogICAgICAgICAgICAgY29udGVudF90eXBlPU5vbmUsIHRpbWVvdXQ9NjApOgogICAgaGVhZGVycyA9IHsiVXNlci1BZ2VudCI6IF9VU0VSX0FHRU5UfQogICAgaWYgYXBpX2tleToKICAgICAgICBoZWFkZXJzWyJYLUFQSS1LZXkiXSA9IGFwaV9rZXkKICAgIGRhdGEgPSBOb25lCiAgICBpZiBqc29uX2JvZHkgaXMgbm90IE5vbmU6CiAgICAgICAgZGF0YSA9IGpzb24uZHVtcHMoanNvbl9ib2R5KS5lbmNvZGUoInV0Zi04IikKICAgICAgICBoZWFkZXJzWyJDb250ZW50LVR5cGUiXSA9ICJhcHBsaWNhdGlvbi9qc29uIgogICAgZWxpZiByYXdfYm9keSBpcyBub3QgTm9uZToKICAgICAgICBkYXRhID0gcmF3X2JvZHkKICAgICAgICBpZiBjb250ZW50X3R5cGU6CiAgICAgICAgICAgIGhlYWRlcnNbIkNvbnRlbnQtVHlwZSJdID0gY29udGVudF90eXBlCiAgICBpZiB1cmwuc3RhcnRzd2l0aCgiaHR0cHMiKSBhbmQgbm90IF9odHRwc19zdXBwb3J0ZWQoKToKICAgICAgICByZXR1cm4gX3JlcXVlc3RfY3VybChtZXRob2QsIHVybCwgaGVhZGVycywgZGF0YSwgdGltZW91dCkKICAgIHJldHVybiBfcmVxdWVzdF91cmxsaWIobWV0aG9kLCB1cmwsIGhlYWRlcnMsIGRhdGEsIHRpbWVvdXQpCgoKZGVmIF9yZXF1ZXN0X3VybGxpYihtZXRob2QsIHVybCwgaGVhZGVycywgZGF0YSwgdGltZW91dCk6CiAgICByZXEgPSB1cmxsaWIucmVxdWVzdC5SZXF1ZXN0KHVybCwgZGF0YT1kYXRhLCBoZWFkZXJzPWhlYWRlcnMsIG1ldGhvZD1tZXRob2QpCiAgICB0cnk6CiAgICAgICAgd2l0aCB1cmxsaWIucmVxdWVzdC51cmxvcGVuKHJlcSwgdGltZW91dD10aW1lb3V0KSBhcyByZXNwOgogICAgICAgICAgICBib2R5ID0gcmVzcC5yZWFkKCkKICAgICAgICAgICAgaWYgbm90IGJvZHk6CiAgICAgICAgICAgICAgICByZXR1cm4ge30KICAgICAgICAgICAgcmV0dXJuIGpzb24ubG9hZHMoYm9keS5kZWNvZGUoInV0Zi04IikpCiAgICBleGNlcHQgdXJsbGliLmVycm9yLkhUVFBFcnJvciBhcyBleGM6CiAgICAgICAgcmFpc2UgX2Vycm9yX2Zyb21fcmVzcG9uc2UoZXhjLmNvZGUsIGV4Yy5yZWFkKCkuZGVjb2RlKCJ1dGYtOCIsICJyZXBsYWNlIikpCiAgICBleGNlcHQgdXJsbGliLmVycm9yLlVSTEVycm9yIGFzIGV4YzoKICAgICAgICByYWlzZSBHbG9zc2lBcGlFcnJvcigibmV0d29yayBlcnJvcjogJXMiICUgZXhjLnJlYXNvbikKCgpkZWYgX3JlcXVlc3RfY3VybChtZXRob2QsIHVybCwgaGVhZGVycywgZGF0YSwgdGltZW91dCk6CiAgICAiIiJGYWxsYmFjayB0cmFuc3BvcnQgdmlhIHRoZSBjdXJsIGJpbmFyeSAoc2hpcHMgd2l0aCBtYWNPUyBhbmQKICAgIFdpbmRvd3MgMTArKSwgZm9yIGludGVycHJldGVycyB3aG9zZSBzc2wgbW9kdWxlIGlzIHVudXNhYmxlLiBCb2RpZXMgZ28KICAgIG92ZXIgc3RkaW4sIHNvIHVwbG9hZHMgbmV2ZXIgaGl0IGFyZ3YgbGltaXRzOyB0aGUgc3RhdHVzIGNvZGUgaXMgYXBwZW5kZWQKICAgIHRvIHN0ZG91dCBhZnRlciBhIG5ld2xpbmUgd2UgYWRkIG91cnNlbHZlcy4iIiIKICAgIGFyZ3MgPSBbImN1cmwiLCAiLS1zaWxlbnQiLCAiLS1zaG93LWVycm9yIiwgIi0tcmVxdWVzdCIsIG1ldGhvZCwKICAgICAgICAgICAgIi0tbWF4LXRpbWUiLCBzdHIoaW50KHRpbWVvdXQpKSwKICAgICAgICAgICAgIi0td3JpdGUtb3V0IiwgIlxcbiV7aHR0cF9jb2RlfSIsIHVybF0KICAgIGZvciBuYW1lLCB2YWx1ZSBpbiBoZWFkZXJzLml0ZW1zKCk6CiAgICAgICAgYXJncyArPSBbIi0taGVhZGVyIiwgIiVzOiAlcyIgJSAobmFtZSwgdmFsdWUpXQogICAgaWYgZGF0YSBpcyBub3QgTm9uZToKICAgICAgICBhcmdzICs9IFsiLS1kYXRhLWJpbmFyeSIsICJALSJdCiAgICB0cnk6CiAgICAgICAgcHJvYyA9IHN1YnByb2Nlc3MucnVuKGFyZ3MsIGlucHV0PWRhdGEsIGNhcHR1cmVfb3V0cHV0PVRydWUpCiAgICBleGNlcHQgT1NFcnJvciBhcyBleGM6CiAgICAgICAgcmFpc2UgR2xvc3NpQXBpRXJyb3IoCiAgICAgICAgICAgICJuZXR3b3JrIGVycm9yOiB0aGlzIGFwcCdzIFB5dGhvbiBjYW5ub3QgdXNlIGh0dHBzIGFuZCBjdXJsIGlzICIKICAgICAgICAgICAgInVuYXZhaWxhYmxlICglcykiICUgZXhjKQogICAgaWYgcHJvYy5yZXR1cm5jb2RlICE9IDA6CiAgICAgICAgcmFpc2UgR2xvc3NpQXBpRXJyb3IoIm5ldHdvcmsgZXJyb3I6ICVzIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICUgcHJvYy5zdGRlcnIuZGVjb2RlKCJ1dGYtOCIsICJyZXBsYWNlIikuc3RyaXAoKSkKICAgIGJvZHksIF8sIHN0YXR1c19zdHIgPSBwcm9jLnN0ZG91dC5ycGFydGl0aW9uKGIiXG4iKQogICAgdHJ5OgogICAgICAgIHN0YXR1cyA9IGludChzdGF0dXNfc3RyKQogICAgZXhjZXB0IFZhbHVlRXJyb3I6CiAgICAgICAgcmFpc2UgR2xvc3NpQXBpRXJyb3IoIm5ldHdvcmsgZXJyb3I6IHVuZXhwZWN0ZWQgY3VybCBvdXRwdXQgJXIiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJSBwcm9jLnN0ZG91dFstNDA6XSkKICAgIGlmIHN0YXR1cyA+PSA0MDA6CiAgICAgICAgcmFpc2UgX2Vycm9yX2Zyb21fcmVzcG9uc2Uoc3RhdHVzLCBib2R5LmRlY29kZSgidXRmLTgiLCAicmVwbGFjZSIpKQogICAgaWYgbm90IGJvZHk6CiAgICAgICAgcmV0dXJuIHt9CiAgICByZXR1cm4ganNvbi5sb2Fkcyhib2R5LmRlY29kZSgidXRmLTgiKSkKCgpkZWYgcmVzb2x2ZV9iYXNlX3VybChhcHBfbmFtZSk6CiAgICAiIiJUaGUgQVBJIGJhc2UgVVJMIGEgcGx1Z2luIHNob3VsZCB1c2UuCgogICAgUHJvZHVjdGlvbiBpcyBiYWtlZCBpbiAoREVGQVVMVF9CQVNFX1VSTCk7IG5vcm1hbCB1c2VycyBuZXZlciBjb25maWd1cmUKICAgIHRoaXMuIEZvciBkZXYvdGVzdGluZyBvbmx5LCBpdCBjYW4gYmUgb3ZlcnJpZGRlbiDigJQgcHJlY2VkZW5jZToKICAgIEdMT1NTSV9CQVNFX1VSTCBlbnYgdmFyID4gImJhc2VfdXJsIiBpbiB0aGUgYXBwJ3MgY29uZmlnIGZpbGUgPiBwcm9kLgogICAgU2V0L2NsZWFyIG92ZXJyaWRlcyB3aXRoIHNjcmlwdHMvZGV2X292ZXJyaWRlLnB5LgogICAgIiIiCiAgICBlbnYgPSBvcy5lbnZpcm9uLmdldCgiR0xPU1NJX0JBU0VfVVJMIikKICAgIGlmIGVudjoKICAgICAgICByZXR1cm4gZW52LnN0cmlwKCkucnN0cmlwKCIvIikKICAgIGNvbmZpZ3VyZWQgPSBsb2FkX2NvbmZpZyhhcHBfbmFtZSkuZ2V0KCJiYXNlX3VybCIpCiAgICBpZiBjb25maWd1cmVkOgogICAgICAgIHJldHVybiBjb25maWd1cmVkLnN0cmlwKCkucnN0cmlwKCIvIikKICAgIHJldHVybiBERUZBVUxUX0JBU0VfVVJMCgoKZGVmIHZhbGlkYXRlX2tleShhcGlfa2V5LCBiYXNlX3VybD1ERUZBVUxUX0JBU0VfVVJMKToKICAgICIiIkNoZWNrIGFuIEFQSSBrZXkgYW5kIGlkZW50aWZ5IGl0cyB3b3Jrc3BhY2UgdmlhIEdFVCAvbWUuCgogICAgUmV0dXJucyB0aGUgcmVzcG9uc2UgZGljdCwgZS5nLgogICAgeyJ3b3Jrc3BhY2UiOiB7ImlkIjogIi4uLiIsICJuYW1lIjogIi4uLiJ9LCAiYXBpS2V5IjogeyJuYW1lIjogIi4uLiIsIC4uLn19LgogICAgUmFpc2VzIEdsb3NzaUFwaUVycm9yIChjb2RlICJJTlZBTElEX0FQSV9LRVkiLCBodHRwX3N0YXR1cyA0MDEpIG9uIGEgYmFkIGtleS4KICAgICIiIgogICAgcmV0dXJuIF9yZXF1ZXN0KCJHRVQiLCBiYXNlX3VybCArICIvbWUiLCBhcGlfa2V5PWFwaV9rZXkpCgoKZGVmIGZpbGVfdHlwZV9mb3IocGF0aCk6CiAgICAiIiJCYXJlIGxvd2VyY2FzZSBleHRlbnNpb24gdGhlIEFQSSBleHBlY3RzIGFzIGZpbGVUeXBlICgiZ2xiIiwgImZieCIsIC4uLikuIiIiCiAgICBleHQgPSBvcy5wYXRoLnNwbGl0ZXh0KHBhdGgpWzFdCiAgICByZXR1cm4gZXh0LmxzdHJpcCgiLiIpLmxvd2VyKCkKCgpkZWYgdXBsb2FkX21vZGVsKHBhdGgsIGFwaV9rZXksIG5hbWU9Tm9uZSwgZmlsZV90eXBlPU5vbmUsCiAgICAgICAgICAgICAgICAgYmFzZV91cmw9REVGQVVMVF9CQVNFX1VSTCwgcG9sbF9zZWNvbmRzPTYsCiAgICAgICAgICAgICAgICAgdGltZW91dF9zZWNvbmRzPTYwMCwgcHJvZ3Jlc3M9Tm9uZSk6CiAgICAiIiJVcGxvYWQgYSBtb2RlbCBmaWxlIGFuZCBibG9jayB1bnRpbCBHbG9zc2kgaGFzIHByb2Nlc3NlZCBpdC4KCiAgICBDcmVhdGUgcmVjb3JkIC0+IFBVVCBieXRlcyB0byBzaWduZWQgUzMgVVJMIC0+IGNvbmZpcm0gLT4gcG9sbCBzdGF0dXMuCiAgICBSZXR1cm5zIHRoZSBmaW5hbCBtb2RlbCBkaWN0IGZyb20gL21vZGVscy9zdGF0dXMgKHN0YXR1cyBSRUFEWS9BUFBST1ZFRCkuCgogICAgYHByb2dyZXNzYCBpcyBhbiBvcHRpb25hbCBjYWxsYWJsZShzdHIpIGZvciBVSSBzdGF0dXMgbGluZXMuIFJhaXNlcwogICAgR2xvc3NpQXBpRXJyb3Igb24gYW55IGZhaWx1cmUsIGluY2x1ZGluZyBzZXJ2ZXItc2lkZSBGQUlMRUQgcHJvY2Vzc2luZwogICAgYW5kIHRoZSBwb2xsaW5nIHRpbWVvdXQuCiAgICAiIiIKICAgIGRlZiBzYXkobXNnKToKICAgICAgICBpZiBwcm9ncmVzczoKICAgICAgICAgICAgcHJvZ3Jlc3MobXNnKQoKICAgIGZpbGVfdHlwZSA9IGZpbGVfdHlwZSBvciBmaWxlX3R5cGVfZm9yKHBhdGgpCiAgICBpZiBub3QgZmlsZV90eXBlOgogICAgICAgIHJhaXNlIEdsb3NzaUFwaUVycm9yKCJjYW5ub3QgZGV0ZXJtaW5lIGZpbGUgdHlwZSBvZiAlciIgJSBwYXRoKQogICAgZmlsZV9uYW1lID0gb3MucGF0aC5iYXNlbmFtZShwYXRoKQogICAgbW9kZWxfbmFtZSA9IG5hbWUgb3Igb3MucGF0aC5zcGxpdGV4dChmaWxlX25hbWUpWzBdCgogICAgd2l0aCBvcGVuKHBhdGgsICJyYiIpIGFzIGY6CiAgICAgICAgcGF5bG9hZCA9IGYucmVhZCgpCgogICAgc2F5KCJSZWdpc3RlcmluZyBtb2RlbCB3aXRoIEdsb3NzaS4uLiIpCiAgICBjcmVhdGVkID0gX3JlcXVlc3QoIlBPU1QiLCBiYXNlX3VybCArICIvbW9kZWxzIiwgYXBpX2tleT1hcGlfa2V5LCBqc29uX2JvZHk9ewogICAgICAgICJtb2RlbHMiOiBbeyJuYW1lIjogbW9kZWxfbmFtZSwgImZpbGVOYW1lIjogZmlsZV9uYW1lLAogICAgICAgICAgICAgICAgICAgICJmaWxlVHlwZSI6IGZpbGVfdHlwZSwgImZpbGVTaXplIjogbGVuKHBheWxvYWQpfV0sCiAgICB9KQogICAgbW9kZWxzID0gY3JlYXRlZC5nZXQoIm1vZGVscyIpIG9yIFtdCiAgICBpZiBub3QgbW9kZWxzOgogICAgICAgIHJhaXNlIEdsb3NzaUFwaUVycm9yKCJubyB1cGxvYWQgVVJMIHJldHVybmVkOiAlcyIgJSBjcmVhdGVkLmdldCgiZXJyb3JzIikpCiAgICBtb2RlbF9pZCA9IG1vZGVsc1swXVsibW9kZWxJZCJdCiAgICB1cGxvYWRfdXJsID0gbW9kZWxzWzBdWyJ1cGxvYWRVcmwiXQoKICAgIHNheSgiVXBsb2FkaW5nICUuMWYgTUIuLi4iICUgKGxlbihwYXlsb2FkKSAvIDFlNikpCiAgICAjIFRoZSBzaWduZWQgVVJMIGNhcnJpZXMgaXRzIG93biBhdXRoIC0tIG5vIEFQSSBrZXkgb24gdGhpcyByZXF1ZXN0LgogICAgX3JlcXVlc3QoIlBVVCIsIHVwbG9hZF91cmwsIHJhd19ib2R5PXBheWxvYWQsCiAgICAgICAgICAgICBjb250ZW50X3R5cGU9X0NPTlRFTlRfVFlQRVMuZ2V0KGZpbGVfdHlwZSwgImFwcGxpY2F0aW9uL29jdGV0LXN0cmVhbSIpLAogICAgICAgICAgICAgdGltZW91dD02MDApCgogICAgc2F5KCJDb25maXJtaW5nIHVwbG9hZC4uLiIpCiAgICBfcmVxdWVzdCgiUE9TVCIsICIlcy9tb2RlbHMvJXMvY29uZmlybSIgJSAoYmFzZV91cmwsIG1vZGVsX2lkKSwKICAgICAgICAgICAgIGFwaV9rZXk9YXBpX2tleSwganNvbl9ib2R5PXsiZmlsZVR5cGUiOiBmaWxlX3R5cGV9KQoKICAgIHN0YXJ0ID0gdGltZS5tb25vdG9uaWMoKQogICAgd2hpbGUgVHJ1ZToKICAgICAgICBzdGF0dXMgPSBfcmVxdWVzdCgiUE9TVCIsIGJhc2VfdXJsICsgIi9tb2RlbHMvc3RhdHVzIiwgYXBpX2tleT1hcGlfa2V5LAogICAgICAgICAgICAgICAgICAgICAgICAgIGpzb25fYm9keT17Im1vZGVsSWRzIjogW21vZGVsX2lkXX0pCiAgICAgICAgbW9kZWwgPSAoc3RhdHVzLmdldCgibW9kZWxzIikgb3IgW3t9XSlbMF0KICAgICAgICBzdGF0ZSA9IG1vZGVsLmdldCgic3RhdHVzIiwgIlBFTkRJTkciKQogICAgICAgIGlmIG1vZGVsLmdldCgicmVhZHkiKToKICAgICAgICAgICAgc2F5KCJNb2RlbCByZWFkeS4iKQogICAgICAgICAgICByZXR1cm4gbW9kZWwKICAgICAgICBpZiBzdGF0ZSA9PSAiRkFJTEVEIjoKICAgICAgICAgICAgcmFpc2UgR2xvc3NpQXBpRXJyb3IoIm1vZGVsIHByb2Nlc3NpbmcgZmFpbGVkIHNlcnZlci1zaWRlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29kZT0iUFJPQ0VTU0lOR19GQUlMRUQiKQogICAgICAgIGlmIHRpbWUubW9ub3RvbmljKCkgLSBzdGFydCA+IHRpbWVvdXRfc2Vjb25kczoKICAgICAgICAgICAgcmFpc2UgR2xvc3NpQXBpRXJyb3IoCiAgICAgICAgICAgICAgICAidGltZWQgb3V0IGFmdGVyICVzcyB3YWl0aW5nIGZvciBwcm9jZXNzaW5nIChsYXN0IHN0YXR1czogJXMpIgogICAgICAgICAgICAgICAgJSAodGltZW91dF9zZWNvbmRzLCBzdGF0ZSkpCiAgICAgICAgc2F5KCJQcm9jZXNzaW5nICglcykuLi4iICUgc3RhdGUpCiAgICAgICAgdGltZS5zbGVlcChwb2xsX3NlY29uZHMpCgoKIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyBBUEkga2V5IHBlcnNpc3RlbmNlCiMKIyBPbmUgY29uZmlnIGZpbGUgcGVyIGhvc3QgYXBwIHNvIGEgbWFjaGluZSB3aXRoIHNldmVyYWwgRENDcyBrZWVwcwojIGluZGVwZW5kZW50IHNldHRpbmdzLiBHTE9TU0lfQ09ORklHX0RJUiBvdmVycmlkZXMgdGhlIGxvY2F0aW9uICh0ZXN0cykuCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgpkZWYgY29uZmlnX2RpcihhcHBfbmFtZSk6CiAgICBvdmVycmlkZSA9IG9zLmVudmlyb24uZ2V0KCJHTE9TU0lfQ09ORklHX0RJUiIpCiAgICBpZiBvdmVycmlkZToKICAgICAgICByZXR1cm4gb3MucGF0aC5qb2luKG92ZXJyaWRlLCBhcHBfbmFtZSkKICAgIGlmIHN5cy5wbGF0Zm9ybSA9PSAid2luMzIiOgogICAgICAgIGJhc2UgPSBvcy5lbnZpcm9uLmdldCgiTE9DQUxBUFBEQVRBIikgb3Igb3MucGF0aC5leHBhbmR1c2VyKCJ+IikKICAgICAgICByZXR1cm4gb3MucGF0aC5qb2luKGJhc2UsICJHbG9zc2kiLCBhcHBfbmFtZSkKICAgIGlmIHN5cy5wbGF0Zm9ybSA9PSAiZGFyd2luIjoKICAgICAgICByZXR1cm4gb3MucGF0aC5leHBhbmR1c2VyKCJ+L0xpYnJhcnkvQXBwbGljYXRpb24gU3VwcG9ydC9HbG9zc2kvJXMiICUgYXBwX25hbWUpCiAgICBiYXNlID0gb3MuZW52aXJvbi5nZXQoIlhER19DT05GSUdfSE9NRSIpIG9yIG9zLnBhdGguZXhwYW5kdXNlcigifi8uY29uZmlnIikKICAgIHJldHVybiBvcy5wYXRoLmpvaW4oYmFzZSwgImdsb3NzaSIsIGFwcF9uYW1lKQoKCmRlZiBfY29uZmlnX3BhdGgoYXBwX25hbWUpOgogICAgcmV0dXJuIG9zLnBhdGguam9pbihjb25maWdfZGlyKGFwcF9uYW1lKSwgImNvbmZpZy5qc29uIikKCgpkZWYgbG9hZF9jb25maWcoYXBwX25hbWUpOgogICAgdHJ5OgogICAgICAgIHdpdGggb3BlbihfY29uZmlnX3BhdGgoYXBwX25hbWUpLCAiciIpIGFzIGY6CiAgICAgICAgICAgIHJldHVybiBqc29uLmxvYWQoZikKICAgIGV4Y2VwdCAoT1NFcnJvciwgVmFsdWVFcnJvcik6CiAgICAgICAgcmV0dXJuIHt9CgoKZGVmIHNhdmVfY29uZmlnKGFwcF9uYW1lLCBjb25maWcpOgogICAgcGF0aCA9IF9jb25maWdfcGF0aChhcHBfbmFtZSkKICAgIG9zLm1ha2VkaXJzKG9zLnBhdGguZGlybmFtZShwYXRoKSwgZXhpc3Rfb2s9VHJ1ZSkKICAgIHdpdGggb3BlbihwYXRoLCAidyIpIGFzIGY6CiAgICAgICAganNvbi5kdW1wKGNvbmZpZywgZiwgaW5kZW50PTIpCiAgICB0cnk6CiAgICAgICAgb3MuY2htb2QocGF0aCwgMG82MDApICAjIHRoZSBjb25maWcgaG9sZHMgdGhlIEFQSSBrZXkKICAgIGV4Y2VwdCBPU0Vycm9yOgogICAgICAgIHBhc3MKCgpkZWYgbG9hZF9hcGlfa2V5KGFwcF9uYW1lKToKICAgIHJldHVybiBsb2FkX2NvbmZpZyhhcHBfbmFtZSkuZ2V0KCJhcGlfa2V5Iikgb3IgTm9uZQoKCmRlZiBzYXZlX2FwaV9rZXkoYXBwX25hbWUsIGFwaV9rZXkpOgogICAgY29uZmlnID0gbG9hZF9jb25maWcoYXBwX25hbWUpCiAgICBjb25maWdbImFwaV9rZXkiXSA9IGFwaV9rZXkKICAgIHNhdmVfY29uZmlnKGFwcF9uYW1lLCBjb25maWcpCg==").decode("utf-8"), glossi_client.__dict__)
_sys.modules["glossi_client"] = glossi_client

# -*- coding: utf-8 -*-
# AUTHOR Glossi
# VERSION dev
# Export the open scene as GLB (baked PBR textures) and upload it to your Glossi workspace.
"""Glossi Uploader — KeyShot script.

Runs from KeyShot's Scripting Console (Window > Scripting Console > Scripts
tab). Exports the open scene as GLB with lux.exportFile — KeyShot bakes its
procedural materials to PBR textures during glTF export, which is what makes
a KeyShot scene portable to Glossi — then uploads the result to the Glossi
workspace tied to the stored API key.

KeyShot scripts are modal: dialogs block, and KeyShot is unresponsive while
the upload + processing poll runs. Progress lines go to the console output.

Distribution is a single .py built by scripts/package_keyshot.py, which
inlines the shared glossi_client. In the repo, the client is a vendored
sibling.

Requires KeyShot 11 or newer (Python 3.10+ embedded). Scripting is a
KeyShot Pro / Studio feature — it is not available in viewer-only licenses.
"""

import os
import re
import shutil
import sys
import tempfile


import lux  # KeyShot-injected

APP_NAME = "keyshot"
DEFAULT_WEB_URL = "https://app.glossi.io"
MIN_KEYSHOT_MAJOR = 11

# dpi / sample pairs for the glTF texture-baking step. Order matters — the
# first entry is the dialog default.
BAKE_QUALITIES = [
    ("Standard", {"dpi": 150, "num_samples": 32}),
    ("High (slower, larger file)", {"dpi": 300, "num_samples": 64}),
]


def _say(msg):
    print("[Glossi] %s" % msg)


def _info(msg):
    lux.getMessageBox(msg, title="Glossi", type=lux.MESSAGE_BOX_INFO)


def _error(msg):
    lux.getMessageBox(msg, title="Glossi", type=lux.MESSAGE_BOX_CRITICAL)


def _base_url():
    return glossi_client.resolve_base_url(APP_NAME)


def _dev_override_active():
    return _base_url() != glossi_client.DEFAULT_BASE_URL


def _web_url():
    return (glossi_client.load_config(APP_NAME).get("web_url")
            or DEFAULT_WEB_URL).rstrip("/")


def _version_ok():
    try:
        return lux.getKeyShotVersion()[0] >= MIN_KEYSHOT_MAJOR
    except Exception:
        return False  # getKeyShotVersion missing == far too old


def _safe_filename(name):
    cleaned = re.sub(r"[^\w\-. ]", "_", name).strip()
    return cleaned or "model"


def _default_model_name():
    try:
        info = lux.getSceneInfo()
    except Exception:
        return "KeyShot Model"
    name = (info.get("name") or "").strip()
    if name:
        return os.path.splitext(name)[0]
    file_name = (info.get("file name") or "").strip()
    if file_name:
        return os.path.splitext(os.path.basename(file_name))[0]
    return "KeyShot Model"


def _ensure_api_key():
    key = glossi_client.load_api_key(APP_NAME)
    if key:
        return key
    entered = lux.getInputText(
        title="Connect to Glossi",
        label="Workspace API key (glsi_..., from Glossi workspace settings):",
        value="")
    if not entered or not entered.strip():
        return None
    key = entered.strip()
    glossi_client.save_api_key(APP_NAME, key)
    return key


def _bake_settings(choice):
    """Map the quality dialog value (index or label, depending on KeyShot's
    DIALOG_ITEM return convention) to a dpi/samples dict."""
    if isinstance(choice, int) and 0 <= choice < len(BAKE_QUALITIES):
        return BAKE_QUALITIES[choice][1]
    for label, settings in BAKE_QUALITIES:
        if choice == label:
            return settings
    return BAKE_QUALITIES[0][1]


def _upload_dialog(workspace, default_name):
    desc = "Connected to workspace: %s" % workspace
    if _dev_override_active():
        desc += "\nDev API override active: %s" % _base_url()
    values = [
        ("name", lux.DIALOG_TEXT, "Model name", default_name),
        ("quality", lux.DIALOG_ITEM, "Texture baking quality", 0,
         [label for label, _ in BAKE_QUALITIES]),
    ]
    return lux.getInputDialog(title="Upload to Glossi", desc=desc,
                              values=values)


def run():
    if not _version_ok():
        _error("Glossi Uploader needs KeyShot %d or newer."
               % MIN_KEYSHOT_MAJOR)
        return

    key = _ensure_api_key()
    if not key:
        _say("No API key entered — nothing uploaded.")
        return

    base_url = _base_url()
    try:
        info = glossi_client.validate_key(key, base_url=base_url)
    except glossi_client.GlossiApiError as exc:
        if exc.http_status == 401:
            glossi_client.save_api_key(APP_NAME, "")
            _error("That API key was not accepted (revoked or mistyped). "
                   "It has been cleared — run the script again to enter a "
                   "new one.")
        else:
            _error("Could not reach the Glossi API.\n%s" % exc)
        return
    workspace = info["workspace"]["name"]
    _say("Connected to workspace: %s" % workspace)

    result = _upload_dialog(workspace, _default_model_name())
    if result is None:
        _say("Cancelled — nothing uploaded.")
        return
    name = (result.get("name") or "").strip() or _default_model_name()
    bake = _bake_settings(result.get("quality"))

    out_dir = tempfile.mkdtemp(prefix="glossi_keyshot_")
    target = os.path.join(out_dir, _safe_filename(name) + ".glb")

    _say("Exporting GLB (baking textures at %d dpi, %d samples)..."
         % (bake["dpi"], bake["num_samples"]))
    mode = {
        "mode": lux.EXPORT_BAKING,
        "dpi": bake["dpi"],
        "num_samples": bake["num_samples"],
        # AO stays off: Glossi relights the model in Unreal, and baked
        # occlusion double-darkens under real lighting.
        "occlusion": False,
        # Draco stays off for maximum converter compatibility.
        "draco_compression": False,
        "preferred_output": lux.EXPORT_OUTPUT_TEXTURES,
        "keep_uvs": True,
    }
    try:
        lux.exportFile(target, lux.EXPORT_GLTF, mode)
    except Exception as exc:
        shutil.rmtree(out_dir, ignore_errors=True)
        _error("GLB export failed.\n%s" % exc)
        return
    if not os.path.isfile(target) or os.path.getsize(target) == 0:
        shutil.rmtree(out_dir, ignore_errors=True)
        _error("GLB export produced no file — is the scene empty?")
        return

    _say("Uploading to %s — KeyShot may be unresponsive until this finishes."
         % workspace)
    try:
        model = glossi_client.upload_model(target, key, name=name,
                                           base_url=base_url, progress=_say)
    except glossi_client.GlossiApiError as exc:
        # Keep the GLB on disk for a manual retry.
        _error("Upload failed.\n%s\n\nThe exported GLB is still at:\n%s"
               % (exc, target))
        return

    shutil.rmtree(out_dir, ignore_errors=True)
    _info("\"%s\" is uploaded and ready in the %s library.\n\n"
          "Find it under Library > Models at %s"
          % (model["name"], workspace, _web_url()))


run()
