ChangeQueue::ApplyGroundPlaneChanges:how to get the groundplane material?

Hi,

I’m implementing a custom renderer / change queue and I’m trying to retrieve the actual material assigned by the user in the Ground Plane panel.

At the moment, I can access the ground plane and query its material ID, but I always end up with a default gray material instead of the user-selected one (for example a gold metal).

Here is what I currently do:

void ChangeQueue::ApplyGroundPlaneChanges(const GroundPlane& rhinoGroundPlane) const
{
  int materialIndex = -1;
  ON__UINT32 materialId = rhinoGroundPlane.MaterialId();

  if (materialId != 0)
  {
    if (const CRhRdkMaterial* rdkMaterial = MaterialFromId(materialId))
    {
#ifdef _DEBUG
      const ON_Material& simMat = rdkMaterial->SimulatedMaterial();
      char msg[512];
      sprintf_s(msg,
        "[GroundPlane] Material found via MaterialFromId(%u):\n"
        "  Diffuse RGB: (%d, %d, %d) -> (%.2f, %.2f, %.2f)\n",
        materialId,
        simMat.Diffuse().Red(),
        simMat.Diffuse().Green(),
        simMat.Diffuse().Blue(),
        simMat.Diffuse().FractionRed(),
        simMat.Diffuse().FractionGreen(),
        simMat.Diffuse().FractionBlue());
      OutputDebugStringA(msg);
#endif
    }
  }
}

The debug output always looks like this:

[GroundPlane] Material found via MaterialFromId(2279065610):
Diffuse RGB: (126, 126, 126) -> (0.49, 0.49, 0.49)

This appears to be the default gray, regardless of what material is actually selected in the Ground Plane UI.

From my understanding, the ground plane uses an RDK material instance, not a document material, and I suspect I’m resolving the material incorrectly.

What is the correct way to retrieve the actual RDK material assigned to the ground plane by the user?
Should this be resolved via MaterialInstanceId() and RDK APIs instead of a material ID?

This should work the way you have typed it. However a gold material may have a grey diffuse component….it is purely reflective.

Unfortunately that’s not the case, gold metal was just an example, it does not work for any material,plaster or plastic, I cannot get the material attributes, base color, etc

I think you’re getting a physically based ON_Material. Check to see if it returns “true” from “IsPhysicallyBased”.

If so, you can either access the params through the PhysicallyBased interface, or you can call PhysicallyBased().ToLegacy() on it (you’ll have to copy it so you can change it).

Could you please give me an example of how to use the PhysicallyBased interface in my case?
However, there must by some kind of mystery with the material of GroundPlane.

No matter what material I assign, it is always received as grey, and somehow the material I set for groundplane is never stored in the material table (m_material_table).

This debug output is from a red plastic cube and a yellow plaster grounplane. The yellow plaster is listed in the materials dropdown selection menu (Properties:Material), but it is not in the m_material_table (I tried to get the groundplane material from there):

[GroundPlane] Material found via MaterialFromId(2279065610):
  Diffuse RGB: (126, 126, 126) -> (0.49, 0.49, 0.49)

===== MATERIAL TABLE =====
MaterialCount        : 1
CurrentMaterialIndex : -1
CurrentMaterialId    : 00000000-0000-0000-0000-000000000000

[Material 0]
Name        : Plastic
Id          : 5f7141f2-d776-43e9-a585-df9bd9bbd39f
Diffuse RGB : ( 53, 223, 111)
Specular RGB: (217, 217, 217)
Transparency: 0.000
Shine       : 216.750
Reference   : false
Deleted     : false
Is Current  : no

This is my code:

	int materialIndex = -1;
	ON__UINT32 materialId = rhinoGroundPlane.MaterialId();

	if (materialId != 0)
	{
		if (const CRhRdkMaterial* rdkMaterial = MaterialFromId(materialId))
		{

#ifdef _DEBUG
const ON_Material& simMat = rdkMaterial->SimulatedMaterial();
char msg[512];
sprintf_s(msg,
“[GroundPlane] Material found via MaterialFromId(%u):\n”
"  Diffuse RGB: (%d, %d, %d) → (%.2f, %.2f, %.2f)\n",
materialId,
simMat.Diffuse().Red(),
simMat.Diffuse().Green(),
simMat.Diffuse().Blue(),
simMat.Diffuse().FractionRed(),
simMat.Diffuse().FractionGreen(),
simMat.Diffuse().FractionBlue());
OutputDebugStringA(msg);
#endif

#define DBG_PRINTA(fmt, …)                
{                                           
char _buf[1024];                          
sprintf_s(_buf, sizeof(_buf), fmt, VA_ARGS); 
OutputDebugStringA(_buf);                 
}

			const CRhinoMaterialTable& mt = that->_rhinoDoc.m_material_table;
			const int material_count = mt.MaterialCount();
			const int current_index = mt.CurrentMaterialIndex();
			const ON_UUID current_id = mt.CurrentMaterialId();

			wchar_t current_id_w[64]; // Increased buffer size for safety
			ON_UuidToString(current_id, current_id_w);
			char current_id_a[64];
			int result = WideCharToMultiByte(
				CP_ACP, 0,
				current_id_w, -1,
				current_id_a, sizeof(current_id_a),
				nullptr, nullptr
			);

			if (result == 0)
			{
				strcpy_s(current_id_a, sizeof(current_id_a), "<conversion failed>");
			}

			DBG_PRINTA(
				"\n===== MATERIAL TABLE =====\n"
				"MaterialCount        : %d\n"
				"CurrentMaterialIndex : %d\n"
				"CurrentMaterialId    : %s\n"
				"==========================\n\n",
				material_count,
				current_index,
				current_id_a
			);

			for (int i = 0; i < material_count; ++i)
			{
				const CRhinoMaterial& mat = mt[i];
				const bool is_deleted = mat.IsDeleted();
				const bool is_reference = mat.IsReferenceComponent();
				const ON_Material& on_mat = mat;
				ON_Color diffuse = on_mat.Diffuse();
				ON_Color specular = on_mat.Specular();

				wchar_t id_w[64]; // Increased buffer
				ON_UuidToString(mat.Id(), id_w);
				char id_a[64];
				result = WideCharToMultiByte(
					CP_ACP, 0,
					id_w, -1,
					id_a, sizeof(id_a),
					nullptr, nullptr
				);

				if (result == 0)
				{
					strcpy_s(id_a, sizeof(id_a), "<conversion failed>");
				}

				const bool is_current =
					(i == current_index) || (mat.Id() == current_id);

				DBG_PRINTA(
					"[Material %d]%s%s\n"
					"  Name        : %ls\n"
					"  Id          : %s\n"
					"  Diffuse RGB : (%3d, %3d, %3d)\n"
					"  Specular RGB: (%3d, %3d, %3d)\n"
					"  Transparency: %.3f\n"
					"  Shine       : %.3f\n"
					"  Reference   : %s\n"
					"  Deleted     : %s\n"
					"  Is Current  : %s\n\n",
					i,
					is_deleted ? " (DELETED)" : "",
					is_reference ? " (REFERENCE)" : "",
					static_cast<const wchar_t*>(mat.Name()), // Fixed: explicit cast
					id_a,
					diffuse.Red(), diffuse.Green(), diffuse.Blue(),
					specular.Red(), specular.Green(), specular.Blue(),
					on_mat.Transparency(),
					on_mat.Shine(),
					is_reference ? "true" : "false",
					is_deleted ? "true" : "false",
					is_current ? "YES" : "no"
				);
			}

		}
	}


Hi there!

I’m afraid I can’t repeat anything like that here.

Could you send me a complete C++ project with a sample plug-in that demonstrates this, along with a 3dm file and instructions to repeat?

I assume that you are writing a renderer integration? Have you returned “true” to the relevant “SupportsFeature” calls? Specifically for Materials and Ground Plane?

  • Andy

The “mystery” you’re encountering is a common hurdle in Rhino RDK development. The Ground Plane—and most modern Rhino materials—uses RDK Materials (Render Contents), which are distinct from the legacy ON_Material entries stored in the m_material_table.

The m_material_table is a collection of “Attributes” materials. When you assign a material to the Ground Plane, Rhino creates a CRhRdkMaterial. These live in the RDK Document’s content list, not the document’s global material table. This explains why your loop through m_material_table only shows the Plastic cube (likely a basic Rhino material) and ignores the Ground Plane’s Plaster.

To get the “real” data (like Base Color, Metallic, or Roughness), you shouldn’t rely on SimulatedMaterial(). The simulation is a “dumbed-down” version for the viewport, which is why it’s returning a default grey (0.49, 0.49, 0.49).

Instead, you need to query the ON_PhysicallyBasedMaterial interface from the RDK material. For example, by calling pMaterial→PhysicallyBased()→BaseColor()

Thanks for your answer! indeed, after doing a lot of attempts to get the materialId, I realized that no matter the material changes I was applying on the ground plane, I was receiving the same meaningless ID. So I ended up receiving the actual ground plane object from the document:

const IRhRdkGroundPlane& rdkGroundPlane = that->_rhinoDoc.GroundPlane();
const CRhRdkMaterial* rdkMaterial = rdkGroundPlane.Material();

which then made me implement a whole new material manager class (with attribute-based hash key extractor) in order to integrate the ground plane material with the rest of the materials of the scene. Just to note, the rest of the materials of the scene were using just the materialId for identification, ==operator, etc.

I am not sure if this is the proper handling of materials while integrating a renderer into rhino, but now I can manage all materials together. Please let me know if another more rhino way is applicable for this case.