diff --git a/cpp/SampleCommands/SampleCommands.vcxproj b/cpp/SampleCommands/SampleCommands.vcxproj
index 141b33ca..af70d4d9 100644
--- a/cpp/SampleCommands/SampleCommands.vcxproj
+++ b/cpp/SampleCommands/SampleCommands.vcxproj
@@ -17,6 +17,7 @@
+
diff --git a/cpp/SampleCommands/SampleCommands.vcxproj.filters b/cpp/SampleCommands/SampleCommands.vcxproj.filters
index 0abc5702..ce136dae 100644
--- a/cpp/SampleCommands/SampleCommands.vcxproj.filters
+++ b/cpp/SampleCommands/SampleCommands.vcxproj.filters
@@ -852,6 +852,9 @@
Source Files
+
+ Source Files
+
diff --git a/cpp/SampleCommands/cmdSampleCsCachedTextureCoordinates.cpp b/cpp/SampleCommands/cmdSampleCsCachedTextureCoordinates.cpp
new file mode 100644
index 00000000..c1a1ceec
--- /dev/null
+++ b/cpp/SampleCommands/cmdSampleCsCachedTextureCoordinates.cpp
@@ -0,0 +1,154 @@
+#include "stdafx.h"
+
+static ON_wString TextureTypeString(const ON_Texture::TYPE type)
+{
+ switch (type)
+ {
+ case ON_Texture::TYPE::no_texture_type: return L"no_texture_type";
+ case ON_Texture::TYPE::bump_texture: return L"bump_texture";
+ case ON_Texture::TYPE::transparency_texture: return L"transparency_texture";
+ case ON_Texture::TYPE::pbr_base_color_texture: return L"pbr_base_color_texture";
+ case ON_Texture::TYPE::pbr_subsurface_texture: return L"pbr_subsurface_texture";
+ case ON_Texture::TYPE::pbr_subsurface_scattering_color_texture: return L"pbr_subsurface_scattering_color_texture";
+ case ON_Texture::TYPE::pbr_subsurface_scattering_radius_texture: return L"pbr_subsurface_scattering_radius_texture";
+ case ON_Texture::TYPE::pbr_metallic_texture: return L"pbr_metallic_texture";
+ case ON_Texture::TYPE::pbr_specular_texture: return L"pbr_specular_texture";
+ case ON_Texture::TYPE::pbr_specular_tint_texture: return L"pbr_specular_tint_texture";
+ case ON_Texture::TYPE::pbr_roughness_texture: return L"pbr_roughness_texture";
+ case ON_Texture::TYPE::pbr_anisotropic_texture: return L"pbr_anisotropic_texture";
+ case ON_Texture::TYPE::pbr_anisotropic_rotation_texture: return L"pbr_anisotropic_rotation_texture";
+ case ON_Texture::TYPE::pbr_sheen_texture: return L"pbr_sheen_texture";
+ case ON_Texture::TYPE::pbr_sheen_tint_texture: return L"pbr_sheen_tint_texture";
+ case ON_Texture::TYPE::pbr_clearcoat_texture: return L"pbr_clearcoat_texture";
+ case ON_Texture::TYPE::pbr_clearcoat_roughness_texture: return L"pbr_clearcoat_roughness_texture";
+ case ON_Texture::TYPE::pbr_opacity_ior_texture: return L"pbr_opacity_ior_texture";
+ case ON_Texture::TYPE::pbr_opacity_roughness_texture: return L"pbr_opacity_roughness_texture";
+ case ON_Texture::TYPE::pbr_emission_texture: return L"pbr_emission_texture";
+ case ON_Texture::TYPE::pbr_ambient_occlusion_texture: return L"pbr_ambient_occlusion_texture";
+ case ON_Texture::TYPE::pbr_displacement_texture: return L"pbr_displacement_texture";
+ case ON_Texture::TYPE::pbr_clearcoat_bump_texture: return L"pbr_clearcoat_bump_texture";
+ case ON_Texture::TYPE::pbr_alpha_texture: return L"pbr_alpha_texture";
+ case ON_Texture::TYPE::emap_texture: return L"";
+ default: return L"unknown_type";
+ }
+}
+
+
+
+class CCommandSampleCachedTextureCoordinates : public CRhinoCommand
+{
+public:
+ CCommandSampleCachedTextureCoordinates() = default;
+ ~CCommandSampleCachedTextureCoordinates() = default;
+ UUID CommandUUID() override
+ {
+ // {E7C7DF80-FCC8-468B-BC73-63EBE6081941}
+ static const GUID SampleCachedTextureCoordinatesCommand_UUID =
+ { 0xe7c7df80, 0xfcc8, 0x468b, { 0xbc, 0x73, 0x63, 0xeb, 0xe6, 0x8, 0x19, 0x41 } };
+ return SampleCachedTextureCoordinatesCommand_UUID;
+ }
+ const wchar_t* EnglishCommandName() override { return L"SampleCachedTextureCoordinates"; }
+ CRhinoCommand::result RunCommand(const CRhinoCommandContext& context) override;
+};
+
+// The one and only CCommandSampleDrawString object
+static class CCommandSampleCachedTextureCoordinates theSampleCachedTextureCoordinatesCommand;
+
+CRhinoCommand::result CCommandSampleCachedTextureCoordinates::RunCommand(const CRhinoCommandContext& context)
+{
+ const CRhinoDoc* pDoc = context.Document();
+ if (nullptr == pDoc)
+ return CRhinoCommand::failure;
+
+ CRhinoGetObject go;
+ go.SetCommandPrompt(L"Select objects");
+ CRhinoGet::result res = go.GetObjects(1, 0);
+ if (res != CRhinoGet::result::object)
+ return go.CommandResult();
+
+ for (int i = 0; i < go.ObjectCount(); i++)
+ {
+ const CRhinoObjRef& rhinoObjectRef = go.Object(i);
+ const CRhinoObject* pRhinoObject = rhinoObjectRef.Object();
+ if (nullptr == pRhinoObject)
+ continue;
+
+ // Get the render meshes from the object
+ ON_SimpleArray meshes;
+ pRhinoObject->GetMeshes(ON::mesh_type::render_mesh, meshes);
+
+ // If there are no render meshes, create them and get them again
+ if (meshes.Count() == 0)
+ {
+ ON_MeshParameters mp;
+ pRhinoObject->GetRenderMeshParameters(mp);
+ const_cast(pRhinoObject)->CreateMeshes(ON::mesh_type::render_mesh, mp, false);
+ meshes = pRhinoObject->GetMeshes(ON::mesh_type::render_mesh, meshes);
+ if (meshes.Count() == 0)
+ {
+ continue;
+ }
+ }
+
+ ON_wString sObjectId;
+ ON_UuidToString(pRhinoObject->Id(), sObjectId);
+
+ RhinoApp().Print(L"Object %s with %u meshes\n", (const wchar_t*)sObjectId, meshes.UnsignedCount());
+
+ // Get the mapping ref for the object. Mapping ref is needed to access object's texture mappings.
+ // Mapping ref may be null if there are no mappings but cached texture coordinates methods will handle this case.
+ // To get a mapping ref for a specific render plug-in pass in its id.
+ const ON_MappingRef* pMappingRef = pRhinoObject->Attributes().m_rendering_attributes.MappingRef(ON_nil_uuid);
+
+ // Iterate through the meshes, each polysurface face has a corresponding mesh
+ for (int mi = 0; mi < meshes.Count(); mi++)
+ {
+ const ON_Mesh* pMesh = meshes[mi];
+ if (nullptr == pMesh)
+ continue;
+
+ // Figure out what is the component type of this object's subobjects
+ ON_COMPONENT_INDEX::TYPE subMaterialComponentType = ON_COMPONENT_INDEX::TYPE::invalid_type;
+ if (pRhinoObject->ObjectType() == ON::object_type::subd_object)
+ subMaterialComponentType = ON_COMPONENT_INDEX::TYPE::subd_face;
+ else if (pRhinoObject->ObjectType() == ON::object_type::brep_object)
+ subMaterialComponentType = ON_COMPONENT_INDEX::TYPE::brep_face;
+ else if (pRhinoObject->ObjectType() == ON::object_type::extrusion_object)
+ subMaterialComponentType = ON_COMPONENT_INDEX::TYPE::brep_face;
+
+ // Get object material. If the current subobject uses a subobject material then it is returned.
+ ON_COMPONENT_INDEX ci(subMaterialComponentType, mi);
+ const CRhinoMaterial& meshMaterial = pRhinoObject->ObjectMaterial(ci);
+
+ ON_wString sMaterialId;
+ ON_UuidToString(meshMaterial.Id(), sMaterialId);
+ RhinoApp().Print(L" Mesh %u material %s\n", mi, (const wchar_t*)sMaterialId);
+
+ // Set up cached texture coordinates based on the material texture channels.
+ pMesh->SetCachedTextureCoordinatesFromMaterial(*pDoc, meshMaterial, pMappingRef);
+
+ // Get all the textures used by the material
+ for (int ti = 0; ti < meshMaterial.m_textures.Count(); ti++)
+ {
+ const ON_Texture& texture = meshMaterial.m_textures[ti];
+
+ // Get the cached texture coordinates for this texture
+ const ON_TextureCoordinates* pCachedTCs = pMesh->GetCachedTextureCoordinates(*pDoc, texture, pMappingRef);
+ if (nullptr != pCachedTCs && pCachedTCs->m_T.Count() == pMesh->VertexCount())
+ {
+ ON_wString sTextureType = TextureTypeString(texture.m_type);
+ RhinoApp().Print(L" Cached texture coordinates for %s texture. ", (const wchar_t*)sTextureType);
+ if (texture.IsWcsProjected())
+ RhinoApp().Print(L"Wcs projection used.");
+ else if (texture.IsWcsBoxProjected())
+ RhinoApp().Print(L"Wcs box projection used.");
+ else if (texture.m_mapping_channel_id > 0)
+ RhinoApp().Print(L"Mapping channel %d used.", texture.m_mapping_channel_id);
+ RhinoApp().Print(L"\n");
+ }
+ }
+ }
+ }
+ return CRhinoCommand::success;
+}
+