Inquiry on Multi-Model Loading with SC Server Setup

Hello TechSoft3D Team,

I have a question regarding the Model Loading and Aggregation section in your documentation.

In the example provided for multi-model loading, the combined models are loaded from local files. However, in our setup, we are using the SC Server and client architecture, where models are loaded via a WebSocket connection.

Currently, we are able to load only one model at a time. Could you please guide us on how to implement multi-model loading using the SC Server setup?

Looking forward to your guidance.

It’s unclear what you mean by “local files.” In the file HOOPS_Communicator_2025.3.0\quick_start\server_config.js, there are two parameters which control where files are loaded from. These parameters, with their default values, are:

modelDirs: [
        "./quick_start/converted_models/user/sc_models",
        "./quick_start/converted_models/authoring_samples_data",
        "./quick_start/converted_models/standard/sc_models",
    ],

and:

fileServerStaticDirs: [
        "./web_viewer",
        "./web_viewer/examples",
        "./web_viewer/demo-app",
        "./quick_start/converted_models/user/scs_models",
        "./quick_start/converted_models/standard",
        "./quick_start/converted_models/standard/scs_models",
        "./quick_start"
    ],

Worth noting is that modelDirs is for Stream Cache files (SC/SCZ). And fileServerStaticDirs is for SCS (the file-based variant of SC/SCZ). When you aggregate models by calling either loadSubtree* or loadSubtreeFromScs*, the models are being loaded from the directories referenced above.

Based on your description of only being able to load models one at a time, this sounds like the model when the Web Viewer (hwv) object is first intitialized/started and controlled by the parameter WebViewerConfig.model.

Client Code -

  const viewer = new WebViewer({
    containerId: divId,
    endpointUri,
    model: model?.modelName,
    rendererType: renererType,
  });

Server Code -

export function spawnViewer(project: string, model: string, res: any): void {
const modelDir = ${modelDirectory}/${project};
const spawnId = uuidv4();
const wsPort = allocateWsPort();
const args = [
“–id”,
spawnId,
“–sc-port”,
wsPort.toString(),
“–liveliness-endpoint”,
http://${host}:${port}/liveliness,
“–model-search-directories”,
modelDir,
“–license”,
license,
“–initial-use-duration”,
“0”,
];

console.log(host, port, wsPort, modelDir, license);

const options = { env: { DISPLAY: “:0” } };
let process: ChildProcess;

if (renderType === “ssr”) {
args.push(“–ssr”, “true”);
}

try {
process = spawn(executablePath, args, options);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
console.error([ERROR] Failed to spawn viewer: ${message});
res.status(500).json({ error: “Failed to start viewer” });
return;
}

const spawnInfo: SpawnInfo = {
spawnId,
wsPort,
rendererType: renderType === “csr” ? “csr” : “ssr”,
response: res,
startTime: new Date(),
process,
modelDir,
modelName: model,
};

spawnArray.push(spawnInfo);
setupProcessListeners(spawnInfo);
}

// Handles liveliness updates (ready, disconnect, ping) from a viewer process.
export function handleLiveliness(
spawnId: string,
updateType: string,
res: any,
wsHost: string,
): void {
const spawnInfo = spawnArray.find((e) => e.spawnId === spawnId);
if (!spawnInfo) {
res.status(404).json({ error: No active spawn with ID ${spawnId} });
return;
}

switch (updateType) {
case “ready”:
console.log(“ready”);
spawnInfo.response?.status(201).json({
endpoint: ws://${wsHost}:${spawnInfo.wsPort},
wsPort: spawnInfo.wsPort,
rendererType: spawnInfo.rendererType,
spawnId,
modelName: spawnInfo.modelName,
});
spawnInfo.response = null;
break;
case “disconnect”:
console.log(“disconnect”);
cleanupSpawn(spawnId, spawnInfo.wsPort);
res.status(200).end();
break;
case “ping”:
console.log(“ping”);
res.status(200).json({ message: Ping received for ${spawnId} });
break;
default:
console.log(“default”);
cleanupSpawn(spawnId, spawnInfo.wsPort);
res.status(400).json({ error: “Invalid liveliness value” });
}
}

This is the current implementation of the Client Viewer WebSocket connection, which is set up to load only a single model at a time. How can I modify this to support multi-model loading, as demonstrated in the Model Loading and Aggregation section of your example code?

Quite crucial to aggregating models are calls to loadSubtreeFrom* and its different variants. In your reference to our docs, this is covered in the section Loading two models.

There is also an example of model assembly in the quickstart module of the Communicator package. To view this example, please do the following:

If you want to load models after the Web Viewer object starts, you can make the necessary calls to loadSubtreeFrom* from the modelStructureReady callback.