Creating a Model Explosion Operation in HOOPS Visualize

A common operation that is useful in the CAD visualization workflow is to provide the ability to explode a model, enabling the user to see each individual part. There are many ways to architect a solution that supports this concept. One way is to instance the model parts so that you can manipulate them without changing anything to the underlying model. This allows you to swap out the exploded model and the original model quickly with the exploded instances simply being shown and hidden when desired.

To help provide a way to isolate this functionality, in collaboration with Chris Pillion, we have put together example explode code into an explosion manager (below) where you can simply pass the component root that you went to explode with a center of explosion. This is not production tested code, but something to provide guidance and/or an example on how an explode operator could work. We implemented an explode slider scale also that on update pushes a change to scale using “UpdateExplodeScale”, but that code is not fully shown in the example code below.

Reach out if you are interested in the full source associated with this explode code or similar functionality for viewing your CAD models.

How to call the explode manager (this example was used in the MfcSandbox sample code provided with the HOOPS Visualize installation package):

void CHPSView::OnUserCode3()
{
	GetCanvas().GetFrontView().GetAxisTriadControl().SetVisibility(true);

	// Can only run this code if a file is loaded and a CAD Model Exists.
	auto hpsDoc = GetCHPSDoc();
	if (hpsDoc != NULL) {
		auto cadModel = hpsDoc->GetCADModel();
		if (!cadModel.Empty()) {
			auto riComponents = cadModel.GetAllSubcomponents(HPS::Exchange::Component::ComponentType::ExchangeRepresentationItemMask);

			
			// Calculate Model Bounding and center of explode
			HPS::Point center_of_explode = HPS::Point(0, 0, 0);
			double explodeCenterSphereRadius = 1;
			HPS::SimpleCuboid bounding_cuboid;
			HPS::SimpleSphere bounding_sphere;
			HPS::KeyPathArray keyPathArray = HPS::Component::GetKeyPath(cadModel);
			HPS::KeyPath keyPath = keyPathArray.front();
			HPS::BoundingKit bounding_kit;
			if (keyPath.Size() != 0 && keyPath.ShowNetBounding(bounding_kit) && bounding_kit.ShowVolume(bounding_sphere, bounding_cuboid))
			{
				center_of_explode = bounding_sphere.center;
			}

			// Perform Explode
			m_explodeManager.SetModelSegmentKey(GetCHPSDoc()->GetCADModel().GetModel().GetSegmentKey());
			auto explodeSlider = GetCHPSDoc()->GetCHPSFrame()->GetExplodeSlider();
			auto scale = (float)explodeSlider->GetPos() / (explodeSlider->GetRangeMax() - explodeSlider->GetRangeMin());
			m_explodeManager.ExplodeChildren(riComponents, center_of_explode, scale);

			GetCanvas().Update();

		}
	}
}


void CHPSView::OnUserCode4()
{
	m_explodeManager.RemoveExplodedModel();
	GetCanvas().Update();
}

void CHPSView::UpdateExplodeScale() {
	auto ribbonSlider = GetCHPSDoc()->GetCHPSFrame()->GetExplodeSlider();
	auto scale = (float)ribbonSlider->GetPos() / (ribbonSlider->GetRangeMax() - ribbonSlider->GetRangeMin());

	auto hpsDoc = GetCHPSDoc();
	if (hpsDoc != NULL) {
		auto cadModel = hpsDoc->GetCADModel();
		if (!cadModel.Empty()) {
			auto riComponents = cadModel.GetAllSubcomponents(HPS::Exchange::Component::ComponentType::ExchangeRepresentationItemMask);

			m_explodeManager.UpdateScale(riComponents, scale);
			GetCanvas().Update();
		}
	}
}

ExplodeManager.cpp:

#include "stdafx.h"
#include "ExplodeManager.h"

//-------------------------------------------------------------------------------------------------------------------------
// 
// This is required to set the root key of the model so the explode operator can store the model while it is operating on an exploded version
// The original model is stored so we don't create any changes to the underlying model and can fully restore the model as needed.
// 
// This must be called before ExplodeChildren is call and should be updated if a new model is loaded.
// 
//-------------------------------------------------------------------------------------------------------------------------

void ExplodeManager::SetModelSegmentKey(HPS::SegmentKey modelSegmentKey) {
	m_modelSegmentKey = modelSegmentKey;
}

//-------------------------------------------------------------------------------------------------------------------------
// This code simply removes the old exploded model and replaces it with one that is scaled at the new scale.
// This code was not designed for efficient updating of scale, but to provide a concept if you want to provide multiple explode levels.
//-------------------------------------------------------------------------------------------------------------------------

void ExplodeManager::UpdateScale(HPS::ComponentArray& riComponents, float scale) {
	if (!m_explodedModelSegmentKey.Empty()) {
		RemoveExplodedModel();
		ExplodeChildren(riComponents, m_last_explode_center, scale);
	}
}

//-------------------------------------------------------------------------------------------------------------------------
// 
// The explode children function creates instances of all the items with-in the model that are exploded to the scale as desired from the center of the explode_center
// The original model is stored in full capacity in the storageSegment so that it can be recalled without any manipulation.
// 
//-------------------------------------------------------------------------------------------------------------------------

void ExplodeManager::ExplodeChildren(HPS::ComponentArray& riComponents, HPS::Point explode_center, float scale) {
	m_last_explode_center = explode_center; // Store last explode center so we can update the scale with the last known explode center.

	if (!m_modelSegmentKey.Empty() && m_explodedModelSegmentKey.Empty()) {
		if (m_modelStorageSegment.Empty()) {
			m_modelStorageSegment = HPS::Database::CreateRootSegment();
			m_modelStorageSegment.SetName(m_storageSegmentName.c_str());
		}

		if (!m_explodedModelSegmentKey.Empty()) m_explodedModelSegmentKey.Delete();

		m_explodedModelSegmentKey = HPS::Database::CreateRootSegment();
		m_explodedModelSegmentKey.SetName(m_explodeSegmentName.c_str());

		// TODO: Add code to check if this already segment and all its children exists - if so, just update the component matrices
		

		scale = scale * 10;

		for (auto riComponent : riComponents) {
			HPS::KeyPathArray keyPathArray = HPS::Component::GetKeyPath(riComponent);

			auto nthInstance = 0;
			for (auto keyPath : keyPathArray) {

				if (keyPath.Size() <= 0) {
					continue;
				}

				// Need to append additional info to avoid returning the existing segment (if instanced) - Using a simple keypath counter.
				auto riComponentName = riComponent.GetName();
				auto childExplodeSegment = m_explodedModelSegmentKey.Subsegment(riComponentName + "_Instance_" + nthInstance);

				HPS::MatrixKit componentDefaultMatrix;
				HPS::MaterialMappingKit componentDefaultMaterial;
				keyPath.ShowNetModellingMatrix(componentDefaultMatrix);
				keyPath.ShowNetMaterialMapping(componentDefaultMaterial);

				HPS::SimpleCuboid bounding_cuboid;
				HPS::SimpleSphere bounding_sphere;
				HPS::BoundingKit bounding_kit;

				if (keyPath.ShowNetBounding(false, bounding_kit) && bounding_kit.ShowVolume(bounding_sphere, bounding_cuboid))
				{
					// Scale the explosion to the distance of desire.
					auto scaled_explode_world_vector = (bounding_sphere.center - explode_center).Scale(HPS::Vector(scale, scale, scale));

					// Create a translation matrix kit from the explode vector.
					HPS::MatrixKit updateMatrix = componentDefaultMatrix.Translate(scaled_explode_world_vector);

					auto refKey = m_explodedModelSegmentKey.ReferenceGeometry(keyPath.Front());
					refKey.SetMaterialMapping(componentDefaultMaterial);
					refKey.SetModellingMatrix(updateMatrix);
				}
				nthInstance++;
			}

		}

		auto modelIncludes = GatherIncludes(m_modelSegmentKey);

		for (auto includeKey : modelIncludes) {
			auto segmentName = includeKey.GetTarget().Name();
			if (segmentName == m_explodeSegmentName.c_str()) {
				continue;
			}
			else {
				includeKey.MoveTo(m_modelStorageSegment);
			}
		}
		auto explodeModelIncludeKey = m_modelSegmentKey.IncludeSegment(m_explodedModelSegmentKey);
	}
}

//-------------------------------------------------------------------------------------------------------------------------
// This is a helper function to gather the children includes of the model to save for recall
//-------------------------------------------------------------------------------------------------------------------------
HPS::IncludeKeyArray ExplodeManager::GatherIncludes(HPS::SegmentKey searchSegmentRoot) {
	HPS::SearchResults segmentSearchResults;
	size_t numResults = searchSegmentRoot.Find(HPS::Search::Type::Include, HPS::Search::Space::SegmentOnly, segmentSearchResults);

	HPS::IncludeKeyArray segmentIncludes;

	HPS::SearchResultsIterator it = segmentSearchResults.GetIterator();
	while (it.IsValid())
	{
		HPS::Key key = it.GetItem();

		if (key.Type() == HPS::Type::IncludeKey)
		{
			segmentIncludes.push_back((HPS::IncludeKey)key);
		}
		it.Next();
	}
	return segmentIncludes;
}

//-------------------------------------------------------------------------------------------------------------------------
//
// This simply deletes the exploded model by segment key and restores the stored model back into the base model segment
//
//-------------------------------------------------------------------------------------------------------------------------

void ExplodeManager::RemoveExplodedModel() {
	if (!m_modelSegmentKey.Empty() && !m_modelStorageSegment.Empty()) {
		// Reset exploded model to original model from the stored includes.
		auto storageIncludes = GatherIncludes(m_modelStorageSegment);

		for (auto includeKey : storageIncludes) {
			auto segmentName = includeKey.GetTarget().Name();
			if (segmentName != m_explodeSegmentName.c_str()) {
				includeKey.MoveTo(m_modelSegmentKey);
				//includeKey.Delete();
			}
		}
		m_modelStorageSegment.Delete();
		m_explodedModelSegmentKey.Delete();
	}
}

Explode Manager.h

#pragma once

#include "hps.h"
#include "sprk.h"

//-------------------------------------------------------------------------------------------------------------------------
//
// A manage to handle the necessary compoents of an exploded model for viewing and restoring.
// 
//-------------------------------------------------------------------------------------------------------------------------
class ExplodeManager
{
private:
	HPS::SegmentKey		m_modelSegmentKey;
	HPS::SegmentKey		m_modelStorageSegment;
	HPS::SegmentKey		m_explodedModelSegmentKey;
	const std::string	m_explodeSegmentName = "ExplodeModel";
	const std::string	m_storageSegmentName = "StorageSegment";
	HPS::Point			m_last_explode_center;

public:
	void				 SetModelSegmentKey(HPS::SegmentKey modelSegmentKey);
	void				 UpdateScale(HPS::ComponentArray& riComponents, float scale);
	void				 ExplodeChildren(HPS::ComponentArray& subComponents, HPS::Point explode_center, float scale /* as a decimal percentage between 0 and 1*/);
	HPS::IncludeKeyArray GatherIncludes(HPS::SegmentKey searchSegmentRoot);
	void				 RemoveExplodedModel();
};
4 Likes