From 145a0df082654b960a225c276ff1328ccac67a29 Mon Sep 17 00:00:00 2001
From: humbletim <humbletim@gmail.com>
Date: Thu, 15 Feb 2018 14:14:07 -0500
Subject: [PATCH] interim checkin

---
 interface/src/Application.cpp                 |   4 +-
 interface/src/ui/overlays/Base3DOverlay.h     |   4 +-
 interface/src/ui/overlays/ModelOverlay.cpp    |   2 +-
 interface/src/ui/overlays/ModelOverlay.h      |   2 +-
 interface/src/ui/overlays/Shape3DOverlay.cpp  |   6 +-
 interface/src/ui/overlays/Shape3DOverlay.h    |   2 +-
 libraries/entities-renderer/CMakeLists.txt    |   2 +-
 .../src/RenderableEntityItem.h                |   4 +-
 .../src/RenderableModelEntityItem.cpp         |  19 +-
 .../src/RenderableModelEntityItem.h           |   3 +-
 .../graphics-scripting/BufferViewHelpers.cpp  | 416 +++++++++-
 .../graphics-scripting/BufferViewHelpers.h    |  35 +-
 .../BufferViewScripting.cpp                   |   4 +-
 .../src/graphics-scripting/Forward.h          | 107 +++
 .../GraphicsScriptingUtil.cpp                 |   3 +
 .../GraphicsScriptingUtil.h                   |  85 +++
 .../ModelScriptingInterface.cpp               | 152 ++--
 .../ModelScriptingInterface.h                 |  10 +-
 .../src/graphics-scripting/ScriptableMesh.cpp | 714 +++++++++---------
 .../src/graphics-scripting/ScriptableMesh.h   | 198 +++--
 .../src/graphics-scripting/ScriptableModel.h  |  81 +-
 21 files changed, 1219 insertions(+), 634 deletions(-)
 create mode 100644 libraries/graphics-scripting/src/graphics-scripting/Forward.h
 create mode 100644 libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.cpp
 create mode 100644 libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.h

diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index f24969ce60..bdef2f456b 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -600,7 +600,9 @@ public:
         QString error;
 
         scriptable::ModelProviderPointer provider;
-        if (auto entityInterface = getEntityModelProvider(static_cast<EntityItemID>(uuid))) {
+        if (uuid.isNull()) {
+            provider = nullptr;
+        } else if (auto entityInterface = getEntityModelProvider(static_cast<EntityItemID>(uuid))) {
             provider = entityInterface;
         } else if (auto overlayInterface = getOverlayModelProvider(static_cast<OverlayID>(uuid))) {
             provider = overlayInterface;
diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h
index 0c8bc5aacb..6ccad338c9 100644
--- a/interface/src/ui/overlays/Base3DOverlay.h
+++ b/interface/src/ui/overlays/Base3DOverlay.h
@@ -13,7 +13,7 @@
 
 #include <Transform.h>
 #include <SpatiallyNestable.h>
-#include <graphics-scripting/ScriptableModel.h>
+#include <graphics-scripting/Forward.h>
 #include "Overlay.h"
 
 namespace model { class Mesh; }
@@ -37,7 +37,7 @@ public:
     virtual bool is3D() const override { return true; }
 
     virtual uint32_t fetchMetaSubItems(render::ItemIDs& subItems) const override { subItems.push_back(getRenderItemID()); return (uint32_t) subItems.size(); }
-    virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override { return scriptable::ModelProvider::modelUnavailableError(ok); }
+    virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) override { return scriptable::ModelProvider::modelUnavailableError(ok); }
 
     // TODO: consider implementing registration points in this class
     glm::vec3 getCenter() const { return getWorldPosition(); }
diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp
index 5a80ca1abf..e007591ce0 100644
--- a/interface/src/ui/overlays/ModelOverlay.cpp
+++ b/interface/src/ui/overlays/ModelOverlay.cpp
@@ -630,7 +630,7 @@ uint32_t ModelOverlay::fetchMetaSubItems(render::ItemIDs& subItems) const {
     return 0;
 }
 
-scriptable::ScriptableModel ModelOverlay::getScriptableModel(bool* ok) {
+scriptable::ScriptableModelBase ModelOverlay::getScriptableModel(bool* ok) {
     if (!_model || !_model->isLoaded()) {
         return Base3DOverlay::getScriptableModel(ok);
     }
diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h
index 32d9a08c70..8dc386c733 100644
--- a/interface/src/ui/overlays/ModelOverlay.h
+++ b/interface/src/ui/overlays/ModelOverlay.h
@@ -59,7 +59,7 @@ public:
     void setDrawInFront(bool drawInFront) override;
     void setDrawHUDLayer(bool drawHUDLayer) override;
 
-    virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override;
+    virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) override;
 protected:
     Transform evalRenderTransform() override;
 
diff --git a/interface/src/ui/overlays/Shape3DOverlay.cpp b/interface/src/ui/overlays/Shape3DOverlay.cpp
index 8bb3d16888..54423feef6 100644
--- a/interface/src/ui/overlays/Shape3DOverlay.cpp
+++ b/interface/src/ui/overlays/Shape3DOverlay.cpp
@@ -180,15 +180,15 @@ Transform Shape3DOverlay::evalRenderTransform() {
     return transform;
 }
 
-scriptable::ScriptableModel Shape3DOverlay::getScriptableModel(bool* ok) {
+scriptable::ScriptableModelBase Shape3DOverlay::getScriptableModel(bool* ok) {
     auto geometryCache = DependencyManager::get<GeometryCache>();
     auto vertexColor = ColorUtils::toVec3(_color);
-    scriptable::ScriptableModel result;
+    scriptable::ScriptableModelBase result;
     result.metadata = {
         { "origin", "Shape3DOverlay::"+shapeStrings[_shape] },
         { "overlayID", getID() },
     };
-    result.meshes << geometryCache->meshFromShape(_shape, vertexColor);
+    result.append(geometryCache->meshFromShape(_shape, vertexColor), {{ "shape", shapeStrings[_shape] }});
     if (ok) {
         *ok = true;
     }
diff --git a/interface/src/ui/overlays/Shape3DOverlay.h b/interface/src/ui/overlays/Shape3DOverlay.h
index 34f82af278..f5246d95ac 100644
--- a/interface/src/ui/overlays/Shape3DOverlay.h
+++ b/interface/src/ui/overlays/Shape3DOverlay.h
@@ -37,7 +37,7 @@ public:
     void setProperties(const QVariantMap& properties) override;
     QVariant getProperty(const QString& property) override;
 
-    virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override;
+    virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) override;
 protected:
     Transform evalRenderTransform() override;
 
diff --git a/libraries/entities-renderer/CMakeLists.txt b/libraries/entities-renderer/CMakeLists.txt
index 27ea04f642..3aa561f927 100644
--- a/libraries/entities-renderer/CMakeLists.txt
+++ b/libraries/entities-renderer/CMakeLists.txt
@@ -13,7 +13,7 @@ include_hifi_library_headers(fbx)
 include_hifi_library_headers(entities)
 include_hifi_library_headers(avatars)
 include_hifi_library_headers(controllers)
-include_hifi_library_headers(graphics-scripting) # for ScriptableModel.h
+include_hifi_library_headers(graphics-scripting) # for Forward.h
 
 target_bullet()
 target_polyvox()
diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h
index f07b67fbd0..74759f4fe4 100644
--- a/libraries/entities-renderer/src/RenderableEntityItem.h
+++ b/libraries/entities-renderer/src/RenderableEntityItem.h
@@ -17,7 +17,7 @@
 #include <Sound.h>
 #include "AbstractViewStateInterface.h"
 #include "EntitiesRendererLogging.h"
-#include <graphics-scripting/ScriptableModel.h>
+#include <graphics-scripting/Forward.h>
 
 class EntityTreeRenderer;
 
@@ -55,7 +55,7 @@ public:
 
     const uint64_t& getUpdateTime() const { return _updateTime; }
 
-    virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override { return scriptable::ModelProvider::modelUnavailableError(ok); }
+    virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) override { return scriptable::ModelProvider::modelUnavailableError(ok); }
 protected:
     virtual bool needsRenderUpdateFromEntity() const final { return needsRenderUpdateFromEntity(_entity); }
     virtual void onAddToScene(const EntityItemPointer& entity);
diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
index 7b022fefac..3d6714a400 100644
--- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
@@ -950,12 +950,9 @@ QStringList RenderableModelEntityItem::getJointNames() const {
     return result;
 }
 
-
-scriptable::ScriptableModel render::entities::ModelEntityRenderer::getScriptableModel(bool* ok) {
+scriptable::ScriptableModelBase render::entities::ModelEntityRenderer::getScriptableModel(bool* ok) {
     ModelPointer model;
-    withReadLock([&] {
-        model = _model;
-    });
+    withReadLock([&] { model = _model; });
 
     if (!model || !model->isLoaded()) {
         return scriptable::ModelProvider::modelUnavailableError(ok);
@@ -964,6 +961,18 @@ scriptable::ScriptableModel render::entities::ModelEntityRenderer::getScriptable
     return _model->getScriptableModel(ok);
 }
 
+bool render::entities::ModelEntityRenderer::replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointer newModel, int meshIndex, int partIndex) {
+    qCDebug(entitiesrenderer) << "REPLACING RenderableModelEntityItem" << newModel->objectName();
+    ModelPointer model;
+    withReadLock([&] { model = _model; });
+
+    if (!model || !model->isLoaded()) {
+        return false;
+    }
+
+   return _model->replaceScriptableModelMeshPart(newModel, meshIndex, partIndex);
+}
+
 void RenderableModelEntityItem::simulateRelayedJoints() {
     ModelPointer model = getModel();
     if (model && model->isLoaded()) {
diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h
index 3e952cb9a7..ffb83d3609 100644
--- a/libraries/entities-renderer/src/RenderableModelEntityItem.h
+++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h
@@ -140,7 +140,8 @@ class ModelEntityRenderer : public TypedEntityRenderer<RenderableModelEntityItem
 
 public:
     ModelEntityRenderer(const EntityItemPointer& entity);
-    virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override;
+    virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) override;
+    virtual bool replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointer model, int meshIndex, int partIndex) override;
 
 protected:
     virtual void removeFromScene(const ScenePointer& scene, Transaction& transaction) override;
diff --git a/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.cpp b/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.cpp
index d83322f360..68cebe2d78 100644
--- a/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.cpp
+++ b/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.cpp
@@ -7,23 +7,48 @@
 #include <gpu/Format.h>
 #include <gpu/Stream.h>
 
+#include <graphics/Geometry.h>
+
+#include <Extents.h>
+#include <AABox.h>
+
+#include <glm/gtx/string_cast.hpp>
 #include <glm/gtc/packing.hpp>
+#include <glm/detail/type_vec.hpp>
 namespace glm {
     using hvec2 = glm::tvec2<glm::detail::hdata>;
     using hvec4 = glm::tvec4<glm::detail::hdata>;
 }
 
 //#define DEBUG_BUFFERVIEW_SCRIPTING
-#ifdef DEBUG_BUFFERVIEW_SCRIPTING
+//#ifdef DEBUG_BUFFERVIEW_SCRIPTING
     #include "DebugNames.h"
-    QLoggingCategory bufferview_helpers{"hifi.bufferview"};
-#endif
+//#endif
 
 namespace {
+    QLoggingCategory bufferhelper_logging{"hifi.bufferview"};
     const std::array<const char*, 4> XYZW = {{ "x", "y", "z", "w" }};
     const std::array<const char*, 4> ZERO123 = {{ "0", "1", "2", "3" }};
 }
 
+gpu::BufferView buffer_helpers::getBufferView(graphics::MeshPointer mesh, gpu::Stream::Slot slot) {
+    return slot == gpu::Stream::POSITION ? mesh->getVertexBuffer() : mesh->getAttributeBuffer(slot);
+}
+QMap<QString,int> buffer_helpers::ATTRIBUTES{
+    {"position", gpu::Stream::POSITION },
+    {"normal", gpu::Stream::NORMAL },
+    {"color", gpu::Stream::COLOR },
+    {"tangent", gpu::Stream::TEXCOORD0 },
+    {"skin_cluster_index", gpu::Stream::SKIN_CLUSTER_INDEX },
+    {"skin_cluster_weight", gpu::Stream::SKIN_CLUSTER_WEIGHT },
+    {"texcoord0", gpu::Stream::TEXCOORD0 },
+    {"texcoord1", gpu::Stream::TEXCOORD1 },
+    {"texcoord2", gpu::Stream::TEXCOORD2 },
+    {"texcoord3", gpu::Stream::TEXCOORD3 },
+    {"texcoord4", gpu::Stream::TEXCOORD4 },
+};
+
+
 template <typename T>
 QVariant getBufferViewElement(const gpu::BufferView& view, quint32 index, bool asArray = false) {
     return glmVecToVariant(view.get<T>(index), asArray);
@@ -61,14 +86,14 @@ static void packNormalAndTangent(glm::vec3 normal, glm::vec3 tangent, glm::uint3
     packedTangent = tangentStruct.pack;
 }
 
-bool bufferViewElementFromVariant(const gpu::BufferView& view, quint32 index, const QVariant& v) {
+bool buffer_helpers::fromVariant(const gpu::BufferView& view, quint32 index, const QVariant& v) {
     const auto& element = view._element;
     const auto vecN = element.getScalarCount();
     const auto dataType = element.getType();
     const auto byteLength = element.getSize();
     const auto BYTES_PER_ELEMENT = byteLength / vecN;
 #ifdef DEBUG_BUFFERVIEW_SCRIPTING
-    qCDebug(bufferview_helpers) << "bufferViewElementFromVariant" << index << DebugNames::stringFrom(dataType) << BYTES_PER_ELEMENT << vecN;
+    qCDebug(bufferhelper_logging) << "bufferViewElementFromVariant" << index << DebugNames::stringFrom(dataType) << BYTES_PER_ELEMENT << vecN;
 #endif
     if (BYTES_PER_ELEMENT == 1) {
         switch(vecN) {
@@ -122,16 +147,34 @@ bool bufferViewElementFromVariant(const gpu::BufferView& view, quint32 index, co
     return false;
 }
 
-QVariant bufferViewElementToVariant(const gpu::BufferView& view, quint32 index, bool asArray, const char* hint) {
+bool boundsCheck(const gpu::BufferView& view, quint32 index) {
+    const auto byteLength = view._element.getSize();
+    return (
+        index < view.getNumElements() &&
+        index * byteLength < (view._size - 1) * byteLength
+     );
+}
+    
+QVariant buffer_helpers::toVariant(const gpu::BufferView& view, quint32 index, bool asArray, const char* hint) {
     const auto& element = view._element;
     const auto vecN = element.getScalarCount();
     const auto dataType = element.getType();
     const auto byteLength = element.getSize();
     const auto BYTES_PER_ELEMENT = byteLength / vecN;
     Q_ASSERT(index < view.getNumElements());
-    Q_ASSERT(index * vecN * BYTES_PER_ELEMENT < (view._size - vecN * BYTES_PER_ELEMENT));
+    if (!boundsCheck(view, index)) {
+        // sanity checks
+        auto byteOffset = index * vecN * BYTES_PER_ELEMENT;
+        auto maxByteOffset = (view._size - 1) * vecN * BYTES_PER_ELEMENT;
+        if (byteOffset > maxByteOffset) {
+            qDebug() << "bufferViewElementToVariant -- byteOffset out of range " << byteOffset << " < " << maxByteOffset << DebugNames::stringFrom(dataType);
+            qDebug() << "bufferViewElementToVariant -- index: " << index << "numElements" << view.getNumElements();
+            qDebug() << "bufferViewElementToVariant -- vecN: " << vecN << "byteLength" << byteLength << "BYTES_PER_ELEMENT" << BYTES_PER_ELEMENT;
+        }
+        Q_ASSERT(byteOffset <= maxByteOffset);
+    }
 #ifdef DEBUG_BUFFERVIEW_SCRIPTING
-    qCDebug(bufferview_helpers) << "bufferViewElementToVariant" << index << DebugNames::stringFrom(dataType) << BYTES_PER_ELEMENT << vecN;
+    qCDebug(bufferhelper_logging) << "bufferViewElementToVariant" << index << DebugNames::stringFrom(dataType) << BYTES_PER_ELEMENT << vecN;
 #endif
     if (BYTES_PER_ELEMENT == 1) {
         switch(vecN) {
@@ -221,22 +264,137 @@ const T glmVecFromVariant(const QVariant& v) {
 }
 
 template <typename T>
-gpu::BufferView bufferViewFromVector(QVector<T> elements, gpu::Element elementType) {
+gpu::BufferView buffer_helpers::fromVector(const QVector<T>& elements, const gpu::Element& elementType) {
     auto vertexBuffer = std::make_shared<gpu::Buffer>(elements.size() * sizeof(T), (gpu::Byte*)elements.data());
     return { vertexBuffer, 0, vertexBuffer->getSize(),sizeof(T), elementType };
 }
+template<> gpu::BufferView buffer_helpers::fromVector<unsigned int>(const QVector<unsigned int>& elements, const gpu::Element& elementType) { return fromVector(elements, elementType); }
+template<> gpu::BufferView buffer_helpers::fromVector<glm::vec3>(const QVector<glm::vec3>& elements, const gpu::Element& elementType) { return fromVector(elements, elementType); }
 
-template<> gpu::BufferView bufferViewFromVector<unsigned int>(QVector<unsigned int> elements, gpu::Element elementType) { return bufferViewFromVector(elements, elementType); }
-template<> gpu::BufferView bufferViewFromVector<glm::vec3>(QVector<glm::vec3> elements, gpu::Element elementType) { return bufferViewFromVector(elements, elementType); }
+template <typename T> struct getVec4;// { static T get(const gpu::BufferView& view, quint32 index, const char *hint); };
+template <typename T> struct getScalar;// { static T get(const gpu::BufferView& view, quint32 index, const char *hint); };
 
-gpu::BufferView cloneBufferView(const gpu::BufferView& input) {
+struct gotter {
+    static float error(const QString& name, const gpu::BufferView& view, quint32 index, const char *hint) {
+        qDebug() << QString("gotter:: unhandled type=%1(element=%2(%3)) size=%4(per=%5) vec%6 hint=%7 #%8")
+            .arg(name)
+            .arg(DebugNames::stringFrom(view._element.getType()))
+            .arg(view._element.getType())
+            .arg(view._element.getSize())
+            .arg(view._element.getSize() / view._element.getScalarCount())
+            .arg(view._element.getScalarCount())
+            .arg(hint)
+            .arg(view.getNumElements());
+        Q_ASSERT(false);
+        assert(false);
+        return NAN;
+    }        
+};
+template <typename T> struct getScalar : gotter {
+    static T get(const gpu::BufferView& view, quint32 index, const char *hint) { switch(view._element.getType()) {
+        case gpu::UINT32: return view.get<glm::uint32>(index);
+        case gpu::UINT16: return view.get<glm::uint16>(index);
+        case gpu::UINT8: return view.get<glm::uint8>(index);
+        case gpu::INT32: return view.get<glm::int32>(index);
+        case gpu::INT16: return view.get<glm::int16>(index);
+        case gpu::INT8: return view.get<glm::int8>(index);
+        case gpu::FLOAT: return view.get<glm::float32>(index);
+        case gpu::HALF: return T(glm::unpackSnorm1x8(view.get<glm::int8>(index)));
+        default: break;
+        } return T(error("getScalar", view, index, hint));
+    }
+};
+
+template <typename T> struct getVec2 : gotter { static T get(const gpu::BufferView& view, quint32 index, const char *hint) { switch(view._element.getType()) {
+        case gpu::UINT32: return view.get<glm::u32vec2>(index);
+        case gpu::UINT16: return view.get<glm::u16vec2>(index);
+        case gpu::UINT8: return view.get<glm::u8vec2>(index);
+        case gpu::INT32: return view.get<glm::i32vec2>(index);
+        case gpu::INT16: return view.get<glm::i16vec2>(index);
+        case gpu::INT8: return view.get<glm::i8vec2>(index);
+        case gpu::FLOAT: return view.get<glm::fvec2>(index);
+        case gpu::HALF: return glm::unpackSnorm2x8(view.get<glm::int16>(index));
+        default: break;
+        } return T(error("getVec2", view, index, hint)); }};
+
+
+template <typename T> struct getVec3  : gotter  { static T get(const gpu::BufferView& view, quint32 index, const char *hint) { switch(view._element.getType()) {
+        case gpu::UINT32: return view.get<glm::u32vec3>(index);
+        case gpu::UINT16: return view.get<glm::u16vec3>(index);
+        case gpu::UINT8: return view.get<glm::u8vec3>(index);
+        case gpu::INT32: return view.get<glm::i32vec3>(index);
+        case gpu::INT16: return view.get<glm::i16vec3>(index);
+        case gpu::INT8: return view.get<glm::i8vec3>(index);
+        case gpu::FLOAT: return view.get<glm::fvec3>(index);
+        case gpu::HALF:
+        case gpu::NUINT8:
+        case gpu::NINT2_10_10_10:
+            if (view._element.getSize() == sizeof(glm::int32)) {
+                return getVec4<T>::get(view, index, hint);
+            }
+        default: break;
+        } return T(error("getVec3", view, index, hint)); }};
+
+template <typename T> struct getVec4 : gotter { static T get(const gpu::BufferView& view, quint32 index, const char *hint) {
+    assert(view._element.getSize() == sizeof(glm::int32));
+    switch(view._element.getType()) {
+    case gpu::UINT32: return view.get<glm::u32vec4>(index);
+    case gpu::UINT16: return view.get<glm::u16vec4>(index);
+    case gpu::UINT8: return view.get<glm::u8vec4>(index);
+    case gpu::INT32: return view.get<glm::i32vec4>(index);
+    case gpu::INT16: return view.get<glm::i16vec4>(index);
+    case gpu::INT8: return view.get<glm::i8vec4>(index);
+    case gpu::NUINT32: break;
+    case gpu::NUINT16: break;
+    case gpu::NUINT8: return glm::unpackUnorm4x8(view.get<glm::uint32>(index)); 
+    case gpu::NUINT2: break;
+    case gpu::NINT32: break;
+    case gpu::NINT16: break;
+    case gpu::NINT8: break;
+    case gpu::COMPRESSED: break;
+    case gpu::NUM_TYPES: break;
+    case gpu::FLOAT: return view.get<glm::fvec4>(index);
+    case gpu::HALF: return glm::unpackSnorm4x8(view.get<glm::int32>(index));
+    case gpu::NINT2_10_10_10: return glm::unpackSnorm3x10_1x2(view.get<glm::uint32>(index));
+    } return T(error("getVec4", view, index, hint)); }};
+
+
+template <typename FUNC, typename T>
+struct getVec {
+    static QVector<T> __to_vector__(const gpu::BufferView& view, const char *hint) {
+        QVector<T> result;
+        const quint32 count = (quint32)view.getNumElements();
+        result.resize(count);
+        for (quint32 i = 0; i < count; i++) {
+            result[i] = FUNC::get(view, i, hint);
+        }
+        return result;
+    }
+    static T __to_scalar__(const gpu::BufferView& view, quint32 index, const char *hint) {
+        assert(boundsCheck(view, index));
+        return FUNC::get(view, index, hint);
+    }
+};
+
+template <> QVector<int> buffer_helpers::toVector<int>(const gpu::BufferView& view, const char *hint) { return getVec<getScalar<int>,int>::__to_vector__(view, hint); }
+template <> QVector<glm::vec2> buffer_helpers::toVector<glm::vec2>(const gpu::BufferView& view, const char *hint) { return getVec<getVec2<glm::vec2>,glm::vec2>::__to_vector__(view, hint); }
+template <> QVector<glm::vec3> buffer_helpers::toVector<glm::vec3>(const gpu::BufferView& view, const char *hint) { return getVec<getVec3<glm::vec3>,glm::vec3>::__to_vector__(view, hint); }
+template <> QVector<glm::vec4> buffer_helpers::toVector<glm::vec4>(const gpu::BufferView& view, const char *hint) { return getVec<getVec4<glm::vec4>,glm::vec4>::__to_vector__(view, hint); }
+
+
+template <> int buffer_helpers::convert<int>(const gpu::BufferView& view, quint32 index, const char *hint) { return getVec<getScalar<int>,int>::__to_scalar__(view, index, hint); }
+template <> glm::vec2 buffer_helpers::convert<glm::vec2>(const gpu::BufferView& view, quint32 index, const char *hint) { return getVec<getVec2<glm::vec2>,glm::vec2>::__to_scalar__(view, index, hint); }
+template <> glm::vec3 buffer_helpers::convert<glm::vec3>(const gpu::BufferView& view, quint32 index, const char *hint) { return getVec<getVec3<glm::vec3>,glm::vec3>::__to_scalar__(view, index, hint); }
+template <> glm::vec4 buffer_helpers::convert<glm::vec4>(const gpu::BufferView& view, quint32 index, const char *hint) { return getVec<getVec4<glm::vec4>,glm::vec4>::__to_scalar__(view, index, hint); }
+
+gpu::BufferView buffer_helpers::clone(const gpu::BufferView& input) {
     return gpu::BufferView(
         std::make_shared<gpu::Buffer>(input._buffer->getSize(), input._buffer->getData()),
         input._offset, input._size, input._stride, input._element
         );
 }
 
-gpu::BufferView resizedBufferView(const gpu::BufferView& input, quint32 numElements) {
+gpu::BufferView buffer_helpers::resize(const gpu::BufferView& input, quint32 numElements) {
     auto effectiveSize = input._buffer->getSize() / input.getNumElements();
     qDebug() << "resize input" << input.getNumElements() << input._buffer->getSize() << "effectiveSize" << effectiveSize;
     auto vsize = input._element.getSize() * numElements;
@@ -248,3 +406,235 @@ gpu::BufferView resizedBufferView(const gpu::BufferView& input, quint32 numEleme
     qDebug() << "resized output" << output.getNumElements() << output._buffer->getSize();
     return output;
 }
+
+graphics::MeshPointer buffer_helpers::cloneMesh(graphics::MeshPointer mesh) {
+    auto clone = std::make_shared<graphics::Mesh>();
+    //[](graphics::Mesh* blah) {
+        //qCDebug(bufferhelper_logging) << "--- DELETING MESH POINTER" << blah;
+    //  delete blah;
+    //});
+    clone->displayName = (QString::fromStdString(mesh->displayName) + "-clone").toStdString();
+    //qCInfo(bufferhelper_logging) << "+++ ALLOCATED MESH POINTER ScriptableMesh::cloneMesh" << clone->displayName << clone.get() << !!mesh;
+    clone->setIndexBuffer(buffer_helpers::clone(mesh->getIndexBuffer()));
+    clone->setPartBuffer(buffer_helpers::clone(mesh->getPartBuffer()));
+    auto attributeViews = buffer_helpers::gatherBufferViews(mesh);
+    for (const auto& a : attributeViews) {
+        auto& view = a.second;
+        auto slot = buffer_helpers::ATTRIBUTES[a.first];
+        auto points = buffer_helpers::clone(view);
+        if (slot == gpu::Stream::POSITION) {
+            clone->setVertexBuffer(points);
+        } else {
+            clone->addAttribute(slot, points);
+        }
+    }
+    return clone;
+}
+
+
+/// --- buffer view <-> variant helpers
+
+namespace {
+    // expand the corresponding attribute buffer (creating it if needed) so that it matches POSITIONS size and specified element type
+    gpu::BufferView _expandedAttributeBuffer(const graphics::MeshPointer mesh, gpu::Stream::Slot slot) {
+        gpu::BufferView bufferView = buffer_helpers::getBufferView(mesh, slot);        
+        const auto& elementType = bufferView._element;
+        //auto vecN = element.getScalarCount();
+        //auto type = element.getType();
+        //gpu::Element elementType = getVecNElement(type, vecN);
+        
+        gpu::Size elementSize = elementType.getSize();
+        auto nPositions = mesh->getNumVertices();
+        auto vsize = nPositions * elementSize;
+        auto diffTypes = (elementType.getType() != bufferView._element.getType()  ||
+                          elementType.getSize() > bufferView._element.getSize() ||
+                          elementType.getScalarCount() > bufferView._element.getScalarCount() ||
+                          vsize > bufferView._size
+            );
+        QString hint = QString("%1").arg(slot);
+#ifdef DEBUG_BUFFERVIEW_SCRIPTING
+        hint = DebugNames::stringFrom(slot);
+#endif
+#ifdef DEV_BUILD
+        auto beforeCount = bufferView.getNumElements();
+        auto beforeTotal = bufferView._size;
+#endif
+        if (bufferView.getNumElements() < nPositions || diffTypes) {
+            if (!bufferView._buffer || bufferView.getNumElements() == 0) {
+                qCInfo(bufferhelper_logging).nospace() << "ScriptableMesh -- adding missing mesh attribute '" << hint << "' for BufferView";
+                gpu::Byte *data = new gpu::Byte[vsize];
+                memset(data, 0, vsize);
+                auto buffer = new gpu::Buffer(vsize, (gpu::Byte*)data);
+                delete[] data;
+                bufferView = gpu::BufferView(buffer, elementType);
+                mesh->addAttribute(slot, bufferView);
+            } else {
+                qCInfo(bufferhelper_logging) << "ScriptableMesh -- resizing Buffer current:" << hint << bufferView._buffer->getSize() << "wanted:" << vsize;
+                bufferView._element = elementType;
+                bufferView._buffer->resize(vsize);
+                bufferView._size = bufferView._buffer->getSize();
+            }
+        }
+#ifdef DEV_BUILD
+        auto afterCount = bufferView.getNumElements();
+        auto afterTotal = bufferView._size;
+        if (beforeTotal != afterTotal || beforeCount != afterCount) {
+            QString typeName = QString("%1").arg(bufferView._element.getType());
+#ifdef DEBUG_BUFFERVIEW_SCRIPTING
+            typeName = DebugNames::stringFrom(bufferView._element.getType());
+#endif
+            qCDebug(bufferhelper_logging, "NOTE:: _expandedAttributeBuffer.%s vec%d %s (before count=%lu bytes=%lu // after count=%lu bytes=%lu)",
+                          hint.toStdString().c_str(), bufferView._element.getScalarCount(),
+                          typeName.toStdString().c_str(), beforeCount, beforeTotal, afterCount, afterTotal);
+        }
+#endif
+        return bufferView;
+    }
+
+    gpu::BufferView expandAttributeToMatchPositions(graphics::MeshPointer mesh, gpu::Stream::Slot slot) {
+        if (slot == gpu::Stream::POSITION) {
+            return buffer_helpers::getBufferView(mesh, slot);
+        }
+        return _expandedAttributeBuffer(mesh, slot);
+    }
+}
+
+std::map<QString, gpu::BufferView> buffer_helpers::gatherBufferViews(graphics::MeshPointer mesh, const QStringList& expandToMatchPositions) {
+    std::map<QString, gpu::BufferView> attributeViews;
+    if (!mesh) {
+        return attributeViews;
+    }
+    for (const auto& a : buffer_helpers::ATTRIBUTES.toStdMap()) {
+        auto name = a.first;
+        auto slot = a.second;
+        auto view = getBufferView(mesh, slot);
+        auto beforeCount = view.getNumElements();
+        auto beforeTotal = view._size;
+        if (expandToMatchPositions.contains(name)) {
+            expandAttributeToMatchPositions(mesh, slot);
+        }
+        if (beforeCount > 0) {
+            auto element = view._element;
+            auto vecN = element.getScalarCount();
+            //auto type = element.getType();
+            QString typeName = QString("%1").arg(element.getType());
+#ifdef DEBUG_BUFFERVIEW_SCRIPTING
+            typeName = DebugNames::stringFrom(element.getType());
+#endif
+
+            attributeViews[name] = getBufferView(mesh, slot);
+
+#if DEV_BUILD
+            auto afterTotal = attributeViews[name]._size;
+            auto afterCount = attributeViews[name].getNumElements();
+            if (beforeTotal != afterTotal || beforeCount != afterCount) {
+                qCDebug(bufferhelper_logging, "NOTE:: gatherBufferViews.%s vec%d %s (before count=%lu bytes=%lu // after count=%lu bytes=%lu)",
+                              name.toStdString().c_str(), vecN, typeName.toStdString().c_str(), beforeCount, beforeTotal, afterCount, afterTotal);
+            }
+#endif
+        }
+    }
+    return attributeViews;
+}
+
+
+bool buffer_helpers::recalculateNormals(graphics::MeshPointer mesh) {    
+    qCInfo(bufferhelper_logging) << "Recalculating normals" << !!mesh;
+    if (!mesh) {
+        return false;
+    }
+    buffer_helpers::gatherBufferViews(mesh, { "normal", "color" }); // ensures #normals >= #positions
+    auto normals = mesh->getAttributeBuffer(gpu::Stream::NORMAL);
+    auto verts = mesh->getVertexBuffer();
+    auto indices = mesh->getIndexBuffer();
+    auto esize = indices._element.getSize();
+    auto numPoints = indices.getNumElements();
+    const auto TRIANGLE = 3;
+    quint32 numFaces = (quint32)numPoints / TRIANGLE;
+    //QVector<Triangle> faces;
+    QVector<glm::vec3> faceNormals;
+    QMap<QString,QVector<quint32>> vertexToFaces;
+    //faces.resize(numFaces);
+    faceNormals.resize(numFaces);
+    auto numNormals = normals.getNumElements();
+    qCInfo(bufferhelper_logging) << QString("numFaces: %1, numNormals: %2, numPoints: %3").arg(numFaces).arg(numNormals).arg(numPoints);
+    if (normals.getNumElements() != verts.getNumElements()) {
+        return false;
+    }
+    for (quint32 i = 0; i < numFaces; i++) {
+        quint32 I = TRIANGLE * i;
+        quint32 i0 = esize == 4 ? indices.get<quint32>(I+0) : indices.get<quint16>(I+0);
+        quint32 i1 = esize == 4 ? indices.get<quint32>(I+1) : indices.get<quint16>(I+1);
+        quint32 i2 = esize == 4 ? indices.get<quint32>(I+2) : indices.get<quint16>(I+2);
+
+        Triangle face = {
+            verts.get<glm::vec3>(i1),
+            verts.get<glm::vec3>(i2),
+            verts.get<glm::vec3>(i0)
+        };
+        faceNormals[i] = face.getNormal();
+        if (glm::isnan(faceNormals[i].x)) {
+            qCInfo(bufferhelper_logging) << i << i0 << i1 << i2 << glmVecToVariant(face.v0) << glmVecToVariant(face.v1) << glmVecToVariant(face.v2);
+            break;
+        }
+        vertexToFaces[glm::to_string(face.v0).c_str()] << i;
+        vertexToFaces[glm::to_string(face.v1).c_str()] << i;
+        vertexToFaces[glm::to_string(face.v2).c_str()] << i;
+    }
+    for (quint32 j = 0; j < numNormals; j++) {
+        //auto v = verts.get<glm::vec3>(j);
+        glm::vec3 normal { 0.0f, 0.0f, 0.0f };
+        QString key { glm::to_string(verts.get<glm::vec3>(j)).c_str() };
+        const auto& faces = vertexToFaces.value(key);
+        if (faces.size()) {
+            for (const auto i : faces) {
+                normal += faceNormals[i];
+            }
+            normal *= 1.0f / (float)faces.size();
+        } else {
+            static int logged = 0;
+            if (logged++ < 10) {
+                qCInfo(bufferhelper_logging) << "no faces for key!?" << key;
+            }
+            normal = verts.get<glm::vec3>(j);
+        }
+        if (glm::isnan(normal.x)) {
+            static int logged = 0;
+            if (logged++ < 10) {
+                qCInfo(bufferhelper_logging) << "isnan(normal.x)" << j << glmVecToVariant(normal);
+            }
+            break;
+        }
+        normals.edit<glm::vec3>(j) = glm::normalize(normal);
+    }
+    return true;
+}
+
+QVariant buffer_helpers::toVariant(const glm::mat4& mat4) {
+    QVector<float> floats;
+    floats.resize(16);
+    memcpy(floats.data(), &mat4, sizeof(glm::mat4));
+    QVariant v;
+    v.setValue<QVector<float>>(floats);
+    return v;
+};
+
+QVariant buffer_helpers::toVariant(const Extents& box) {
+    return QVariantMap{
+        { "center", glmVecToVariant(box.minimum + (box.size() / 2.0f)) },
+        { "minimum", glmVecToVariant(box.minimum) },
+        { "maximum", glmVecToVariant(box.maximum) },
+        { "dimensions", glmVecToVariant(box.size()) },
+    };
+}
+
+QVariant buffer_helpers::toVariant(const AABox& box) {
+    return QVariantMap{
+        { "brn", glmVecToVariant(box.getCorner()) },
+        { "tfl", glmVecToVariant(box.calcTopFarLeft()) },
+        { "center", glmVecToVariant(box.calcCenter()) },
+        { "minimum", glmVecToVariant(box.getMinimumPoint()) },
+        { "maximum", glmVecToVariant(box.getMaximumPoint()) },
+        { "dimensions", glmVecToVariant(box.getDimensions()) },
+    };
+}
diff --git a/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h b/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h
index d0d42ca419..d963fd4b22 100644
--- a/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h
+++ b/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h
@@ -7,6 +7,8 @@
 #pragma once
 
 #include <QtCore>
+#include <memory>
+#include <glm/glm.hpp>
 
 namespace gpu {
     class BufferView;
@@ -15,11 +17,34 @@ namespace gpu {
 
 template <typename T> QVariant glmVecToVariant(const T& v, bool asArray = false);
 template <typename T> const T glmVecFromVariant(const QVariant& v);
-QVariant bufferViewElementToVariant(const gpu::BufferView& view, quint32 index, bool asArray = false, const char* hint = "");
-bool bufferViewElementFromVariant(const gpu::BufferView& view, quint32 index, const QVariant& v);
 
-template <typename T> gpu::BufferView bufferViewFromVector(QVector<T> elements, gpu::Element elementType);
+namespace graphics {
+    class Mesh;
+    using MeshPointer = std::shared_ptr<Mesh>;
+}
 
-gpu::BufferView cloneBufferView(const gpu::BufferView& input);
-gpu::BufferView resizedBufferView(const gpu::BufferView& input, quint32 numElements);
+class Extents;
+class AABox;
 
+struct buffer_helpers {
+    static graphics::MeshPointer cloneMesh(graphics::MeshPointer mesh);
+    static QMap<QString,int> ATTRIBUTES;
+    static std::map<QString, gpu::BufferView> gatherBufferViews(graphics::MeshPointer mesh, const QStringList& expandToMatchPositions = QStringList());
+    static bool recalculateNormals(graphics::MeshPointer meshProxy);
+    static gpu::BufferView getBufferView(graphics::MeshPointer mesh, quint8 slot);
+
+    static QVariant toVariant(const Extents& box);
+    static QVariant toVariant(const AABox& box);
+    static QVariant toVariant(const glm::mat4& mat4);
+    static QVariant toVariant(const gpu::BufferView& view, quint32 index, bool asArray = false, const char* hint = "");
+
+    static bool fromVariant(const gpu::BufferView& view, quint32 index, const QVariant& v);
+
+    template <typename T> static gpu::BufferView fromVector(const QVector<T>& elements, const gpu::Element& elementType);
+
+    template <typename T> static QVector<T> toVector(const gpu::BufferView& view, const char *hint = "");    
+    template <typename T> static T convert(const gpu::BufferView& view, quint32 index, const char* hint = "");
+    
+    static gpu::BufferView clone(const gpu::BufferView& input);
+    static gpu::BufferView resize(const gpu::BufferView& input, quint32 numElements);    
+};
diff --git a/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp b/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp
index 367c0589e9..ab6f2c92be 100644
--- a/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp
+++ b/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp
@@ -26,7 +26,7 @@ QScriptValue getBufferViewElement(QScriptEngine* js, const gpu::BufferView& view
 }
 
 QScriptValue bufferViewElementToScriptValue(QScriptEngine* engine, const gpu::BufferView& view, quint32 index, bool asArray, const char* hint) {
-    QVariant result = bufferViewElementToVariant(view, index, asArray, hint);
+    QVariant result = buffer_helpers::toVariant(view, index, asArray, hint);
     if (!result.isValid()) {
         return QScriptValue::NullValue;
     }
@@ -39,7 +39,7 @@ void setBufferViewElement(const gpu::BufferView& view, quint32 index, const QScr
 }
 
 bool bufferViewElementFromScriptValue(const QScriptValue& v, const gpu::BufferView& view, quint32 index) {
-    return bufferViewElementFromVariant(view, index, v.toVariant());
+    return buffer_helpers::fromVariant(view, index, v.toVariant());
 }
 
 //
diff --git a/libraries/graphics-scripting/src/graphics-scripting/Forward.h b/libraries/graphics-scripting/src/graphics-scripting/Forward.h
new file mode 100644
index 0000000000..15973b5852
--- /dev/null
+++ b/libraries/graphics-scripting/src/graphics-scripting/Forward.h
@@ -0,0 +1,107 @@
+#pragma once
+
+#include <QtCore/QObject>
+#include <QtCore/QVector>
+#include <QtCore/QVariant>
+#include <QtCore/QUuid>
+#include <QPointer>
+#include <memory>
+
+#include <DependencyManager.h>
+
+namespace graphics {
+    class Mesh;
+}
+namespace gpu {
+    class BufferView;
+}
+class QScriptEngine;
+
+namespace scriptable {
+    using Mesh = graphics::Mesh;
+    using MeshPointer = std::shared_ptr<scriptable::Mesh>;
+    using WeakMeshPointer = std::weak_ptr<scriptable::Mesh>;
+
+    class ScriptableModelBase;
+    using ScriptableModelBasePointer = QPointer<ScriptableModelBase>;
+
+    class ModelProvider;
+    using ModelProviderPointer = std::shared_ptr<scriptable::ModelProvider>;
+    using WeakModelProviderPointer = std::weak_ptr<scriptable::ModelProvider>;
+
+    class ScriptableMeshBase : public QObject {
+        Q_OBJECT
+    public:
+        WeakModelProviderPointer provider;
+        ScriptableModelBasePointer model;
+        WeakMeshPointer mesh;
+        MeshPointer ownedMesh;
+        QVariantMap metadata;
+        ScriptableMeshBase(WeakModelProviderPointer provider, ScriptableModelBasePointer model, WeakMeshPointer mesh, const QVariantMap& metadata);
+        ScriptableMeshBase(WeakMeshPointer mesh = WeakMeshPointer());
+        ScriptableMeshBase(MeshPointer mesh, const QVariantMap& metadata);
+        ScriptableMeshBase(const ScriptableMeshBase& other) { *this = other; }
+        ScriptableMeshBase& operator=(const ScriptableMeshBase& view);
+        virtual ~ScriptableMeshBase();
+        Q_INVOKABLE const scriptable::MeshPointer getMeshPointer() const { return mesh.lock(); }
+        Q_INVOKABLE const scriptable::ModelProviderPointer getModelProviderPointer() const { return provider.lock(); }
+        Q_INVOKABLE const scriptable::ScriptableModelBasePointer getModelBasePointer() const { return model; }
+    };
+    
+    // abstract container for holding one or more references to mesh pointers
+    class ScriptableModelBase : public QObject {
+        Q_OBJECT
+    public:
+        WeakModelProviderPointer provider;
+        QUuid objectID; // spatially nestable ID
+        QVariantMap metadata;
+        QVector<scriptable::ScriptableMeshBase> meshes;
+
+        ScriptableModelBase(QObject* parent = nullptr) : QObject(parent) {}
+        ScriptableModelBase(const ScriptableModelBase& other) { *this = other; }
+        ScriptableModelBase& operator=(const ScriptableModelBase& other) {
+            provider = other.provider;
+            objectID = other.objectID;
+            metadata = other.metadata;
+            for (auto& mesh : other.meshes) {
+                append(mesh);
+            }
+            return *this;
+        }
+        virtual ~ScriptableModelBase();
+
+        void mixin(const QVariantMap& other);
+        void append(const ScriptableModelBase& other, const QVariantMap& modelMetadata = QVariantMap());
+        void append(scriptable::WeakMeshPointer mesh, const QVariantMap& metadata = QVariantMap());
+        void append(const ScriptableMeshBase& mesh, const QVariantMap& metadata = QVariantMap());
+        // TODO: in future containers for these could go here
+        // QVariantMap shapes;
+        // QVariantMap materials;
+        // QVariantMap armature;
+    };
+
+    // mixin class for Avatar/Entity/Overlay Rendering that expose their in-memory graphics::Meshes
+    class ModelProvider {
+    public:
+        QVariantMap metadata{ { "providerType", "unknown" } };
+        static scriptable::ScriptableModelBase modelUnavailableError(bool* ok) { if (ok) { *ok = false; } return {}; }
+        virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) = 0;
+
+        virtual bool replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointer model, int meshIndex, int partIndex) { return false; }
+    };
+
+    // mixin class for resolving UUIDs into a corresponding ModelProvider
+    class ModelProviderFactory : public Dependency {
+    public:
+        virtual scriptable::ModelProviderPointer lookupModelProvider(QUuid uuid) = 0;
+    };
+
+    using uint32 = quint32;
+    class ScriptableModel;
+    using ScriptableModelPointer = QPointer<ScriptableModel>;
+    class ScriptableMesh;
+    using ScriptableMeshPointer = QPointer<ScriptableMesh>;
+    class ScriptableMeshPart;
+    using ScriptableMeshPartPointer = QPointer<ScriptableMeshPart>;
+    bool registerMetaTypes(QScriptEngine* engine);
+}
diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.cpp b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.cpp
new file mode 100644
index 0000000000..aabf83ff66
--- /dev/null
+++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.cpp
@@ -0,0 +1,3 @@
+#include "GraphicsScriptingUtil.h"
+
+Q_LOGGING_CATEGORY(graphics_scripting, "hifi.scripting.graphics")
diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.h b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.h
new file mode 100644
index 0000000000..a536fc413c
--- /dev/null
+++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.h
@@ -0,0 +1,85 @@
+#pragma once
+
+#include <QtScript/QScriptEngine>
+#include <QtScript/QScriptValue>
+#include <QtCore/QPointer>
+#include <QtCore/QObject>
+#include <QtCore/QLoggingCategory>
+#include <QDebug>
+#include <memory>
+#include <functional>
+Q_DECLARE_LOGGING_CATEGORY(graphics_scripting)
+
+namespace scriptable {
+    // derive current context's C++ QObject (based on current JS "this" value)
+    template <typename T> T this_qobject_cast(QScriptEngine* engine) {
+        auto context = engine ? engine->currentContext() : nullptr;
+        return qscriptvalue_cast<T>(context ? context->thisObject() : QScriptValue::NullValue);
+    }
+    // JS => QPointer<QObject>
+    template <typename T> QPointer<T> qpointer_qobject_cast(const QScriptValue& value) {
+        auto obj = value.toQObject();
+        qCInfo(graphics_scripting) << "qpointer_qobject_cast" << obj << value.toString();
+        if (auto tmp = qobject_cast<T*>(obj)) {
+            return QPointer<T>(tmp);
+        }
+        if (auto tmp = static_cast<T*>(obj)) {
+            return QPointer<T>(tmp);
+        }
+        return nullptr;
+    }
+    inline QString toDebugString(QObject* tmp) {
+        return QString("%0 (0x%1%2)")
+            .arg(tmp ? tmp->metaObject()->className() : "QObject")
+            .arg(qulonglong(tmp), 16, 16, QChar('0'))
+            .arg(tmp && tmp->objectName().size() ? " name=" + tmp->objectName() : "");
+    }
+    template <typename T> QString toDebugString(std::shared_ptr<T> tmp) {
+        return toDebugString(qobject_cast<QObject*>(tmp.get()));
+    }
+
+    // C++ > QtOwned instance
+    template <typename T, class... Rest> std::shared_ptr<T> make_qtowned(Rest... rest) {
+        T* tmp = new T(rest...);
+        qCInfo(graphics_scripting) << "scriptable::make_qtowned" << toDebugString(tmp);
+        QString debug = toDebugString(tmp);
+        if (tmp) {
+            tmp->metadata["__ownership__"] = QScriptEngine::QtOwnership;
+            QObject::connect(tmp, &QObject::destroyed, [=]() {  qCInfo(graphics_scripting) << "-------- ~scriptable::make_qtowned" << debug; });
+            auto ptr = std::shared_ptr<T>(tmp, [debug](T* tmp) {
+                    //qDebug() << "~std::shared_ptr<T>" << debug;
+                delete tmp;
+            });
+            return ptr;
+        } else {
+            return std::shared_ptr<T>(tmp);
+        }
+    }
+    // C++ > ScriptOwned JS instance
+    template <typename T, class... Rest> QPointer<T> make_scriptowned(Rest... rest) {
+        T* tmp = new T(rest...);
+        qCInfo(graphics_scripting) << "scriptable::make_scriptowned" << toDebugString(tmp);
+        if (tmp) {
+            tmp->metadata["__ownership__"] = QScriptEngine::ScriptOwnership;
+            //auto blah = (DeleterFunction)[](void* delme) { };
+            return add_scriptowned_destructor(tmp);
+        } else {
+            return QPointer<T>(tmp);
+        }
+    }
+    // C++ > ScriptOwned JS instance
+    template <typename T> QPointer<T> add_scriptowned_destructor(T* tmp) {
+        QString debug = toDebugString(tmp);
+        if (tmp) {
+            QObject::connect(tmp, &QObject::destroyed, [=]() {
+                    qCInfo(graphics_scripting) << "-------- ~scriptable::make_scriptowned" << debug;// << !!customDeleter;
+                //if (customDeleter) {
+                    // customDeleter(tmp);
+                //}
+            });
+        } else {
+            qCInfo(graphics_scripting) << "add_scriptowned_destructor -- not connecting to null value" << debug;
+        }
+        return QPointer<T>(tmp);
+    }
+}
diff --git a/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp b/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp
index ab85fb8265..ab9403a8ed 100644
--- a/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp
+++ b/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp
@@ -26,12 +26,11 @@
 
 #include "BufferViewScripting.h"
 #include "ScriptableMesh.h"
+#include "GraphicsScriptingUtil.h"
 
 #include "ModelScriptingInterface.moc"
 
-namespace {
-    QLoggingCategory model_scripting { "hifi.model.scripting" };
-}
+#include "RegisteredMetaTypes.h"
 
 ModelScriptingInterface::ModelScriptingInterface(QObject* parent) : QObject(parent) {
     if (auto scriptEngine = qobject_cast<QScriptEngine*>(parent)) {
@@ -39,8 +38,51 @@ ModelScriptingInterface::ModelScriptingInterface(QObject* parent) : QObject(pare
     }
 }
 
-void ModelScriptingInterface::getMeshes(QUuid uuid, QScriptValue scopeOrCallback, QScriptValue methodOrName) {
-    auto handler = makeScopedHandlerObject(scopeOrCallback, methodOrName);
+bool ModelScriptingInterface::updateMeshes(QUuid uuid, const scriptable::ScriptableMeshPointer mesh, int meshIndex, int partIndex) {
+    auto model = scriptable::make_qtowned<scriptable::ScriptableModel>();
+    if (mesh) {
+        model->append(*mesh);
+    }
+    return updateMeshes(uuid, model.get());
+}
+
+bool ModelScriptingInterface::updateMeshes(QUuid uuid, const scriptable::ScriptableModelPointer model) {
+    auto appProvider = DependencyManager::get<scriptable::ModelProviderFactory>();
+    qCDebug(graphics_scripting) << "appProvider" << appProvider.data();
+    scriptable::ModelProviderPointer provider = appProvider ? appProvider->lookupModelProvider(uuid) : nullptr;
+    QString providerType = provider ? provider->metadata.value("providerType").toString() : QString();
+    if (providerType.isEmpty()) {
+        providerType = "unknown";
+    }
+    bool success = false;
+    if (provider) {
+        qCDebug(graphics_scripting) << "fetching meshes from " << providerType << "...";
+        auto scriptableMeshes = provider->getScriptableModel(&success);
+        qCDebug(graphics_scripting) << "//fetched meshes from " << providerType << "success:" <<success << "#" << scriptableMeshes.meshes.size();
+        if (success) {
+            const scriptable::ScriptableModelBasePointer base = model->operator scriptable::ScriptableModelBasePointer();
+            qCDebug(graphics_scripting) << "as base" << base;
+            if (base) {
+                //auto meshes = model->getConstMeshes();
+                success = provider->replaceScriptableModelMeshPart(base, -1, -1);
+                
+                // for (uint32_t m = 0; success && m < meshes.size(); m++) {
+                //     const auto& mesh = meshes.at(m);
+                //     for (int p = 0; success && p < mesh->getNumParts(); p++) {
+                //         qCDebug(graphics_scripting) << "provider->replaceScriptableModelMeshPart" << "meshIndex" << m << "partIndex" << p;
+                //         success = provider->replaceScriptableModelMeshPart(base, m, p);
+                //         //if (!success) {
+                //         qCDebug(graphics_scripting) << "//provider->replaceScriptableModelMeshPart" << "meshIndex" << m << "partIndex" << p << success;
+                //     }
+                // }
+            }
+        }
+    }
+    return success;
+}
+
+void ModelScriptingInterface::getMeshes(QUuid uuid, QScriptValue callback) {
+    auto handler = scriptable::jsBindCallback(callback);
     Q_ASSERT(handler.engine() == this->engine());
     QPointer<BaseScriptEngine> engine = dynamic_cast<BaseScriptEngine*>(handler.engine());
 
@@ -49,18 +91,23 @@ void ModelScriptingInterface::getMeshes(QUuid uuid, QScriptValue scopeOrCallback
     QString error;
 
     auto appProvider = DependencyManager::get<scriptable::ModelProviderFactory>();
-    qDebug() << "appProvider" << appProvider.data();
+    qCDebug(graphics_scripting) << "appProvider" << appProvider.data();
     scriptable::ModelProviderPointer provider = appProvider ? appProvider->lookupModelProvider(uuid) : nullptr;
     QString providerType = provider ? provider->metadata.value("providerType").toString() : QString();
     if (providerType.isEmpty()) {
         providerType = "unknown";
     }
     if (provider) {
-        qCDebug(model_scripting) << "fetching meshes from " << providerType << "...";
+        qCDebug(graphics_scripting) << "fetching meshes from " << providerType << "...";
         auto scriptableMeshes = provider->getScriptableModel(&success);
-        qCDebug(model_scripting) << "//fetched meshes from " << providerType << "success:" <<success << "#" << scriptableMeshes.meshes.size();
+        qCDebug(graphics_scripting) << "//fetched meshes from " << providerType << "success:" <<success << "#" << scriptableMeshes.meshes.size();
         if (success) {
-            meshes = new scriptable::ScriptableModel(scriptableMeshes);//SimpleModelProxy::fromScriptableModel(scriptableMeshes);
+            meshes = scriptable::make_scriptowned<scriptable::ScriptableModel>(scriptableMeshes);
+        QString debugString = scriptable::toDebugString(meshes);
+        QObject::connect(meshes, &QObject::destroyed, this, [=]() {
+                qCDebug(graphics_scripting) << "///fetched meshes" << debugString;
+            });
+        
             if (meshes->objectName().isEmpty()) {
                 meshes->setObjectName(providerType+"::meshes");
             }
@@ -75,20 +122,20 @@ void ModelScriptingInterface::getMeshes(QUuid uuid, QScriptValue scopeOrCallback
     }
 
     if (!error.isEmpty()) {
-        qCWarning(model_scripting) << "ModelScriptingInterface::getMeshes ERROR" << error;
+        qCWarning(graphics_scripting) << "ModelScriptingInterface::getMeshes ERROR" << error;
         callScopedHandlerObject(handler, engine->makeError(error), QScriptValue::NullValue);
     } else {
-        callScopedHandlerObject(handler, QScriptValue::NullValue, engine->newQObject(meshes, QScriptEngine::ScriptOwnership));
+        callScopedHandlerObject(handler, QScriptValue::NullValue, engine->toScriptValue(meshes));
     }
 }
 
 QString ModelScriptingInterface::meshToOBJ(const scriptable::ScriptableModel& _in) {
     const auto& in = _in.getConstMeshes();
-    qCDebug(model_scripting) << "meshToOBJ" << in.size();
+    qCDebug(graphics_scripting) << "meshToOBJ" << in.size();
     if (in.size()) {
         QList<scriptable::MeshPointer> meshes;
         foreach (auto meshProxy, in) {
-            qCDebug(model_scripting) << "meshToOBJ" << meshProxy;
+            qCDebug(graphics_scripting) << "meshToOBJ" << meshProxy;
             if (meshProxy) {
                 meshes.append(getMeshPointer(meshProxy));
             }
@@ -207,7 +254,7 @@ QScriptValue ModelScriptingInterface::appendMeshes(scriptable::ScriptableModel _
                                                           (gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL));
 
 
-    return engine()->toScriptValue(scriptable::ScriptableMeshPointer(new scriptable::ScriptableMesh(nullptr, result)));
+    return engine()->toScriptValue(scriptable::make_scriptowned<scriptable::ScriptableMesh>(result));
 }
 
 QScriptValue ModelScriptingInterface::transformMesh(scriptable::ScriptableMeshPointer meshProxy, glm::mat4 transform) {
@@ -220,8 +267,7 @@ QScriptValue ModelScriptingInterface::transformMesh(scriptable::ScriptableMeshPo
                                           [&](glm::vec3 color){ return color; },
                                           [&](glm::vec3 normal){ return glm::vec3(transform * glm::vec4(normal, 0.0f)); },
                                           [&](uint32_t index){ return index; });
-    scriptable::ScriptableMeshPointer resultProxy = scriptable::ScriptableMeshPointer(new scriptable::ScriptableMesh(nullptr, result));
-    return engine()->toScriptValue(resultProxy);
+    return engine()->toScriptValue(scriptable::make_scriptowned<scriptable::ScriptableMesh>(result));
 }
 
 QScriptValue ModelScriptingInterface::getVertexCount(scriptable::ScriptableMeshPointer meshProxy) {
@@ -270,7 +316,7 @@ QScriptValue ModelScriptingInterface::newMesh(const QVector<glm::vec3>& vertices
                                          sizeof(glm::vec3), gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
         mesh->addAttribute(gpu::Stream::NORMAL, normalBufferView);
     } else {
-        qCWarning(model_scripting, "ModelScriptingInterface::newMesh normals must be same length as vertices");
+        qCWarning(graphics_scripting, "ModelScriptingInterface::newMesh normals must be same length as vertices");
     }
 
     // indices (faces)
@@ -300,54 +346,10 @@ QScriptValue ModelScriptingInterface::newMesh(const QVector<glm::vec3>& vertices
 
 
 
-    scriptable::ScriptableMeshPointer meshProxy = scriptable::ScriptableMeshPointer(new scriptable::ScriptableMesh(nullptr, mesh));
-    return engine()->toScriptValue(meshProxy);
+    return engine()->toScriptValue(scriptable::make_scriptowned<scriptable::ScriptableMesh>(mesh));
 }
 
 namespace {
-    QScriptValue meshPointerToScriptValue(QScriptEngine* engine, scriptable::ScriptableMeshPointer const &in) {
-        if (!in) {
-            return QScriptValue::NullValue;
-        }
-        return engine->newQObject(in, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects);
-    }
-
-    void meshPointerFromScriptValue(const QScriptValue& value, scriptable::ScriptableMeshPointer &out) {
-        auto obj = value.toQObject();
-        qDebug() << "meshPointerFromScriptValue" << obj;
-        if (auto tmp = qobject_cast<scriptable::ScriptableMesh*>(obj)) {
-            out = tmp;
-        }
-        // FIXME: Why does above cast not work on Win32!?
-        if (!out) {
-            if (auto smp = static_cast<scriptable::ScriptableMesh*>(obj)) {
-                qDebug() << "meshPointerFromScriptValue2" << smp;
-                out = smp;
-            }
-        }
-    }
-
-    QScriptValue modelPointerToScriptValue(QScriptEngine* engine, const scriptable::ScriptableModelPointer &in) {
-        return engine->newQObject(in, QScriptEngine::QtOwnership,  QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects);
-        // QScriptValue result = engine->newArray();
-        // int i = 0;
-        // foreach(auto& mesh, in->getMeshes()) {
-        //     result.setProperty(i++, meshPointerToScriptValue(engine, mesh));
-        // }
-        // return result;
-    }
-
-    void modelPointerFromScriptValue(const QScriptValue& value, scriptable::ScriptableModelPointer &out) {
-        const auto length = value.property("length").toInt32();
-        qCDebug(model_scripting) << "in modelPointerFromScriptValue, length =" << length;
-        for (int i = 0; i < length; i++) {
-            if (const auto meshProxy = qobject_cast<scriptable::ScriptableMesh*>(value.property(i).toQObject())) {
-                out->meshes.append(meshProxy->getMeshPointer());
-            } else {
-                qCDebug(model_scripting) << "null meshProxy" << i;
-            }
-        }
-    }
 
     // FIXME: MESHFACES:
     // QScriptValue meshFaceToScriptValue(QScriptEngine* engine, const mesh::MeshFace &meshFace) {
@@ -365,29 +367,11 @@ namespace {
     //     qScriptValueToSequence(array, result);
     // }
 
-    QScriptValue qVectorUInt32ToScriptValue(QScriptEngine* engine, const QVector<quint32>& vector) {
-        return qScriptValueFromSequence(engine, vector);
-    }
-
-    void qVectorUInt32FromScriptValue(const QScriptValue& array, QVector<quint32>& result) {
-        qScriptValueToSequence(array, result);
-    }
 }
 
-int meshUint32 = qRegisterMetaType<quint32>();
-namespace mesh {
-    int meshUint32 = qRegisterMetaType<uint32>();
-}
-int qVectorMeshUint32 = qRegisterMetaType<QVector<quint32>>();
 
 void ModelScriptingInterface::registerMetaTypes(QScriptEngine* engine) {
-    qScriptRegisterSequenceMetaType<QVector<scriptable::ScriptableMeshPointer>>(engine);
-    qScriptRegisterSequenceMetaType<QVector<quint32>>(engine);
-
-    qScriptRegisterMetaType(engine, qVectorUInt32ToScriptValue, qVectorUInt32FromScriptValue);
-    qScriptRegisterMetaType(engine, meshPointerToScriptValue, meshPointerFromScriptValue);
-    qScriptRegisterMetaType(engine, modelPointerToScriptValue, modelPointerFromScriptValue);
-
+    scriptable::registerMetaTypes(engine);
     // FIXME: MESHFACES: remove if MeshFace is not needed anywhere
     // qScriptRegisterSequenceMetaType<mesh::MeshFaces>(engine);
     // qScriptRegisterMetaType(engine, meshFaceToScriptValue, meshFaceFromScriptValue);
@@ -395,7 +379,7 @@ void ModelScriptingInterface::registerMetaTypes(QScriptEngine* engine) {
 }
 
 MeshPointer ModelScriptingInterface::getMeshPointer(const scriptable::ScriptableMesh& meshProxy) {
-    return meshProxy._mesh;//getMeshPointer(&meshProxy);
+    return meshProxy.getMeshPointer();
 }
 MeshPointer ModelScriptingInterface::getMeshPointer(scriptable::ScriptableMesh& meshProxy) {
     return getMeshPointer(&meshProxy);
@@ -406,7 +390,7 @@ MeshPointer ModelScriptingInterface::getMeshPointer(scriptable::ScriptableMeshPo
         if (context()){
             context()->throwError("expected meshProxy as first parameter");
         } else {
-            qDebug() << "expected meshProxy as first parameter";
+            qCDebug(graphics_scripting) << "expected meshProxy as first parameter";
         }
         return result;
     }
@@ -415,7 +399,7 @@ MeshPointer ModelScriptingInterface::getMeshPointer(scriptable::ScriptableMeshPo
         if (context()) {
             context()->throwError("expected valid meshProxy as first parameter");
         } else {
-            qDebug() << "expected valid meshProxy as first parameter";
+            qCDebug(graphics_scripting) << "expected valid meshProxy as first parameter";
         }
         return result;
     }
diff --git a/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.h b/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.h
index eac4df3216..fa7b885014 100644
--- a/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.h
+++ b/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.h
@@ -15,19 +15,17 @@
 #include <QtCore/QObject>
 #include <QUrl>
 
-#include <RegisteredMetaTypes.h>
-
 #include <QtScript/QScriptEngine>
 #include <QtScript/QScriptable>
 
 #include "ScriptableMesh.h"
 #include <DependencyManager.h>
+
 class ModelScriptingInterface : public QObject, public QScriptable, public Dependency {
     Q_OBJECT
 
 public:
     ModelScriptingInterface(QObject* parent = nullptr);
-    static void registerMetaTypes(QScriptEngine* engine);
 
 public slots:
     /**jsdoc
@@ -36,7 +34,9 @@ public slots:
      * @function ModelScriptingInterface.getMeshes
      * @param {EntityID} entityID The ID of the entity whose meshes are to be retrieve
      */
-    void getMeshes(QUuid uuid, QScriptValue scopeOrCallback, QScriptValue methodOrName = QScriptValue());
+    void getMeshes(QUuid uuid, QScriptValue callback);
+    bool updateMeshes(QUuid uuid, const scriptable::ScriptableModelPointer model);
+    bool updateMeshes(QUuid uuid, const scriptable::ScriptableMeshPointer mesh, int meshIndex=0, int partIndex=0);
 
     QString meshToOBJ(const scriptable::ScriptableModel& in);
 
@@ -48,6 +48,8 @@ public slots:
     QScriptValue getVertexCount(scriptable::ScriptableMeshPointer meshProxy);
     QScriptValue getVertex(scriptable::ScriptableMeshPointer meshProxy, quint32 vertexIndex);
 
+    static void registerMetaTypes(QScriptEngine* engine);
+    
 private:
     scriptable::MeshPointer getMeshPointer(scriptable::ScriptableMeshPointer meshProxy);
     scriptable::MeshPointer getMeshPointer(scriptable::ScriptableMesh& meshProxy);
diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp
index 1b16a6d263..b83b901acd 100644
--- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp
+++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp
@@ -9,17 +9,16 @@
 //  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
 //
 
+#include "GraphicsScriptingUtil.h"
 #include "ScriptableMesh.h"
 
 #include <glm/glm.hpp>
 #include <glm/gtx/transform.hpp>
 #include <glm/gtx/norm.hpp>
-#include <glm/gtx/string_cast.hpp>
 #include <graphics/Geometry.h>
 #include <graphics-scripting/DebugNames.h>
 #include <graphics-scripting/BufferViewHelpers.h>
 #include <graphics-scripting/BufferViewScripting.h>
-#include <Extents.h>
 
 #include "ScriptableMesh.moc"
 
@@ -29,49 +28,45 @@
 
 #include "OBJWriter.h"
 
-QLoggingCategory mesh_logging { "hifi.scripting.mesh" };
-
-// FIXME: unroll/resolve before PR
-using namespace scriptable;
-QMap<QString,int> ScriptableMesh::ATTRIBUTES{
-    {"position", gpu::Stream::POSITION },
-    {"normal", gpu::Stream::NORMAL },
-    {"color", gpu::Stream::COLOR },
-    {"tangent", gpu::Stream::TEXCOORD0 },
-    {"skin_cluster_index", gpu::Stream::SKIN_CLUSTER_INDEX },
-    {"skin_cluster_weight", gpu::Stream::SKIN_CLUSTER_WEIGHT },
-    {"texcoord0", gpu::Stream::TEXCOORD0 },
-    {"texcoord1", gpu::Stream::TEXCOORD1 },
-    {"texcoord2", gpu::Stream::TEXCOORD2 },
-    {"texcoord3", gpu::Stream::TEXCOORD3 },
-    {"texcoord4", gpu::Stream::TEXCOORD4 },
-};
-
-
-QString scriptable::ScriptableModel::toString() const {
-    return QString("[ScriptableModel%1%2]")
-        .arg(objectID.isNull() ? "" : " objectID="+objectID.toString())
-        .arg(objectName().isEmpty() ? "" : " name=" +objectName());
+namespace scriptable {
+    // QScriptValue jsBindCallback(QScriptValue callback);
+    // template <typename T> QPointer<T> qpointer_qobject_cast(const QScriptValue& value);
+    // template <typename T> T this_qobject_cast(QScriptEngine* engine);
+    // template <typename T, class... Rest> QPointer<T> make_scriptowned(Rest... rest);
 }
 
-const QVector<scriptable::ScriptableMeshPointer> scriptable::ScriptableModel::getConstMeshes() const {
-    QVector<scriptable::ScriptableMeshPointer> out;
-    for(const auto& mesh : meshes) {
-        const scriptable::ScriptableMeshPointer m = scriptable::ScriptableMeshPointer(new scriptable::ScriptableMesh(const_cast<scriptable::ScriptableModel*>(this), mesh));
-        out << m;
+scriptable::ScriptableMeshPart::ScriptableMeshPart(scriptable::ScriptableMeshPointer parentMesh, int partIndex)
+    : parentMesh(parentMesh), partIndex(partIndex)  {
+    setObjectName(QString("%1.part[%2]").arg(parentMesh ? parentMesh->objectName() : "").arg(partIndex));
+}
+
+scriptable::ScriptableMesh::ScriptableMesh(const ScriptableMeshBase& other)
+    : ScriptableMeshBase(other) {
+    auto mesh = getMeshPointer();
+    QString name = mesh ? QString::fromStdString(mesh->modelName) : "";
+    if (name.isEmpty()) {
+        name = mesh ? QString::fromStdString(mesh->displayName) : "";
     }
-    return out;
+    auto parentModel = getParentModel();
+    setObjectName(QString("%1#%2").arg(parentModel ? parentModel->objectName() : "").arg(name));
 }
-QVector<scriptable::ScriptableMeshPointer> scriptable::ScriptableModel::getMeshes() {
-    QVector<scriptable::ScriptableMeshPointer> out;
-    for(auto& mesh : meshes) {
-        scriptable::ScriptableMeshPointer m{new scriptable::ScriptableMesh(this, mesh)};
-        out << m;
+
+QVector<scriptable::ScriptableMeshPartPointer> scriptable::ScriptableMesh::getMeshParts() const {
+    QVector<scriptable::ScriptableMeshPartPointer> out;
+    for (quint32 i = 0; i < getNumParts(); i++) {
+        out << scriptable::make_scriptowned<scriptable::ScriptableMeshPart>(getSelf(), i);
     }
     return out;
 }
 
-quint32 ScriptableMesh::getNumVertices() const {
+quint32 scriptable::ScriptableMesh::getNumIndices() const {
+    if (auto mesh = getMeshPointer()) {
+        return (quint32)mesh->getNumIndices();
+    }
+    return 0;
+}
+
+quint32 scriptable::ScriptableMesh::getNumVertices() const {
     if (auto mesh = getMeshPointer()) {
         return (quint32)mesh->getNumVertices();
     }
@@ -87,16 +82,10 @@ quint32 ScriptableMesh::getNumVertices() const {
 //     return glm::vec3(NAN);
 // }
 
-namespace {
-    gpu::BufferView getBufferView(scriptable::MeshPointer mesh, gpu::Stream::Slot slot) {
-        return slot == gpu::Stream::POSITION ? mesh->getVertexBuffer() : mesh->getAttributeBuffer(slot);
-    }
-}
-
-QVector<quint32> ScriptableMesh::findNearbyIndices(const glm::vec3& origin, float epsilon) const {
+QVector<quint32> scriptable::ScriptableMesh::findNearbyIndices(const glm::vec3& origin, float epsilon) const {
     QVector<quint32> result;
     if (auto mesh = getMeshPointer()) {
-        const auto& pos = getBufferView(mesh, gpu::Stream::POSITION);
+        const auto& pos = buffer_helpers::getBufferView(mesh, gpu::Stream::POSITION);
         const uint32_t num = (uint32_t)pos.getNumElements();
         for (uint32_t i = 0; i < num; i++) {
             const auto& position = pos.get<glm::vec3>(i);
@@ -108,40 +97,45 @@ QVector<quint32> ScriptableMesh::findNearbyIndices(const glm::vec3& origin, floa
     return result;
 }
 
-QVector<quint32> ScriptableMesh::getIndices() const {
+QVector<quint32> scriptable::ScriptableMesh::getIndices() const {
     QVector<quint32> result;
     if (auto mesh = getMeshPointer()) {
-        qCDebug(mesh_logging, "getTriangleIndices mesh %p", mesh.get());
+        qCDebug(graphics_scripting, "getTriangleIndices mesh %p", mesh.get());
         gpu::BufferView indexBufferView = mesh->getIndexBuffer();
         if (quint32 count = (quint32)indexBufferView.getNumElements()) {
             result.resize(count);
-            auto buffer = indexBufferView._buffer;
-            if (indexBufferView._element.getSize() == 4) {
+            switch(indexBufferView._element.getType()) {
+            case gpu::UINT32:
                 // memcpy(result.data(), buffer->getData(), result.size()*sizeof(quint32));
                 for (quint32 i = 0; i < count; i++) {
                     result[i] = indexBufferView.get<quint32>(i);
                 }
-            } else {
+                break;
+            case gpu::UINT16:
                 for (quint32 i = 0; i < count; i++) {
                     result[i] = indexBufferView.get<quint16>(i);
                 }
+                break;
+            default:
+                assert(false);
+                Q_ASSERT(false);
             }
         }
     }
     return result;
 }
 
-quint32 ScriptableMesh::getNumAttributes() const {
+quint32 scriptable::ScriptableMesh::getNumAttributes() const {
     if (auto mesh = getMeshPointer()) {
         return (quint32)mesh->getNumAttributes();
     }
     return 0;
 }
-QVector<QString> ScriptableMesh::getAttributeNames() const {
+QVector<QString> scriptable::ScriptableMesh::getAttributeNames() const {
     QVector<QString> result;
     if (auto mesh = getMeshPointer()) {
-        for (const auto& a : ATTRIBUTES.toStdMap()) {
-            auto bufferView = getBufferView(mesh, a.second);
+        for (const auto& a : buffer_helpers::ATTRIBUTES.toStdMap()) {
+            auto bufferView = buffer_helpers::getBufferView(mesh, a.second);
             if (bufferView.getNumElements() > 0) {
                 result << a.first;
             }
@@ -151,55 +145,49 @@ QVector<QString> ScriptableMesh::getAttributeNames() const {
 }
 
 // override
-QVariantMap ScriptableMesh::getVertexAttributes(quint32 vertexIndex) const {
+QVariantMap scriptable::ScriptableMesh::getVertexAttributes(quint32 vertexIndex) const {
     return getVertexAttributes(vertexIndex, getAttributeNames());
 }
 
-bool ScriptableMesh::setVertexAttributes(quint32 vertexIndex, QVariantMap attributes) {
-    //qDebug()  << "setVertexAttributes" << vertexIndex << attributes;
-    for (auto& a : gatherBufferViews(getMeshPointer())) {
+bool scriptable::ScriptableMesh::setVertexAttributes(quint32 vertexIndex, QVariantMap attributes) {
+    //qCInfo(graphics_scripting)  << "setVertexAttributes" << vertexIndex << attributes;
+    metadata["last-modified"] = QDateTime::currentDateTime().toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate);
+    for (auto& a : buffer_helpers::gatherBufferViews(getMeshPointer())) {
         const auto& name = a.first;
         const auto& value = attributes.value(name);
         if (value.isValid()) {
             auto& view = a.second;
-            //qCDebug(mesh_logging) << "setVertexAttributes" << vertexIndex << name;
-            bufferViewElementFromVariant(view, vertexIndex, value);
+            //qCDebug(graphics_scripting) << "setVertexAttributes" << vertexIndex << name;
+            buffer_helpers::fromVariant(view, vertexIndex, value);
         } else {
-            //qCDebug(mesh_logging) << "(skipping) setVertexAttributes" << vertexIndex << name;
+            //qCDebug(graphics_scripting) << "(skipping) setVertexAttributes" << vertexIndex << name;
         }
     }
     return true;
 }
 
-int ScriptableMesh::_getSlotNumber(const QString& attributeName) const {
+int scriptable::ScriptableMesh::_getSlotNumber(const QString& attributeName) const {
     if (auto mesh = getMeshPointer()) {
-        return ATTRIBUTES.value(attributeName, -1);
+        return buffer_helpers::ATTRIBUTES.value(attributeName, -1);
     }
     return -1;
 }
 
 
-QVariantMap ScriptableMesh::getMeshExtents() const {
+QVariantMap scriptable::ScriptableMesh::getMeshExtents() const {
     auto mesh = getMeshPointer();
     auto box = mesh ? mesh->evalPartsBound(0, (int)mesh->getNumParts()) : AABox();
-    return {
-        { "brn", glmVecToVariant(box.getCorner()) },
-        { "tfl", glmVecToVariant(box.calcTopFarLeft()) },
-        { "center", glmVecToVariant(box.calcCenter()) },
-        { "min", glmVecToVariant(box.getMinimumPoint()) },
-        { "max", glmVecToVariant(box.getMaximumPoint()) },
-        { "dimensions", glmVecToVariant(box.getDimensions()) },
-    };
+    return buffer_helpers::toVariant(box).toMap();
 }
 
-quint32 ScriptableMesh::getNumParts() const {
+quint32 scriptable::ScriptableMesh::getNumParts() const {
     if (auto mesh = getMeshPointer()) {
         return (quint32)mesh->getNumParts();
     }
     return 0;
 }
 
-QVariantMap ScriptableMesh::scaleToFit(float unitScale) {
+QVariantMap scriptable::ScriptableMeshPart::scaleToFit(float unitScale) {
     if (auto mesh = getMeshPointer()) {
         auto box = mesh->evalPartsBound(0, (int)mesh->getNumParts());
         auto center = box.calcCenter();
@@ -208,10 +196,10 @@ QVariantMap ScriptableMesh::scaleToFit(float unitScale) {
     }
     return {};
 }
-QVariantMap ScriptableMesh::translate(const glm::vec3& translation) {
+QVariantMap scriptable::ScriptableMeshPart::translate(const glm::vec3& translation) {
     return transform(glm::translate(translation));
 }
-QVariantMap ScriptableMesh::scale(const glm::vec3& scale, const glm::vec3& origin) {
+QVariantMap scriptable::ScriptableMeshPart::scale(const glm::vec3& scale, const glm::vec3& origin) {
     if (auto mesh = getMeshPointer()) {
         auto box = mesh->evalPartsBound(0, (int)mesh->getNumParts());
         glm::vec3 center = glm::isnan(origin.x) ? box.calcCenter() : origin;
@@ -219,10 +207,10 @@ QVariantMap ScriptableMesh::scale(const glm::vec3& scale, const glm::vec3& origi
     }
     return {};
 }
-QVariantMap ScriptableMesh::rotateDegrees(const glm::vec3& eulerAngles, const glm::vec3& origin) {
+QVariantMap scriptable::ScriptableMeshPart::rotateDegrees(const glm::vec3& eulerAngles, const glm::vec3& origin) {
     return rotate(glm::quat(glm::radians(eulerAngles)), origin);
 }
-QVariantMap ScriptableMesh::rotate(const glm::quat& rotation, const glm::vec3& origin) {
+QVariantMap scriptable::ScriptableMeshPart::rotate(const glm::quat& rotation, const glm::vec3& origin) {
     if (auto mesh = getMeshPointer()) {
         auto box = mesh->evalPartsBound(0, (int)mesh->getNumParts());
         glm::vec3 center = glm::isnan(origin.x) ? box.calcCenter() : origin;
@@ -230,184 +218,61 @@ QVariantMap ScriptableMesh::rotate(const glm::quat& rotation, const glm::vec3& o
     }
     return {};
 }
-QVariantMap ScriptableMesh::transform(const glm::mat4& transform) {
+QVariantMap scriptable::ScriptableMeshPart::transform(const glm::mat4& transform) {
     if (auto mesh = getMeshPointer()) {
-        const auto& pos = getBufferView(mesh, gpu::Stream::POSITION);
+        const auto& pos = buffer_helpers::getBufferView(mesh, gpu::Stream::POSITION);
         const uint32_t num = (uint32_t)pos.getNumElements();
         for (uint32_t i = 0; i < num; i++) {
             auto& position = pos.edit<glm::vec3>(i);
             position = transform * glm::vec4(position, 1.0f);
         }
+        return parentMesh->getMeshExtents();
     }
-    return getMeshExtents();
+    return {};
 }
 
-QVariantList ScriptableMesh::getAttributeValues(const QString& attributeName) const {
+QVariantList scriptable::ScriptableMesh::getAttributeValues(const QString& attributeName) const {
     QVariantList result;
     auto slotNum = _getSlotNumber(attributeName);
     if (slotNum >= 0) {
         auto slot = (gpu::Stream::Slot)slotNum;
-        const auto& bufferView = getBufferView(getMeshPointer(), slot);
+        const auto& bufferView = buffer_helpers::getBufferView(getMeshPointer(), slot);
         if (auto len = bufferView.getNumElements()) {
             bool asArray = bufferView._element.getType() != gpu::FLOAT;
             for (quint32 i = 0; i < len; i++) {
-                result << bufferViewElementToVariant(bufferView, i, asArray, attributeName.toStdString().c_str());
+                result << buffer_helpers::toVariant(bufferView, i, asArray, attributeName.toStdString().c_str());
             }
         }
     }
     return result;
 }
-QVariantMap ScriptableMesh::getVertexAttributes(quint32 vertexIndex, QVector<QString> names) const {
+QVariantMap scriptable::ScriptableMesh::getVertexAttributes(quint32 vertexIndex, QVector<QString> names) const {
     QVariantMap result;
     auto mesh = getMeshPointer();
     if (!mesh || vertexIndex >= getNumVertices()) {
         return result;
     }
-    for (const auto& a : ATTRIBUTES.toStdMap()) {
+    for (const auto& a : buffer_helpers::ATTRIBUTES.toStdMap()) {
         auto name = a.first;
         if (!names.contains(name)) {
             continue;
         }
         auto slot = a.second;
-        const gpu::BufferView& bufferView = getBufferView(mesh, slot);
+        const gpu::BufferView& bufferView = buffer_helpers::getBufferView(mesh, slot);
         if (vertexIndex < bufferView.getNumElements()) {
             bool asArray = bufferView._element.getType() != gpu::FLOAT;
-            result[name] = bufferViewElementToVariant(bufferView, vertexIndex, asArray, name.toStdString().c_str());
+            result[name] = buffer_helpers::toVariant(bufferView, vertexIndex, asArray, name.toStdString().c_str());
         }
     }
     return result;
 }
 
-/// --- buffer view <-> variant helpers
-
-namespace {
-    // expand the corresponding attribute buffer (creating it if needed) so that it matches POSITIONS size and specified element type
-    gpu::BufferView _expandedAttributeBuffer(const scriptable::MeshPointer mesh, gpu::Stream::Slot slot, const gpu::Element& elementType) {
-        gpu::Size elementSize = elementType.getSize();
-        gpu::BufferView bufferView = getBufferView(mesh, slot);
-        auto nPositions = mesh->getNumVertices();
-        auto vsize = nPositions * elementSize;
-        auto diffTypes = (elementType.getType() != bufferView._element.getType()  ||
-                          elementType.getSize() > bufferView._element.getSize() ||
-                          elementType.getScalarCount() > bufferView._element.getScalarCount() ||
-                          vsize > bufferView._size
-            );
-        auto hint = DebugNames::stringFrom(slot);
-
-#ifdef DEV_BUILD
-        auto beforeCount = bufferView.getNumElements();
-        auto beforeTotal = bufferView._size;
-#endif
-        if (bufferView.getNumElements() < nPositions || diffTypes) {
-            if (!bufferView._buffer || bufferView.getNumElements() == 0) {
-                qCInfo(mesh_logging).nospace() << "ScriptableMesh -- adding missing mesh attribute '" << hint << "' for BufferView";
-                gpu::Byte *data = new gpu::Byte[vsize];
-                memset(data, 0, vsize);
-                auto buffer = new gpu::Buffer(vsize, (gpu::Byte*)data);
-                delete[] data;
-                bufferView = gpu::BufferView(buffer, elementType);
-                mesh->addAttribute(slot, bufferView);
-            } else {
-                qCInfo(mesh_logging) << "ScriptableMesh -- resizing Buffer current:" << hint << bufferView._buffer->getSize() << "wanted:" << vsize;
-                bufferView._element = elementType;
-                bufferView._buffer->resize(vsize);
-                bufferView._size = bufferView._buffer->getSize();
-            }
-        }
-#ifdef DEV_BUILD
-        auto afterCount = bufferView.getNumElements();
-        auto afterTotal = bufferView._size;
-        if (beforeTotal != afterTotal || beforeCount != afterCount) {
-            auto typeName = DebugNames::stringFrom(bufferView._element.getType());
-            qCDebug(mesh_logging, "NOTE:: _expandedAttributeBuffer.%s vec%d %s (before count=%lu bytes=%lu // after count=%lu bytes=%lu)",
-                          hint.toStdString().c_str(), bufferView._element.getScalarCount(),
-                          typeName.toStdString().c_str(), beforeCount, beforeTotal, afterCount, afterTotal);
-        }
-#endif
-        return bufferView;
-    }
-    const gpu::Element UNUSED{ gpu::SCALAR, gpu::UINT8, gpu::RAW };
-
-    gpu::Element getVecNElement(gpu::Type T, int N) {
-        switch(N) {
-        case 2: return { gpu::VEC2, T, gpu::XY };
-        case 3: return { gpu::VEC3, T, gpu::XYZ };
-        case 4: return { gpu::VEC4, T, gpu::XYZW };
-        }
-        Q_ASSERT(false);
-        return UNUSED;
-    }
-
-    gpu::BufferView expandAttributeToMatchPositions(scriptable::MeshPointer mesh, gpu::Stream::Slot slot) {
-        if (slot == gpu::Stream::POSITION) {
-            return getBufferView(mesh, slot);
-        }
-        return _expandedAttributeBuffer(mesh, slot, getVecNElement(gpu::FLOAT, 3));
-    }
-}
-
-std::map<QString, gpu::BufferView> ScriptableMesh::gatherBufferViews(scriptable::MeshPointer mesh, const QStringList& expandToMatchPositions) {
-    std::map<QString, gpu::BufferView> attributeViews;
-    if (!mesh) {
-        return attributeViews;
-    }
-    for (const auto& a : ScriptableMesh::ATTRIBUTES.toStdMap()) {
-        auto name = a.first;
-        auto slot = a.second;
-        if (expandToMatchPositions.contains(name)) {
-            expandAttributeToMatchPositions(mesh, slot);
-        }
-        auto view = getBufferView(mesh, slot);
-        auto beforeCount = view.getNumElements();
-        if (beforeCount > 0) {
-            auto element = view._element;
-            auto vecN = element.getScalarCount();
-            auto type = element.getType();
-            QString typeName = DebugNames::stringFrom(element.getType());
-            auto beforeTotal = view._size;
-
-            attributeViews[name] = _expandedAttributeBuffer(mesh, slot, getVecNElement(type, vecN));
-
-#if DEV_BUILD
-            auto afterTotal = attributeViews[name]._size;
-            auto afterCount = attributeViews[name].getNumElements();
-            if (beforeTotal != afterTotal || beforeCount != afterCount) {
-                qCDebug(mesh_logging, "NOTE:: gatherBufferViews.%s vec%d %s (before count=%lu bytes=%lu // after count=%lu bytes=%lu)",
-                              name.toStdString().c_str(), vecN, typeName.toStdString().c_str(), beforeCount, beforeTotal, afterCount, afterTotal);
-            }
-#endif
-        }
-    }
-    return attributeViews;
-}
-
-QScriptValue ScriptableModel::mapAttributeValues(QScriptValue scopeOrCallback, QScriptValue methodOrName) {
-    auto context = scopeOrCallback.engine()->currentContext();
-    auto _in = context->thisObject();
-    qCInfo(mesh_logging) << "mapAttributeValues" << _in.toVariant().typeName() << _in.toVariant().toString() << _in.toQObject();
-    auto model = qscriptvalue_cast<scriptable::ScriptableModel>(_in);
-    QVector<scriptable::ScriptableMeshPointer> in = model.getMeshes();
-    if (in.size()) {
-        foreach (scriptable::ScriptableMeshPointer meshProxy, in) {
-            meshProxy->mapAttributeValues(scopeOrCallback, methodOrName);
-        }
-        return _in;
-    } else if (auto meshProxy = qobject_cast<scriptable::ScriptableMesh*>(_in.toQObject())) {
-        return meshProxy->mapAttributeValues(scopeOrCallback, methodOrName);
-    } else {
-        context->throwError("invalid ModelProxy || MeshProxyPointer");
-    }
-    return false;
-}
-
-
-
-QScriptValue ScriptableMesh::mapAttributeValues(QScriptValue scopeOrCallback, QScriptValue methodOrName) {
+quint32 scriptable::ScriptableMesh::mapAttributeValues(QScriptValue _callback) {
     auto mesh = getMeshPointer();
     if (!mesh) {
-        return false;
+        return 0;
     }
-    auto scopedHandler = makeScopedHandlerObject(scopeOrCallback, methodOrName);
+    auto scopedHandler = jsBindCallback(_callback);
 
     // input buffers
     gpu::BufferView positions = mesh->getVertexBuffer();
@@ -417,20 +282,25 @@ QScriptValue ScriptableMesh::mapAttributeValues(QScriptValue scopeOrCallback, QS
     // destructure so we can still invoke callback scoped, but with a custom signature (obj, i, jsMesh)
     auto scope = scopedHandler.property("scope");
     auto callback = scopedHandler.property("callback");
-    auto js = engine(); // cache value to avoid resolving each iteration
-    auto meshPart = thisObject();//js->toScriptValue(meshProxy);
-
+    auto js = engine() ? engine() : scopedHandler.engine(); // cache value to avoid resolving each iteration
+    if (!js) {
+        return 0;
+    }
+    auto meshPart = js ? js->toScriptValue(getSelf()) : QScriptValue::NullValue;
+    qCInfo(graphics_scripting) << "mapAttributeValues" << mesh.get() << js->currentContext()->thisObject().toQObject();
     auto obj = js->newObject();
-    auto attributeViews = ScriptableMesh::gatherBufferViews(mesh, { "normal", "color" });
-    for (uint32_t i=0; i < nPositions; i++) {
+    auto attributeViews = buffer_helpers::gatherBufferViews(mesh, { "normal", "color" });
+    metadata["last-modified"] = QDateTime::currentDateTime().toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate);
+    uint32_t i = 0;
+    for (; i < nPositions; i++) {
         for (const auto& a : attributeViews) {
             bool asArray = a.second._element.getType() != gpu::FLOAT;
             obj.setProperty(a.first, bufferViewElementToScriptValue(js, a.second, i, asArray, a.first.toStdString().c_str()));
         }
         auto result = callback.call(scope, { obj, i, meshPart });
         if (js->hasUncaughtException()) {
-            context()->throwValue(js->uncaughtException());
-            return false;
+            js->currentContext()->throwValue(js->uncaughtException());
+            return i;
         }
 
         if (result.isBool() && !result.toBool()) {
@@ -450,15 +320,19 @@ QScriptValue ScriptableMesh::mapAttributeValues(QScriptValue scopeOrCallback, QS
             }
         }
     }
-    return thisObject();
+    return i;
 }
 
-QScriptValue ScriptableMesh::unrollVertices(bool recalcNormals) {
+quint32 scriptable::ScriptableMeshPart::mapAttributeValues(QScriptValue callback) {
+    return parentMesh ? parentMesh->mapAttributeValues(callback) : 0; 
+}
+
+bool scriptable::ScriptableMeshPart::unrollVertices(bool recalcNormals) {
     auto meshProxy = this;
     auto mesh = getMeshPointer();
-    qCInfo(mesh_logging) << "ScriptableMesh::unrollVertices" << !!mesh<< !!meshProxy;
+    qCInfo(graphics_scripting) << "ScriptableMeshPart::unrollVertices" << !!mesh<< !!meshProxy;
     if (!mesh) {
-        return QScriptValue();
+        return false;
     }
 
     auto positions = mesh->getVertexBuffer();
@@ -467,8 +341,9 @@ QScriptValue ScriptableMesh::unrollVertices(bool recalcNormals) {
     auto buffer = new gpu::Buffer();
     buffer->resize(numPoints * sizeof(uint32_t));
     auto newindices = gpu::BufferView(buffer, { gpu::SCALAR, gpu::UINT32, gpu::INDEX });
-    qCInfo(mesh_logging) << "ScriptableMesh::unrollVertices numPoints" << numPoints;
-    auto attributeViews = ScriptableMesh::gatherBufferViews(mesh);
+    metadata["last-modified"] = QDateTime::currentDateTime().toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate);
+    qCInfo(graphics_scripting) << "ScriptableMeshPart::unrollVertices numPoints" << numPoints;
+    auto attributeViews = buffer_helpers::gatherBufferViews(mesh);
     for (const auto& a : attributeViews) {
         auto& view = a.second;
         auto sz = view._element.getSize();
@@ -477,19 +352,21 @@ QScriptValue ScriptableMesh::unrollVertices(bool recalcNormals) {
         auto points = gpu::BufferView(buffer, view._element);
         auto src = (uint8_t*)view._buffer->getData();
         auto dest = (uint8_t*)points._buffer->getData();
-        auto slot = ScriptableMesh::ATTRIBUTES[a.first];
-        qCInfo(mesh_logging) << "ScriptableMesh::unrollVertices buffer" << a.first;
-        qCInfo(mesh_logging) << "ScriptableMesh::unrollVertices source" << view.getNumElements();
-        qCInfo(mesh_logging) << "ScriptableMesh::unrollVertices dest" << points.getNumElements();
-        qCInfo(mesh_logging) << "ScriptableMesh::unrollVertices sz" << sz << src << dest << slot;
+        auto slot = buffer_helpers::ATTRIBUTES[a.first];
+        if (0) {
+            qCInfo(graphics_scripting) << "ScriptableMeshPart::unrollVertices buffer" << a.first;
+            qCInfo(graphics_scripting) << "ScriptableMeshPart::unrollVertices source" << view.getNumElements();
+            qCInfo(graphics_scripting) << "ScriptableMeshPart::unrollVertices dest" << points.getNumElements();
+            qCInfo(graphics_scripting) << "ScriptableMeshPart::unrollVertices sz" << sz << src << dest << slot;
+        }
         auto esize = indices._element.getSize();
         const char* hint= a.first.toStdString().c_str();
         for(quint32 i = 0; i < numPoints; i++) {
             quint32 index = esize == 4 ? indices.get<quint32>(i) : indices.get<quint16>(i);
             newindices.edit<uint32_t>(i) = i;
-            bufferViewElementFromVariant(
+            buffer_helpers::fromVariant(
                 points, i,
-                bufferViewElementToVariant(view, index, false, hint)
+                buffer_helpers::toVariant(view, index, false, hint)
                     );
         }
         if (slot == gpu::Stream::POSITION) {
@@ -505,62 +382,70 @@ QScriptValue ScriptableMesh::unrollVertices(bool recalcNormals) {
     return true;
 }
 
-bool ScriptableMesh::replaceMeshData(scriptable::ScriptableMeshPointer src, const QVector<QString>& attributeNames) {
+bool scriptable::ScriptableMeshPart::replaceMeshData(scriptable::ScriptableMeshPartPointer src, const QVector<QString>& attributeNames) {
     auto target = getMeshPointer();
     auto source = src ? src->getMeshPointer() : nullptr;
     if (!target || !source) {
-        context()->throwError("ScriptableMesh::replaceMeshData -- expected dest and src to be valid mesh proxy pointers");
+        if (context()) {
+            context()->throwError("ScriptableMeshPart::replaceMeshData -- expected dest and src to be valid mesh proxy pointers");
+        } else {
+            qCWarning(graphics_scripting) << "ScriptableMeshPart::replaceMeshData -- expected dest and src to be valid mesh proxy pointers";
+        }
         return false;
     }
 
-    QVector<QString> attributes = attributeNames.isEmpty() ? src->getAttributeNames() : attributeNames;
+    QVector<QString> attributes = attributeNames.isEmpty() ? src->parentMesh->getAttributeNames() : attributeNames;
 
-    //qCInfo(mesh_logging) << "ScriptableMesh::replaceMeshData -- source:" << source->displayName << "target:" << target->displayName << "attributes:" << attributes;
+    qCInfo(graphics_scripting) << "ScriptableMeshPart::replaceMeshData -- " <<
+        "source:" << QString::fromStdString(source->displayName) <<
+        "target:" << QString::fromStdString(target->displayName) <<
+        "attributes:" << attributes;
+
+    metadata["last-modified"] = QDateTime::currentDateTime().toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate);
 
     // remove attributes only found on target mesh, unless user has explicitly specified the relevant attribute names
     if (attributeNames.isEmpty()) {
-        auto attributeViews = ScriptableMesh::gatherBufferViews(target);
+        auto attributeViews = buffer_helpers::gatherBufferViews(target);
         for (const auto& a : attributeViews) {
-            auto slot = ScriptableMesh::ATTRIBUTES[a.first];
+            auto slot = buffer_helpers::ATTRIBUTES[a.first];
             if (!attributes.contains(a.first)) {
-                //qCInfo(mesh_logging) << "ScriptableMesh::replaceMeshData -- pruning target attribute" << a.first << slot;
+                qCInfo(graphics_scripting) << "ScriptableMesh::replaceMeshData -- pruning target attribute" << a.first << slot;
                 target->removeAttribute(slot);
             }
         }
     }
 
-    target->setVertexBuffer(cloneBufferView(source->getVertexBuffer()));
-    target->setIndexBuffer(cloneBufferView(source->getIndexBuffer()));
-    target->setPartBuffer(cloneBufferView(source->getPartBuffer()));
+    target->setVertexBuffer(buffer_helpers::clone(source->getVertexBuffer()));
+    target->setIndexBuffer(buffer_helpers::clone(source->getIndexBuffer()));
+    target->setPartBuffer(buffer_helpers::clone(source->getPartBuffer()));
 
     for (const auto& a : attributes) {
-        auto slot = ScriptableMesh::ATTRIBUTES[a];
+        auto slot = buffer_helpers::ATTRIBUTES[a];
         if (slot == gpu::Stream::POSITION) {
             continue;
         }
-        // auto& before = target->getAttributeBuffer(slot);
+        auto& before = target->getAttributeBuffer(slot);
         auto& input = source->getAttributeBuffer(slot);
         if (input.getNumElements() == 0) {
-            //qCInfo(mesh_logging) << "ScriptableMesh::replaceMeshData buffer is empty -- pruning" << a << slot;
+            qCInfo(graphics_scripting) << "ScriptableMeshPart::replaceMeshData buffer is empty -- pruning" << a << slot;
             target->removeAttribute(slot);
         } else {
-            // if (before.getNumElements() == 0) {
-            //     qCInfo(mesh_logging) << "ScriptableMesh::replaceMeshData target buffer is empty -- adding" << a << slot;
-            // } else {
-            //     qCInfo(mesh_logging) << "ScriptableMesh::replaceMeshData target buffer exists -- updating" << a << slot;
-            // }
-            target->addAttribute(slot, cloneBufferView(input));
+            if (before.getNumElements() == 0) {
+                qCInfo(graphics_scripting) << "ScriptableMeshPart::replaceMeshData target buffer is empty -- adding" << a << slot;
+            } else {
+                qCInfo(graphics_scripting) << "ScriptableMeshPart::replaceMeshData target buffer exists -- updating" << a << slot;
+            }
+            target->addAttribute(slot, buffer_helpers::clone(input));
         }
-        // auto& after = target->getAttributeBuffer(slot);
-        // qCInfo(mesh_logging) << "ScriptableMesh::replaceMeshData" << a << slot << before.getNumElements() << " -> " << after.getNumElements();
+        auto& after = target->getAttributeBuffer(slot);
+        qCInfo(graphics_scripting) << "ScriptableMeshPart::replaceMeshData" << a << slot << before.getNumElements() << " -> " << after.getNumElements();
     }
 
 
     return true;
 }
 
-bool ScriptableMesh::dedupeVertices(float epsilon) {
-    scriptable::ScriptableMeshPointer meshProxy = this;
+bool scriptable::ScriptableMeshPart::dedupeVertices(float epsilon) {
     auto mesh = getMeshPointer();
     if (!mesh) {
         return false;
@@ -573,6 +458,7 @@ bool ScriptableMesh::dedupeVertices(float epsilon) {
     uniqueVerts.reserve((int)numPositions);
     QMap<quint32,quint32> remapIndices;
 
+    metadata["last-modified"] = QDateTime::currentDateTime().toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate);
     for (quint32 i = 0; i < numPositions; i++) {
         const quint32 numUnique = uniqueVerts.size();
         const auto& position = positions.get<glm::vec3>(i);
@@ -590,7 +476,7 @@ bool ScriptableMesh::dedupeVertices(float epsilon) {
         }
     }
 
-    qCInfo(mesh_logging) << "//VERTS before" << numPositions << "after" << uniqueVerts.size();
+    qCInfo(graphics_scripting) << "//VERTS before" << numPositions << "after" << uniqueVerts.size();
 
     auto indices = mesh->getIndexBuffer();
     auto numIndices = indices.getNumElements();
@@ -600,34 +486,34 @@ bool ScriptableMesh::dedupeVertices(float epsilon) {
     for (quint32 i = 0; i < numIndices; i++) {
         quint32 index = esize == 4 ? indices.get<quint32>(i) : indices.get<quint16>(i);
         if (remapIndices.contains(index)) {
-            //qCInfo(mesh_logging) << i << index << "->" << remapIndices[index];
+            //qCInfo(graphics_scripting) << i << index << "->" << remapIndices[index];
             newIndices << remapIndices[index];
         } else {
-            qCInfo(mesh_logging) << i << index << "!remapIndices[index]";
+            qCInfo(graphics_scripting) << i << index << "!remapIndices[index]";
         }
     }
 
-    mesh->setIndexBuffer(bufferViewFromVector(newIndices, { gpu::SCALAR, gpu::UINT32, gpu::INDEX }));
-    mesh->setVertexBuffer(bufferViewFromVector(uniqueVerts, { gpu::VEC3, gpu::FLOAT, gpu::XYZ }));
+    mesh->setIndexBuffer(buffer_helpers::fromVector(newIndices, { gpu::SCALAR, gpu::UINT32, gpu::INDEX }));
+    mesh->setVertexBuffer(buffer_helpers::fromVector(uniqueVerts, { gpu::VEC3, gpu::FLOAT, gpu::XYZ }));
 
-    auto attributeViews = ScriptableMesh::gatherBufferViews(mesh);
+    auto attributeViews = buffer_helpers::gatherBufferViews(mesh);
     quint32 numUniqueVerts = uniqueVerts.size();
     for (const auto& a : attributeViews) {
         auto& view = a.second;
-        auto slot = ScriptableMesh::ATTRIBUTES[a.first];
+        auto slot = buffer_helpers::ATTRIBUTES[a.first];
         if (slot == gpu::Stream::POSITION) {
             continue;
         }
-        qCInfo(mesh_logging) << "ScriptableMesh::dedupeVertices" << a.first << slot << view.getNumElements();
-        auto newView = resizedBufferView(view, numUniqueVerts);
-        qCInfo(mesh_logging) << a.first << "before: #" << view.getNumElements() << "after: #" << newView.getNumElements();
+        qCInfo(graphics_scripting) << "ScriptableMeshPart::dedupeVertices" << a.first << slot << view.getNumElements();
+        auto newView = buffer_helpers::resize(view, numUniqueVerts);
+        qCInfo(graphics_scripting) << a.first << "before: #" << view.getNumElements() << "after: #" << newView.getNumElements();
         quint32 numElements = (quint32)view.getNumElements();
         for (quint32 i = 0; i < numElements; i++) {
             quint32 fromVertexIndex = i;
             quint32 toVertexIndex = remapIndices.contains(fromVertexIndex) ? remapIndices[fromVertexIndex] : fromVertexIndex;
-            bufferViewElementFromVariant(
+            buffer_helpers::fromVariant(
                 newView, toVertexIndex,
-                bufferViewElementToVariant(view, fromVertexIndex, false, "dedupe")
+                buffer_helpers::toVariant(view, fromVertexIndex, false, "dedupe")
                 );
         }
         mesh->addAttribute(slot, newView);
@@ -635,120 +521,202 @@ bool ScriptableMesh::dedupeVertices(float epsilon) {
     return true;
 }
 
-QScriptValue ScriptableMesh::cloneMesh(bool recalcNormals) {
+scriptable::ScriptableMeshPointer scriptable::ScriptableMesh::cloneMesh(bool recalcNormals) {
     auto mesh = getMeshPointer();
     if (!mesh) {
-        return QScriptValue::NullValue;
+        qCInfo(graphics_scripting) << "ScriptableMesh::cloneMesh -- !meshPointer";
+        return nullptr;
     }
-    graphics::MeshPointer clone(new graphics::Mesh());
-    clone->displayName = mesh->displayName + "-clone";
-    qCInfo(mesh_logging) << "ScriptableMesh::cloneMesh" << !!mesh;
-    if (!mesh) {
-        return QScriptValue::NullValue;
-    }
-
-    clone->setIndexBuffer(cloneBufferView(mesh->getIndexBuffer()));
-    clone->setPartBuffer(cloneBufferView(mesh->getPartBuffer()));
-    auto attributeViews = ScriptableMesh::gatherBufferViews(mesh);
-    for (const auto& a : attributeViews) {
-        auto& view = a.second;
-        auto slot = ScriptableMesh::ATTRIBUTES[a.first];
-        qCInfo(mesh_logging) << "ScriptableMesh::cloneVertices buffer" << a.first << slot;
-        auto points = cloneBufferView(view);
-        qCInfo(mesh_logging) << "ScriptableMesh::cloneVertices source" << view.getNumElements();
-        qCInfo(mesh_logging) << "ScriptableMesh::cloneVertices dest" << points.getNumElements();
-        if (slot == gpu::Stream::POSITION) {
-            clone->setVertexBuffer(points);
-        } else {
-            clone->addAttribute(slot, points);
-        }
-    }
-
-    auto result = scriptable::ScriptableMeshPointer(new ScriptableMesh(nullptr, clone));
+    qCInfo(graphics_scripting) << "ScriptableMesh::cloneMesh...";
+    auto clone = buffer_helpers::cloneMesh(mesh);
+    
+    qCInfo(graphics_scripting) << "ScriptableMesh::cloneMesh...";
     if (recalcNormals) {
-        result->recalculateNormals();
+        buffer_helpers::recalculateNormals(clone);
     }
-    return engine()->toScriptValue(result);
+    qCDebug(graphics_scripting) << clone.get();// << metadata;
+    auto meshPointer = scriptable::make_scriptowned<scriptable::ScriptableMesh>(provider, model, clone, metadata);
+    clone.reset(); // free local reference
+    qCInfo(graphics_scripting) << "========= ScriptableMesh::cloneMesh..." << meshPointer << meshPointer->ownedMesh.use_count();
+    //scriptable::MeshPointer* ppMesh = new scriptable::MeshPointer();
+    //*ppMesh = clone;
+
+    if (meshPointer) {
+        scriptable::WeakMeshPointer delme = meshPointer->mesh;
+        QString debugString = scriptable::toDebugString(meshPointer);
+        QObject::connect(meshPointer, &QObject::destroyed, meshPointer, [=]() {
+            qCWarning(graphics_scripting) << "*************** cloneMesh/Destroy";
+            qCWarning(graphics_scripting) << "*************** " << debugString << delme.lock().get();
+            if (!delme.expired()) {
+                qCWarning(graphics_scripting) << "cloneMesh -- potential memory leak..." << debugString << delme.lock().get();
+            }
+        });
+    }
+            
+    meshPointer->metadata["last-modified"] = QDateTime::currentDateTime().toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate);
+    return scriptable::ScriptableMeshPointer(meshPointer);
 }
 
-bool ScriptableMesh::recalculateNormals() {
-    scriptable::ScriptableMeshPointer meshProxy = this;
-    qCInfo(mesh_logging) << "Recalculating normals" << !!meshProxy;
-    auto mesh = getMeshPointer();
-    if (!mesh) {
-        return false;
-    }
-    ScriptableMesh::gatherBufferViews(mesh, { "normal", "color" }); // ensures #normals >= #positions
-    auto normals = mesh->getAttributeBuffer(gpu::Stream::NORMAL);
-    auto verts = mesh->getVertexBuffer();
-    auto indices = mesh->getIndexBuffer();
-    auto esize = indices._element.getSize();
-    auto numPoints = indices.getNumElements();
-    const auto TRIANGLE = 3;
-    quint32 numFaces = (quint32)numPoints / TRIANGLE;
-    //QVector<Triangle> faces;
-    QVector<glm::vec3> faceNormals;
-    QMap<QString,QVector<quint32>> vertexToFaces;
-    //faces.resize(numFaces);
-    faceNormals.resize(numFaces);
-    auto numNormals = normals.getNumElements();
-    qCInfo(mesh_logging) << QString("numFaces: %1, numNormals: %2, numPoints: %3").arg(numFaces).arg(numNormals).arg(numPoints);
-    if (normals.getNumElements() != verts.getNumElements()) {
-        return false;
-    }
-    for (quint32 i = 0; i < numFaces; i++) {
-        quint32 I = TRIANGLE * i;
-        quint32 i0 = esize == 4 ? indices.get<quint32>(I+0) : indices.get<quint16>(I+0);
-        quint32 i1 = esize == 4 ? indices.get<quint32>(I+1) : indices.get<quint16>(I+1);
-        quint32 i2 = esize == 4 ? indices.get<quint32>(I+2) : indices.get<quint16>(I+2);
-
-        Triangle face = {
-            verts.get<glm::vec3>(i1),
-            verts.get<glm::vec3>(i2),
-            verts.get<glm::vec3>(i0)
-        };
-        faceNormals[i] = face.getNormal();
-        if (glm::isnan(faceNormals[i].x)) {
-            qCInfo(mesh_logging) << i << i0 << i1 << i2 << vec3toVariant(face.v0) << vec3toVariant(face.v1) << vec3toVariant(face.v2);
-            break;
-        }
-        vertexToFaces[glm::to_string(face.v0).c_str()] << i;
-        vertexToFaces[glm::to_string(face.v1).c_str()] << i;
-        vertexToFaces[glm::to_string(face.v2).c_str()] << i;
-    }
-    for (quint32 j = 0; j < numNormals; j++) {
-        //auto v = verts.get<glm::vec3>(j);
-        glm::vec3 normal { 0.0f, 0.0f, 0.0f };
-        QString key { glm::to_string(verts.get<glm::vec3>(j)).c_str() };
-        const auto& faces = vertexToFaces.value(key);
-        if (faces.size()) {
-            for (const auto i : faces) {
-                normal += faceNormals[i];
-            }
-            normal *= 1.0f / (float)faces.size();
-        } else {
-            static int logged = 0;
-            if (logged++ < 10) {
-                qCInfo(mesh_logging) << "no faces for key!?" << key;
-            }
-            normal = verts.get<glm::vec3>(j);
-        }
-        if (glm::isnan(normal.x)) {
-            static int logged = 0;
-            if (logged++ < 10) {
-                qCInfo(mesh_logging) << "isnan(normal.x)" << j << vec3toVariant(normal);
-            }
-            break;
-        }
-        normals.edit<glm::vec3>(j) = glm::normalize(normal);
-    }
-    return true;
+scriptable::ScriptableMeshBase::ScriptableMeshBase(scriptable::WeakModelProviderPointer provider, scriptable::ScriptableModelBasePointer model, scriptable::WeakMeshPointer mesh, const QVariantMap& metadata)
+    : provider(provider), model(model), mesh(mesh), metadata(metadata) {}
+scriptable::ScriptableMeshBase::ScriptableMeshBase(scriptable::WeakMeshPointer mesh) : scriptable::ScriptableMeshBase(scriptable::WeakModelProviderPointer(), nullptr, mesh, QVariantMap()) { }
+scriptable::ScriptableMeshBase::ScriptableMeshBase(scriptable::MeshPointer mesh, const QVariantMap& metadata)
+    : ScriptableMeshBase(WeakModelProviderPointer(), nullptr, mesh, metadata) {
+    ownedMesh = mesh;
+}
+//scriptable::ScriptableMeshBase::ScriptableMeshBase(const scriptable::ScriptableMeshBase& other) { *this = other; }
+scriptable::ScriptableMeshBase& scriptable::ScriptableMeshBase::operator=(const scriptable::ScriptableMeshBase& view) {
+    provider = view.provider;
+    model = view.model;
+    mesh = view.mesh;
+    ownedMesh = view.ownedMesh;
+    metadata = view.metadata;
+    return *this;
+}
+                                                                                                                                                                                               scriptable::ScriptableMeshBase::~ScriptableMeshBase() {
+    ownedMesh.reset();
+    qCInfo(graphics_scripting) << "//~ScriptableMeshBase" << this << "ownedMesh:"  << ownedMesh.use_count() << "mesh:" << mesh.use_count();
 }
 
-QString ScriptableMesh::toOBJ() {
+scriptable::ScriptableMesh::~ScriptableMesh() {
+    ownedMesh.reset();
+    qCInfo(graphics_scripting) << "//~ScriptableMesh" << this << "ownedMesh:"  << ownedMesh.use_count() << "mesh:" << mesh.use_count();
+}
+
+QString scriptable::ScriptableMeshPart::toOBJ() {
     if (!getMeshPointer()) {
-        context()->throwError(QString("null mesh"));
+        if (context()) {
+            context()->throwError(QString("null mesh"));
+        } else {
+            qCWarning(graphics_scripting) << "null mesh";
+        }            
+        return QString();
     }
     return writeOBJToString({ getMeshPointer() });
 }
 
+namespace {
+    template <typename T>
+    QScriptValue qObjectToScriptValue(QScriptEngine* engine, const T& object) {
+        if (!object) {
+            return QScriptValue::NullValue;
+        }
+        auto ownership = object->metadata.value("__ownership__");
+        return engine->newQObject(
+            object,
+            ownership.isValid() ? static_cast<QScriptEngine::ValueOwnership>(ownership.toInt()) : QScriptEngine::QtOwnership
+            //, QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects
+        );
+    }
+
+    QScriptValue meshPointerToScriptValue(QScriptEngine* engine, const scriptable::ScriptableMeshPointer& in) {
+        return qObjectToScriptValue(engine, in);
+    }
+    QScriptValue meshPartPointerToScriptValue(QScriptEngine* engine, const scriptable::ScriptableMeshPartPointer& in) {
+        return qObjectToScriptValue(engine, in);
+    }
+    QScriptValue modelPointerToScriptValue(QScriptEngine* engine, const scriptable::ScriptableModelPointer& in) {
+        return qObjectToScriptValue(engine, in);
+    }
+
+    void meshPointerFromScriptValue(const QScriptValue& value, scriptable::ScriptableMeshPointer &out) {
+        out = scriptable::qpointer_qobject_cast<scriptable::ScriptableMesh>(value);
+    }
+    void modelPointerFromScriptValue(const QScriptValue& value, scriptable::ScriptableModelPointer &out) {
+        out = scriptable::qpointer_qobject_cast<scriptable::ScriptableModel>(value);
+    }
+    void meshPartPointerFromScriptValue(const QScriptValue& value, scriptable::ScriptableMeshPartPointer &out) {
+        out = scriptable::qpointer_qobject_cast<scriptable::ScriptableMeshPart>(value);
+    }
+    
+    // FIXME: MESHFACES:
+    // QScriptValue meshFaceToScriptValue(QScriptEngine* engine, const mesh::MeshFace &meshFace) {
+    //     QScriptValue obj = engine->newObject();
+    //     obj.setProperty("vertices", qVectorIntToScriptValue(engine, meshFace.vertexIndices));
+    //     return obj;
+    // }
+    // void meshFaceFromScriptValue(const QScriptValue &object, mesh::MeshFace& meshFaceResult) {
+    //     qScriptValueToSequence(object.property("vertices"), meshFaceResult.vertexIndices);
+    // }
+    // QScriptValue qVectorMeshFaceToScriptValue(QScriptEngine* engine, const QVector<mesh::MeshFace>& vector) {
+    //     return qScriptValueFromSequence(engine, vector);
+    // }
+    // void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector<mesh::MeshFace>& result) {
+    //     qScriptValueToSequence(array, result);
+    // }
+
+    QScriptValue qVectorUInt32ToScriptValue(QScriptEngine* engine, const QVector<scriptable::uint32>& vector) {
+        return qScriptValueFromSequence(engine, vector);
+    }
+
+    void qVectorUInt32FromScriptValue(const QScriptValue& array, QVector<scriptable::uint32>& result) {
+        qScriptValueToSequence(array, result);
+    }
+
+    QVector<int> metaTypeIds{
+        qRegisterMetaType<scriptable::uint32>("uint32"),
+        qRegisterMetaType<scriptable::uint32>("scriptable::uint32"),
+        qRegisterMetaType<QVector<scriptable::uint32>>(),
+        qRegisterMetaType<QVector<scriptable::uint32>>("QVector<uint32>"),
+        qRegisterMetaType<scriptable::ScriptableMeshPointer>(),
+        qRegisterMetaType<scriptable::ScriptableModelPointer>(),
+        qRegisterMetaType<scriptable::ScriptableMeshPartPointer>(),
+    };
+}
+
+namespace scriptable {
+    bool registerMetaTypes(QScriptEngine* engine) {
+        qScriptRegisterSequenceMetaType<QVector<scriptable::ScriptableMeshPartPointer>>(engine);
+        qScriptRegisterSequenceMetaType<QVector<scriptable::ScriptableMeshPointer>>(engine);
+        qScriptRegisterSequenceMetaType<QVector<scriptable::uint32>>(engine);
+        
+        qScriptRegisterMetaType(engine, qVectorUInt32ToScriptValue, qVectorUInt32FromScriptValue);
+        qScriptRegisterMetaType(engine, modelPointerToScriptValue, modelPointerFromScriptValue);
+        qScriptRegisterMetaType(engine, meshPointerToScriptValue, meshPointerFromScriptValue);
+        qScriptRegisterMetaType(engine, meshPartPointerToScriptValue, meshPartPointerFromScriptValue);
+
+        return metaTypeIds.size();
+    }
+    // callback helper that lets C++ method signatures remain simple (ie: taking a single callback argument) while
+    // still supporting extended Qt signal-like (scope, "methodName") and (scope, function(){}) "this" binding conventions
+    QScriptValue jsBindCallback(QScriptValue callback) {
+        if (callback.isObject() && callback.property("callback").isFunction()) {
+            return callback;
+        }
+        auto engine = callback.engine();
+        auto context = engine ? engine->currentContext() : nullptr;
+        auto length = context ? context->argumentCount() : 0;
+        QScriptValue scope = context ? context->thisObject() : QScriptValue::NullValue;
+        QScriptValue method;
+        qCInfo(graphics_scripting) << "jsBindCallback" << engine << length << scope.toQObject() << method.toString();
+        int i = 0;
+        for (; context && i < length; i++) {
+            if (context->argument(i).strictlyEquals(callback)) {
+                method = context->argument(i+1);
+            }
+        }
+        if (method.isFunction() || method.isString()) {
+            scope = callback;
+        } else {
+            method = callback;
+        }
+        qCInfo(graphics_scripting) << "scope:" << scope.toQObject() << "method:" << method.toString();
+        return ::makeScopedHandlerObject(scope,  method);
+    }
+}
+
+bool scriptable::GraphicsScriptingInterface::updateMeshPart(ScriptableMeshPointer mesh, ScriptableMeshPartPointer part) {
+    Q_ASSERT(mesh);
+    Q_ASSERT(part);
+    Q_ASSERT(part->parentMesh);
+    auto tmp = exportMeshPart(mesh, part->partIndex);
+    if (part->parentMesh == mesh) {
+        qCInfo(graphics_scripting) << "updateMeshPart -- update via clone" << mesh << part;
+        tmp->replaceMeshData(part->cloneMeshPart());
+        return false;
+    } else {
+        qCInfo(graphics_scripting) << "updateMeshPart -- update via inplace" << mesh << part;
+        tmp->replaceMeshData(part);
+        return true;
+    }
+}
diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h
index 257285fa90..c655167c2b 100644
--- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h
+++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h
@@ -11,142 +11,188 @@
 
 #include <DependencyManager.h>
 
+//#include <graphics-scriping/Forward.h>
 #include <graphics-scripting/ScriptableModel.h>
+#include <graphics-scripting/BufferViewHelpers.h>
 
 #include <QtScript/QScriptable>
 #include <QtScript/QScriptValue>
 
-namespace graphics {
-    class Mesh;
-}
-namespace gpu {
-    class BufferView;
-}
 namespace scriptable {
-    class ScriptableMeshPart;
-    using ScriptableMeshPartPointer = QPointer<ScriptableMeshPart>;
-    class ScriptableMesh : public QObject, QScriptable {
+
+    QScriptValue jsBindCallback(QScriptValue callback);
+    class ScriptableMesh : public ScriptableMeshBase, QScriptable {
         Q_OBJECT
     public:
-        Q_PROPERTY(quint32 numParts READ getNumParts)
-        Q_PROPERTY(quint32 numAttributes READ getNumAttributes)
-        Q_PROPERTY(quint32 numVertices READ getNumVertices)
-        Q_PROPERTY(quint32 numIndices READ getNumIndices)
-        Q_PROPERTY(QVariantMap metadata MEMBER _metadata)
+        Q_PROPERTY(uint32 numParts READ getNumParts)
+        Q_PROPERTY(uint32 numAttributes READ getNumAttributes)
+        Q_PROPERTY(uint32 numVertices READ getNumVertices)
+        Q_PROPERTY(uint32 numIndices READ getNumIndices)
+        Q_PROPERTY(QVariantMap metadata MEMBER metadata)
         Q_PROPERTY(QVector<QString> attributeNames READ getAttributeNames)
+        Q_PROPERTY(QVector<scriptable::ScriptableMeshPartPointer> parts READ getMeshParts)
+        Q_PROPERTY(bool valid READ hasValidMesh)
+        bool hasValidMesh() const { return (bool)getMeshPointer(); }
+        Q_PROPERTY(bool validOwned READ hasValidOwnedMesh)
+        bool hasValidOwnedMesh() const { return (bool)getOwnedMeshPointer(); }
 
-        static QMap<QString,int> ATTRIBUTES;
-        static std::map<QString, gpu::BufferView> gatherBufferViews(MeshPointer mesh, const QStringList& expandToMatchPositions = QStringList());
+        operator const ScriptableMeshBase*() const { return (qobject_cast<const scriptable::ScriptableMeshBase*>(this)); }
+        ScriptableMesh(scriptable::MeshPointer mesh) : ScriptableMeshBase(mesh) { ownedMesh = mesh; }
+        ScriptableMesh(WeakModelProviderPointer provider, ScriptableModelBasePointer model, MeshPointer mesh, const QVariantMap& metadata)
+            : ScriptableMeshBase(provider, model, mesh, metadata) { ownedMesh = mesh; }
+        //ScriptableMesh& operator=(const ScriptableMesh& other)  { model=other.model; mesh=other.mesh; metadata=other.metadata; return *this; };
+        //ScriptableMesh() : QObject(), model(nullptr) {}
+        //ScriptableMesh(const ScriptableMesh& other) : QObject(), model(other.model), mesh(other.mesh), metadata(other.metadata) {}
+        ScriptableMesh(const ScriptableMeshBase& other);
+        ScriptableMesh(const ScriptableMesh& other) : ScriptableMeshBase(other) {};
+        virtual ~ScriptableMesh();
 
-        ScriptableMesh& operator=(const ScriptableMesh& other)  { _model=other._model; _mesh=other._mesh; _metadata=other._metadata; return *this; };
-        ScriptableMesh() : QObject(), _model(nullptr) {}
-        ScriptableMesh(ScriptableModelPointer parent, scriptable::MeshPointer mesh) : QObject(), _model(parent), _mesh(mesh) {}
-        ScriptableMesh(const ScriptableMesh& other) : QObject(), _model(other._model), _mesh(other._mesh), _metadata(other._metadata) {}
-        ~ScriptableMesh() { qDebug() << "~ScriptableMesh" << this; }
-
-        scriptable::MeshPointer getMeshPointer() const { return _mesh; }
+        Q_INVOKABLE const scriptable::ScriptableModelPointer getParentModel() const { return qobject_cast<scriptable::ScriptableModel*>(model); }
+        Q_INVOKABLE const scriptable::MeshPointer getOwnedMeshPointer() const { return ownedMesh; }
+        scriptable::ScriptableMeshPointer getSelf() const { return const_cast<scriptable::ScriptableMesh*>(this); }
    public slots:
-        quint32 getNumParts() const;
-        quint32 getNumVertices() const;
-        quint32 getNumAttributes() const;
-        quint32 getNumIndices() const { return 0;  }
+        uint32 getNumParts() const;
+        uint32 getNumVertices() const;
+        uint32 getNumAttributes() const;
+        uint32 getNumIndices() const;
         QVector<QString> getAttributeNames() const;
+        QVector<scriptable::ScriptableMeshPartPointer> getMeshParts() const;
 
-        QVariantMap getVertexAttributes(quint32 vertexIndex) const;
-        QVariantMap getVertexAttributes(quint32 vertexIndex, QVector<QString> attributes) const;
+        QVariantMap getVertexAttributes(uint32 vertexIndex) const;
+        QVariantMap getVertexAttributes(uint32 vertexIndex, QVector<QString> attributes) const;
 
-        QVector<quint32> getIndices() const;
-        QVector<quint32> findNearbyIndices(const glm::vec3& origin, float epsilon = 1e-6) const;
+        QVector<uint32> getIndices() const;
+        QVector<uint32> findNearbyIndices(const glm::vec3& origin, float epsilon = 1e-6) const;
         QVariantMap getMeshExtents() const;
-        bool setVertexAttributes(quint32 vertexIndex, QVariantMap attributes);
-        QVariantMap scaleToFit(float unitScale);
+        bool setVertexAttributes(uint32 vertexIndex, QVariantMap attributes);
 
         QVariantList getAttributeValues(const QString& attributeName) const;
 
         int _getSlotNumber(const QString& attributeName) const;
 
+        scriptable::ScriptableMeshPointer cloneMesh(bool recalcNormals = false);        
+    public:
+        operator bool() const { return !mesh.expired(); }
+
+    public slots:
+        // QScriptEngine-specific wrappers
+        uint32 mapAttributeValues(QScriptValue callback);
+    };
+
+    // TODO: part-specific wrapper for working with raw geometries
+    class ScriptableMeshPart : public QObject, QScriptable {
+        Q_OBJECT
+    public:
+        Q_PROPERTY(uint32 partIndex MEMBER partIndex CONSTANT)
+        Q_PROPERTY(int numElementsPerFace MEMBER _elementsPerFace CONSTANT)
+        Q_PROPERTY(QString topology MEMBER _topology CONSTANT)
+
+        Q_PROPERTY(uint32 numFaces READ getNumFaces)
+        Q_PROPERTY(uint32 numAttributes READ getNumAttributes)
+        Q_PROPERTY(uint32 numVertices READ getNumVertices)
+        Q_PROPERTY(uint32 numIndices READ getNumIndices)
+        Q_PROPERTY(QVector<QString> attributeNames READ getAttributeNames)
+
+        Q_PROPERTY(QVariantMap metadata MEMBER metadata)
+
+        //Q_PROPERTY(scriptable::ScriptableMeshPointer parentMesh MEMBER parentMesh CONSTANT HIDE)
+
+        ScriptableMeshPart(scriptable::ScriptableMeshPointer parentMesh, int partIndex);
+        ScriptableMeshPart& operator=(const ScriptableMeshPart& view) { parentMesh=view.parentMesh; return *this; };
+        ScriptableMeshPart(const ScriptableMeshPart& other) : parentMesh(other.parentMesh), partIndex(other.partIndex) {}
+        ~ScriptableMeshPart() { qDebug() << "~ScriptableMeshPart" << this; }
+
+    public slots:
+        scriptable::ScriptableMeshPointer getParentMesh() const { return parentMesh; }
+        uint32 getNumAttributes() const { return parentMesh ? parentMesh->getNumAttributes() : 0; }
+        uint32 getNumVertices() const { return parentMesh ? parentMesh->getNumVertices() : 0; }
+        uint32 getNumIndices() const { return parentMesh ? parentMesh->getNumIndices() : 0; }
+        uint32 getNumFaces() const { return parentMesh ? parentMesh->getNumIndices() / _elementsPerFace : 0; }
+        QVector<QString> getAttributeNames() const { return parentMesh ? parentMesh->getAttributeNames() : QVector<QString>(); }
+        QVector<uint32> getFace(uint32 faceIndex) const {
+            auto inds = parentMesh ? parentMesh->getIndices() : QVector<uint32>();
+            return faceIndex+2 < (uint32)inds.size() ? inds.mid(faceIndex*3, 3) : QVector<uint32>();
+        }
+        QVariantMap scaleToFit(float unitScale);
         QVariantMap translate(const glm::vec3& translation);
         QVariantMap scale(const glm::vec3& scale, const glm::vec3& origin = glm::vec3(NAN));
         QVariantMap rotateDegrees(const glm::vec3& eulerAngles, const glm::vec3& origin = glm::vec3(NAN));
         QVariantMap rotate(const glm::quat& rotation, const glm::vec3& origin = glm::vec3(NAN));
         QVariantMap transform(const glm::mat4& transform);
 
-    public:
-        operator bool() const { return _mesh != nullptr; }
-        ScriptableModelPointer _model;
-        scriptable::MeshPointer _mesh;
-        QVariantMap _metadata;
+        bool unrollVertices(bool recalcNormals = false);
+        bool dedupeVertices(float epsilon = 1e-6);
+        bool recalculateNormals() { return buffer_helpers::recalculateNormals(getMeshPointer()); }
+
+        bool replaceMeshData(scriptable::ScriptableMeshPartPointer source, const QVector<QString>& attributeNames = QVector<QString>());
+        scriptable::ScriptableMeshPartPointer cloneMeshPart(bool recalcNormals = false) {
+            if (parentMesh) {
+                if (auto clone = parentMesh->cloneMesh(recalcNormals)) {
+                    return clone->getMeshParts().value(partIndex);
+                }
+            }
+            return nullptr;
+        }
+        QString toOBJ();
 
     public slots:
         // QScriptEngine-specific wrappers
-        QScriptValue mapAttributeValues(QScriptValue scopeOrCallback, QScriptValue methodOrName = QScriptValue());
-        bool dedupeVertices(float epsilon = 1e-6);
-        bool recalculateNormals();
-        QScriptValue cloneMesh(bool recalcNormals = true);
-        QScriptValue unrollVertices(bool recalcNormals = true);
-        bool replaceMeshData(scriptable::ScriptableMeshPointer source, const QVector<QString>& attributeNames = QVector<QString>());
-        QString toOBJ();
-    };
-
-    // TODO: part-specific wrapper for working with raw geometries
-    class ScriptableMeshPart : public QObject {
-        Q_OBJECT
-    public:
-        Q_PROPERTY(QString topology READ getTopology)
-        Q_PROPERTY(quint32 numFaces READ getNumFaces)
-
-        ScriptableMeshPart& operator=(const ScriptableMeshPart& view) { parentMesh=view.parentMesh; return *this; };
-        ScriptableMeshPart(const ScriptableMeshPart& other) : parentMesh(other.parentMesh) {}
-        ScriptableMeshPart()  {}
-        ~ScriptableMeshPart() { qDebug() << "~ScriptableMeshPart" << this; }
-
-    public slots:
-        QString getTopology() const { return "triangles"; }
-        quint32 getNumFaces() const { return parentMesh.getIndices().size() / 3; }
-        QVector<quint32> getFace(quint32 faceIndex) const {
-            auto inds = parentMesh.getIndices();
-            return faceIndex+2 < (quint32)inds.size() ? inds.mid(faceIndex*3, 3) : QVector<quint32>();
-        }
+        uint32 mapAttributeValues(QScriptValue callback);
 
     public:
-        scriptable::ScriptableMesh parentMesh;
-        int partIndex;
+        scriptable::ScriptableMeshPointer parentMesh;
+        uint32 partIndex;
+        QVariantMap metadata;
+    protected:
+        int _elementsPerFace{ 3 };
+        QString _topology{ "triangles" };
+        scriptable::MeshPointer getMeshPointer() const { return parentMesh ? parentMesh->getMeshPointer() : nullptr; }    
     };
 
-    class GraphicsScriptingInterface : public QObject {
+    class GraphicsScriptingInterface : public QObject, QScriptable {
         Q_OBJECT
     public:
         GraphicsScriptingInterface(QObject* parent = nullptr) : QObject(parent) {}
         GraphicsScriptingInterface(const GraphicsScriptingInterface& other) {}
-        public slots:
-            ScriptableMeshPart exportMeshPart(ScriptableMesh mesh, int part) { return {}; }
+    public slots:
+        ScriptableMeshPartPointer exportMeshPart(ScriptableMeshPointer mesh, int part=0) {
+            return ScriptableMeshPartPointer(new ScriptableMeshPart(mesh, part));
+        }
+        bool updateMeshPart(ScriptableMeshPointer mesh, ScriptableMeshPartPointer part);
     };
+
+    // callback helper that lets C++ method signatures remain simple (ie: taking a single callback argument) while
+    // still supporting extended Qt signal-like (scope, "methodName") and (scope, function(){}) "this" binding conventions
+    QScriptValue jsBindCallback(QScriptValue callback);
+
+    // derive a corresponding C++ class instance from the current script engine's thisObject
+    template <typename T> T this_qobject_cast(QScriptEngine* engine);
 }
 
 Q_DECLARE_METATYPE(scriptable::ScriptableMeshPointer)
 Q_DECLARE_METATYPE(QVector<scriptable::ScriptableMeshPointer>)
 Q_DECLARE_METATYPE(scriptable::ScriptableMeshPartPointer)
+Q_DECLARE_METATYPE(QVector<scriptable::ScriptableMeshPartPointer>)
 Q_DECLARE_METATYPE(scriptable::GraphicsScriptingInterface)
 
 // FIXME: MESHFACES: faces were supported in the original Model.* API -- are they still needed/used/useful for anything yet?
 #include <memory>
 
 namespace mesh {
-    using uint32 = quint32;
     class MeshFace;
     using MeshFaces = QVector<mesh::MeshFace>;
     class MeshFace {
     public:
         MeshFace() {}
-        MeshFace(QVector<mesh::uint32> vertexIndices) : vertexIndices(vertexIndices) {}
+        MeshFace(QVector<scriptable::uint32> vertexIndices) : vertexIndices(vertexIndices) {}
         ~MeshFace() {}
 
-        QVector<mesh::uint32> vertexIndices;
+        QVector<scriptable::uint32> vertexIndices;
         // TODO -- material...
     };
 };
 
 Q_DECLARE_METATYPE(mesh::MeshFace)
 Q_DECLARE_METATYPE(QVector<mesh::MeshFace>)
-Q_DECLARE_METATYPE(mesh::uint32)
-Q_DECLARE_METATYPE(QVector<mesh::uint32>)
+Q_DECLARE_METATYPE(scriptable::uint32)
+Q_DECLARE_METATYPE(QVector<scriptable::uint32>)
diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h
index 4ba5a993b1..97a73ddd61 100644
--- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h
+++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h
@@ -1,56 +1,24 @@
 #pragma once
 
-#include <glm/glm.hpp>
-#include <QtCore/QObject>
-#include <QtCore/QVector>
-#include <QtCore/QList>
-#include <QtCore/QVariant>
-#include <QtCore/QUuid>
-#include <QPointer>
-#include <memory>
+#include "Forward.h"
 
-#include <DependencyManager.h>
-
-namespace graphics {
-    class Mesh;
-}
-namespace gpu {
-    class BufferView;
-}
 class QScriptValue;
-
 namespace scriptable {
-    using Mesh = graphics::Mesh;
-    using MeshPointer = std::shared_ptr<scriptable::Mesh>;
-
-    class ScriptableModel;
-    using ScriptableModelPointer = QPointer<ScriptableModel>;
-    class ScriptableMesh;
-    using ScriptableMeshPointer = QPointer<ScriptableMesh>;
-
-    // abstract container for holding one or more scriptable meshes
-    class ScriptableModel : public QObject {
+    class ScriptableModel : public ScriptableModelBase {
         Q_OBJECT
     public:
-        QUuid objectID;
-        QVariantMap metadata;
-        QVector<scriptable::MeshPointer> meshes;
-
-        Q_PROPERTY(QVector<scriptable::ScriptableMeshPointer> meshes READ getMeshes)
         Q_PROPERTY(QUuid objectID MEMBER objectID CONSTANT)
         Q_PROPERTY(QVariantMap metadata MEMBER metadata CONSTANT)
-        Q_INVOKABLE QString toString() const;
+        Q_PROPERTY(uint32 numMeshes READ getNumMeshes)
+        Q_PROPERTY(QVector<scriptable::ScriptableMeshPointer> meshes READ getMeshes)
 
-        ScriptableModel(QObject* parent = nullptr) : QObject(parent) {}
-        ScriptableModel(const ScriptableModel& other) : objectID(other.objectID), metadata(other.metadata), meshes(other.meshes)  {}
-        ScriptableModel& operator=(const ScriptableModel& view) { objectID = view.objectID; metadata = view.metadata; meshes = view.meshes; return *this; }
-        ~ScriptableModel() { qDebug() << "~ScriptableModel" << this; }
-
-        void mixin(const ScriptableModel& other) {
-            for (const auto& key : other.metadata.keys()) { metadata[key] = other.metadata[key]; }
-            for (const auto& mesh : other.meshes) { meshes << mesh; }
-        }
+        ScriptableModel(QObject* parent = nullptr) : ScriptableModelBase(parent) {}
+        ScriptableModel(const ScriptableModel& other) : ScriptableModelBase(other) {}
+        ScriptableModel(const ScriptableModelBase& other) : ScriptableModelBase(other) {}
+        ScriptableModel& operator=(const ScriptableModelBase& view) {  ScriptableModelBase::operator=(view); return *this; }
+        virtual ~ScriptableModel() { qDebug() << "~ScriptableModel" << this; }
 
+        Q_INVOKABLE scriptable::ScriptableModelPointer cloneModel(const QVariantMap& options = QVariantMap());
         // TODO: in future accessors for these could go here
         // QVariantMap shapes;
         // QVariantMap materials;
@@ -58,28 +26,23 @@ namespace scriptable {
 
         QVector<scriptable::ScriptableMeshPointer> getMeshes();
         const QVector<scriptable::ScriptableMeshPointer> getConstMeshes() const;
+        operator scriptable::ScriptableModelBasePointer() {
+            QPointer<scriptable::ScriptableModelBase> p;
+            p = qobject_cast<scriptable::ScriptableModelBase*>(this);
+            return p;
+        }
+        
 
         // QScriptEngine-specific wrappers
-        Q_INVOKABLE QScriptValue mapAttributeValues(QScriptValue scopeOrCallback, QScriptValue methodOrName);
+        Q_INVOKABLE uint32 mapAttributeValues(QScriptValue callback);
+        Q_INVOKABLE QString toString() const;
+        Q_INVOKABLE uint32 getNumMeshes() { return meshes.size(); }
     };
 
-    // mixin class for Avatar/Entity/Overlay Rendering that expose their in-memory graphics::Meshes
-    class ModelProvider {
-    public:
-        QVariantMap metadata;
-        static scriptable::ScriptableModel modelUnavailableError(bool* ok) { if (ok) { *ok = false; } return {}; }
-        virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) = 0;
-    };
-    using ModelProviderPointer = std::shared_ptr<scriptable::ModelProvider>;
-
-    // mixin class for Application to resolve UUIDs into a corresponding ModelProvider
-    class ModelProviderFactory : public Dependency {
-    public:
-        virtual scriptable::ModelProviderPointer lookupModelProvider(QUuid uuid) = 0;
-    };
 }
 
 Q_DECLARE_METATYPE(scriptable::MeshPointer)
-Q_DECLARE_METATYPE(scriptable::ScriptableModel)
+Q_DECLARE_METATYPE(scriptable::WeakMeshPointer)
 Q_DECLARE_METATYPE(scriptable::ScriptableModelPointer)
-
+Q_DECLARE_METATYPE(scriptable::ScriptableModelBase)
+Q_DECLARE_METATYPE(scriptable::ScriptableModelBasePointer)