ModelHandle/source/website/navigatormeshespanel.js
sladro 16a2e43649
Some checks failed
Build / build (18.x, macos-latest) (push) Has been cancelled
Build / build (18.x, ubuntu-latest) (push) Has been cancelled
Build / build (18.x, windows-latest) (push) Has been cancelled
Build / build (20.x, macos-latest) (push) Has been cancelled
Build / build (20.x, ubuntu-latest) (push) Has been cancelled
Build / build (20.x, windows-latest) (push) Has been cancelled
fix step save flow and add portable backend runtime support
2026-04-13 15:16:30 +08:00

539 lines
18 KiB
JavaScript

import { MeshInstanceId } from '../engine/model/meshinstance.js';
import { AddDiv, CreateDiv, ShowDomElement, ClearDomElement, InsertDomElementBefore, SetDomElementHeight, GetDomElementOuterHeight, IsDomElementVisible, AddButtonElement } from '../engine/viewer/domutils.js';
import { CalculatePopupPositionToElementBottomRight, ShowListPopup } from './dialogs.js';
import { MeshItem, NavigatorItemRecurse, NodeItem } from './navigatoritems.js';
import { NavigatorPanel, NavigatorPopupButton } from './navigatorpanel.js';
import { AddSvgIconElement, GetMaterialName, GetMeshName, GetNodeName, SetElementAccessibleName, SetSvgIconImageElement } from './utils.js';
import { Loc, FLoc } from '../engine/core/localization.js';
const MeshesPanelMode =
{
Simple : 0,
FlatList : 1,
TreeView : 2
};
class NavigatorMaterialsPopupButton extends NavigatorPopupButton
{
constructor (parentDiv)
{
super (parentDiv);
this.materialInfoArray = null;
}
Update (materialInfoArray)
{
this.materialInfoArray = materialInfoArray;
if (this.materialInfoArray === null) {
return;
}
let materialsText = FLoc ('Materials ({0})', this.materialInfoArray.length);
this.buttonText.innerHTML = materialsText;
SetElementAccessibleName (this.button, materialsText);
}
OnButtonClick ()
{
if (this.materialInfoArray === null) {
return;
}
let materialItems = [];
for (let i = 0; i < this.materialInfoArray.length; i++) {
let usedMaterial = this.materialInfoArray[i];
materialItems.push ({
name : GetMaterialName (usedMaterial.name),
color : usedMaterial.color
});
}
if (materialItems.length === 0) {
return;
}
this.popup = ShowListPopup (materialItems, {
calculatePosition : (contentDiv) => {
return CalculatePopupPositionToElementBottomRight (this.button, contentDiv);
},
onClick : (index) => {
let usedMaterial = this.materialInfoArray[index];
this.callbacks.onMaterialSelected (usedMaterial.index);
}
});
}
}
export class NavigatorMeshesPanel extends NavigatorPanel
{
constructor (parentDiv)
{
super (parentDiv);
this.callbacks = null;
this.stepDeletionState = null;
this.nodeIdToItem = new Map ();
this.meshInstanceIdToItem = new Map ();
this.rootItem = null;
this.mode = MeshesPanelMode.Simple;
this.buttons = null;
this.treeView.AddClass ('tight');
this.titleButtonsDiv = AddDiv (this.titleDiv, 'ov_navigator_tree_title_buttons');
this.buttonsDiv = CreateDiv ('ov_navigator_buttons');
InsertDomElementBefore (this.buttonsDiv, this.treeDiv);
this.popupDiv = AddDiv (this.panelDiv, 'ov_navigator_info_panel');
this.materialsButton = new NavigatorMaterialsPopupButton (this.popupDiv);
}
GetName ()
{
return Loc ('Meshes');
}
GetIcon ()
{
return 'meshes';
}
Resize ()
{
let titleHeight = GetDomElementOuterHeight (this.titleDiv);
let buttonsHeight = 0;
if (IsDomElementVisible (this.buttonsDiv)) {
buttonsHeight = GetDomElementOuterHeight (this.buttonsDiv);
}
let popupHeight = GetDomElementOuterHeight (this.popupDiv);
let height = this.parentDiv.offsetHeight;
SetDomElementHeight (this.treeDiv, height - titleHeight - buttonsHeight - popupHeight);
}
Clear ()
{
this.ClearMeshTree ();
ClearDomElement (this.titleButtonsDiv);
ClearDomElement (this.buttonsDiv);
this.buttons = null;
}
ClearMeshTree ()
{
super.Clear ();
this.materialsButton.Clear ();
this.nodeIdToItem = new Map ();
this.meshInstanceIdToItem = new Map ();
this.rootItem = null;
}
Init (callbacks)
{
super.Init (callbacks);
this.materialsButton.Init ({
onMeshHover : (meshInstanceId) => {
this.callbacks.onMeshTemporarySelected (meshInstanceId);
},
onMeshSelected : (meshInstanceId) => {
this.callbacks.onMeshSelected (meshInstanceId);
},
onMaterialSelected : (materialIndex) => {
this.callbacks.onMaterialSelected (materialIndex);
}
});
}
Fill (importResult)
{
super.Fill (importResult);
this.stepDeletionState = importResult.stepDeletionState || null;
const rootNode = importResult.model.GetRootNode ();
let isHierarchical = false;
for (let childNode of rootNode.GetChildNodes ()) {
if (childNode.ChildNodeCount () > 0 || childNode.MeshIndexCount () > 1) {
isHierarchical = true;
break;
}
}
if (this.mode === MeshesPanelMode.Simple && isHierarchical) {
this.mode = MeshesPanelMode.TreeView;
} else if (this.mode !== MeshesPanelMode.Simple && !isHierarchical) {
this.mode = MeshesPanelMode.Simple;
}
this.FillButtons (importResult);
if (this.mode === MeshesPanelMode.Simple) {
ShowDomElement (this.buttonsDiv, false);
this.titleDiv.classList.add ('withbuttons');
this.titleDiv.classList.remove ('nomargin');
} else {
ShowDomElement (this.buttonsDiv, true);
this.titleDiv.classList.remove ('withbuttons');
this.titleDiv.classList.add ('nomargin');
}
this.FillMeshTree (importResult.model);
this.Resize ();
}
FillButtons (importResult)
{
function CreateButton (parentDiv, button, className, onClick)
{
button.div = AddButtonElement (parentDiv, 'ov_navigator_button');
SetElementAccessibleName (button.div, button.name);
if (className) {
button.div.classList.add (className);
}
button.iconDiv = AddSvgIconElement (button.div, button.icon);
button.div.addEventListener ('click', () => {
onClick ();
});
}
function UpdateButtonsStatus (buttons, mode)
{
let showTree = (mode === MeshesPanelMode.TreeView);
if (showTree) {
buttons.flatList.iconDiv.classList.remove ('selected');
buttons.treeView.iconDiv.classList.add ('selected');
} else {
buttons.flatList.iconDiv.classList.add ('selected');
buttons.treeView.iconDiv.classList.remove ('selected');
}
ShowDomElement (buttons.separator, showTree);
ShowDomElement (buttons.expandAll.div, showTree);
ShowDomElement (buttons.collapseAll.div, showTree);
}
function UpdateView (panel, importResult)
{
let hiddenMeshInstanceIds = [];
panel.EnumerateMeshItems ((meshItem) => {
if (!meshItem.IsVisible ()) {
hiddenMeshInstanceIds.push (meshItem.GetMeshInstanceId ());
}
return true;
});
panel.ClearMeshTree ();
panel.FillMeshTree (importResult.model);
for (let meshInstanceId of hiddenMeshInstanceIds) {
let meshItem = panel.GetMeshItem (meshInstanceId);
meshItem.SetVisible (false, NavigatorItemRecurse.Parents);
}
UpdateButtonsStatus (panel.buttons, panel.mode);
panel.callbacks.onViewTypeChanged ();
}
this.buttons = {
flatList : {
name : Loc ('Flat list'),
icon : 'flat_list',
div : null,
iconDiv : null
},
treeView : {
name : Loc ('Tree view'),
icon : 'tree_view',
div : null,
iconDiv : null
},
separator : null,
expandAll : {
name : Loc ('Expand all'),
icon : 'expand',
div : null,
iconDiv : null
},
collapseAll : {
name : Loc ('Collapse all'),
icon : 'collapse',
div : null,
iconDiv : null
},
showHideMeshes : {
name : Loc ('Show/hide meshes'),
icon : 'visible',
div : null,
iconDiv : null
},
fitToWindow : {
name : Loc ('Fit meshes to window'),
icon : 'fit',
div : null,
iconDiv : null
}
};
if (this.mode === MeshesPanelMode.Simple) {
CreateButton (this.titleButtonsDiv, this.buttons.showHideMeshes, 'right', () => {
let nodeId = this.rootItem.GetNodeId ();
this.callbacks.onNodeShowHide (nodeId);
});
CreateButton (this.titleButtonsDiv, this.buttons.fitToWindow, 'right', () => {
let nodeId = this.rootItem.GetNodeId ();
this.callbacks.onNodeFitToWindow (nodeId);
});
} else {
CreateButton (this.buttonsDiv, this.buttons.flatList, null, () => {
if (this.mode === MeshesPanelMode.FlatList) {
return;
}
this.mode = MeshesPanelMode.FlatList;
UpdateView (this, importResult);
});
CreateButton (this.buttonsDiv, this.buttons.treeView, null, () => {
if (this.mode === MeshesPanelMode.TreeView) {
return;
}
this.mode = MeshesPanelMode.TreeView;
UpdateView (this, importResult);
});
this.buttons.separator = AddDiv (this.buttonsDiv, 'ov_navigator_buttons_separator');
CreateButton (this.buttonsDiv, this.buttons.expandAll, null, () => {
this.rootItem.ExpandAll (true);
});
CreateButton (this.buttonsDiv, this.buttons.collapseAll, null, () => {
this.rootItem.ExpandAll (false);
});
CreateButton (this.buttonsDiv, this.buttons.showHideMeshes, 'right', () => {
let nodeId = this.rootItem.GetNodeId ();
this.callbacks.onNodeShowHide (nodeId);
});
CreateButton (this.buttonsDiv, this.buttons.fitToWindow, 'right', () => {
let nodeId = this.rootItem.GetNodeId ();
this.callbacks.onNodeFitToWindow (nodeId);
});
UpdateButtonsStatus (this.buttons, this.mode);
}
}
FillMeshTree (model)
{
function AddMeshToNodeTree (panel, node, mesh, meshIndex, parentItem, mode)
{
let meshName = GetMeshName (node.GetName (), mesh.GetName ());
let meshInstanceId = new MeshInstanceId (node.GetId (), meshIndex);
let meshItemIcon = (mode === MeshesPanelMode.TreeView ? 'tree_mesh' : null);
let meshItem = new MeshItem (meshName, meshItemIcon, meshInstanceId, {
onShowHide : (selectedMeshId) => {
panel.callbacks.onMeshShowHide (selectedMeshId);
},
onFitToWindow : (selectedMeshId) => {
panel.callbacks.onMeshFitToWindow (selectedMeshId);
},
onSelected : (selectedMeshId) => {
panel.callbacks.onMeshSelected (selectedMeshId);
}
});
panel.meshInstanceIdToItem.set (meshInstanceId.GetKey (), meshItem);
parentItem.AddChild (meshItem);
}
function CreateNodeItem (panel, node)
{
const nodeName = GetNodeName (node.GetName ());
const nodeId = node.GetId ();
let nodeItem = new NodeItem (nodeName, nodeId, {
onSelected : (selectedNodeId) => {
panel.callbacks.onNodeSelected (selectedNodeId);
},
onShowHide : (selectedNodeId) => {
panel.callbacks.onNodeShowHide (selectedNodeId);
},
onFitToWindow : (selectedNodeId) => {
panel.callbacks.onNodeFitToWindow (selectedNodeId);
}
});
panel.nodeIdToItem.set (nodeId, nodeItem);
return nodeItem;
}
function CreateDummyRootItem (panel, node)
{
const nodeId = node.GetId ();
let rootItem = new NodeItem (null, nodeId, {
onVisibilityChanged : (isVisible) => {
if (isVisible) {
SetSvgIconImageElement (panel.buttons.showHideMeshes.iconDiv, 'visible');
} else {
SetSvgIconImageElement (panel.buttons.showHideMeshes.iconDiv, 'hidden');
}
}
});
rootItem.Show (false);
rootItem.ShowChildren (true);
panel.treeView.AddChild (rootItem);
panel.nodeIdToItem.set (nodeId, rootItem);
return rootItem;
}
function AddModelNodeToTree (panel, model, node, parentItem, mode)
{
if (panel.stepDeletionState !== null && panel.stepDeletionState.IsNodeDeletedByPath (panel.stepDeletionState.GetNodePath (node.GetId ()))) {
return false;
}
let hasVisibleContent = false;
let meshNodes = [];
for (let childNode of node.GetChildNodes ()) {
if (mode === MeshesPanelMode.TreeView) {
if (childNode.IsMeshNode ()) {
meshNodes.push (childNode);
} else {
let nodeItem = CreateNodeItem (panel, childNode);
let childHasVisibleContent = AddModelNodeToTree (panel, model, childNode, nodeItem, mode);
if (childHasVisibleContent) {
parentItem.AddChild (nodeItem);
hasVisibleContent = true;
} else {
panel.nodeIdToItem.delete (childNode.GetId ());
}
}
} else {
if (AddModelNodeToTree (panel, model, childNode, parentItem, mode)) {
hasVisibleContent = true;
}
}
}
for (let meshNode of meshNodes) {
if (AddModelNodeToTree (panel, model, meshNode, parentItem, mode)) {
hasVisibleContent = true;
}
}
for (let meshIndex of node.GetMeshIndices ()) {
let mesh = model.GetMesh (meshIndex);
if (panel.stepDeletionState !== null) {
let meshInstanceId = new MeshInstanceId (node.GetId (), meshIndex);
if (panel.stepDeletionState.IsNodeDeletedByPath (panel.stepDeletionState.GetMeshNodePath (meshInstanceId))) {
continue;
}
}
AddMeshToNodeTree (panel, node, mesh, meshIndex, parentItem, mode);
hasVisibleContent = true;
}
return hasVisibleContent;
}
let rootNode = model.GetRootNode ();
this.rootItem = CreateDummyRootItem (this, rootNode);
AddModelNodeToTree (this, model, rootNode, this.rootItem, this.mode);
}
UpdateMaterialList (materialInfoArray)
{
this.materialsButton.Update (materialInfoArray);
}
GetNodeItem (nodeId)
{
return this.nodeIdToItem.get (nodeId);
}
MeshItemCount ()
{
return this.meshInstanceIdToItem.size;
}
GetMeshItem (meshInstanceId)
{
return this.meshInstanceIdToItem.get (meshInstanceId.GetKey ());
}
EnumerateNodeItems (processor)
{
for (const nodeItem of this.nodeIdToItem.values ()) {
if (!processor (nodeItem)) {
break;
}
}
}
EnumerateMeshItems (processor)
{
for (const meshItem of this.meshInstanceIdToItem.values ()) {
if (!processor (meshItem)) {
break;
}
}
}
IsMeshVisible (meshInstanceId)
{
let meshItem = this.GetMeshItem (meshInstanceId);
if (meshItem === undefined) {
return false;
}
return meshItem.IsVisible ();
}
HasHiddenMesh ()
{
let hasHiddenMesh = false;
this.EnumerateMeshItems ((meshItem) => {
if (!meshItem.IsVisible ()) {
hasHiddenMesh = true;
return false;
}
return true;
});
return hasHiddenMesh;
}
ShowAllMeshes (show)
{
this.EnumerateNodeItems ((nodeItem) => {
nodeItem.SetVisible (show, NavigatorItemRecurse.No);
return true;
});
this.EnumerateMeshItems ((meshItem) => {
meshItem.SetVisible (show, NavigatorItemRecurse.No);
return true;
});
}
ToggleNodeVisibility (nodeId)
{
let nodeItem = this.GetNodeItem (nodeId);
nodeItem.SetVisible (!nodeItem.IsVisible (), NavigatorItemRecurse.All);
}
ToggleMeshVisibility (meshInstanceId)
{
let meshItem = this.GetMeshItem (meshInstanceId);
meshItem.SetVisible (!meshItem.IsVisible (), NavigatorItemRecurse.Parents);
}
IsMeshIsolated (meshInstanceId)
{
let isIsolated = true;
this.EnumerateMeshItems ((meshItem) => {
if (!meshItem.GetMeshInstanceId ().IsEqual (meshInstanceId) && meshItem.IsVisible ()) {
isIsolated = false;
return false;
}
return true;
});
return isIsolated;
}
IsolateMesh (meshInstanceId)
{
this.ShowAllMeshes (false);
this.ToggleMeshVisibility (meshInstanceId);
}
}