face fields support

This commit is contained in:
Andrea Pozzetti
2025-11-19 23:13:44 +01:00
parent eeaa30307b
commit 54c671eccd
4 changed files with 176 additions and 55 deletions
+20 -7
View File
@@ -1,8 +1,8 @@
"""
Helper function for exporting mesh to VTK PolyData XML format (.vtp) with vertex scalar attributes.
Helper function for exporting mesh to VTK PolyData XML format (.vtp) with scalar attributes.
VTP format preserves vertex attributes (scalar fields) which can be visualized
in VTK.js with color mapping.
VTP format preserves vertex attributes (PointData) and face attributes (CellData)
which can be visualized in VTK.js with color mapping.
"""
import trimesh as trimesh_module
@@ -11,13 +11,13 @@ import xml.etree.ElementTree as ET
def export_mesh_with_scalars_vtp(trimesh: trimesh_module.Trimesh, filepath: str):
"""
Export trimesh to VTK PolyData XML format (.vtp) with vertex scalar attributes.
Export trimesh to VTK PolyData XML format (.vtp) with scalar attributes.
VTP format preserves vertex attributes (scalar fields) which can be visualized
in VTK.js with color mapping.
VTP format preserves vertex attributes (PointData) and face attributes (CellData)
which can be visualized in VTK.js with color mapping.
Args:
trimesh: Trimesh object with optional vertex_attributes
trimesh: Trimesh object with optional vertex_attributes and face_attributes
filepath: Output .vtp file path
"""
print(f"[_export_mesh_with_scalars_vtp] Exporting to VTP: {filepath}")
@@ -54,6 +54,19 @@ def export_mesh_with_scalars_vtp(trimesh: trimesh_module.Trimesh, filepath: str)
format='ascii')
scalar_array.text = ' '.join(map(str, attr_values.flatten()))
# CellData section (face attributes)
cell_data = ET.SubElement(piece, 'CellData')
# Add face attributes as scalar arrays
if hasattr(trimesh, 'face_attributes') and trimesh.face_attributes:
for attr_name, attr_values in trimesh.face_attributes.items():
print(f"[_export_mesh_with_scalars_vtp] Adding face field: {attr_name}")
scalar_array = ET.SubElement(cell_data, 'DataArray',
type='Float32',
Name=attr_name,
format='ascii')
scalar_array.text = ' '.join(map(str, attr_values.flatten()))
# Polys section (faces/triangles)
polys = ET.SubElement(piece, 'Polys')
@@ -2,8 +2,8 @@
Preview mesh with VTK.js viewer optimized for scalar field visualization.
Specifically designed for visualizing vertex scalar fields (like boundary edges,
curvature, distance fields, etc.) with color mapping. Always exports as VTP
format to preserve scalar data.
curvature, distance fields, etc.) and face scalar fields (like segmentation labels).
Always exports as VTP format to preserve scalar data.
"""
import trimesh as trimesh_module
@@ -24,8 +24,8 @@ class PreviewMeshVTKPointCloudNode:
Preview mesh with VTK.js viewer optimized for scalar field visualization.
Specifically designed for visualizing vertex scalar fields (like boundary edges,
curvature, distance fields, etc.) with color mapping. Always exports as VTP
format to preserve scalar data.
curvature, distance fields, etc.) and face scalar fields (like segmentation labels).
Always exports as VTP format to preserve scalar data.
"""
@classmethod
@@ -53,15 +53,20 @@ class PreviewMeshVTKPointCloudNode:
"""
print(f"[PreviewMeshVTKPointCloud] Preparing preview: {len(trimesh.vertices)} vertices, {len(trimesh.faces)} faces")
# Check if mesh has vertex attributes (scalar fields)
# Check if mesh has vertex or face attributes (scalar fields)
has_vertex_attrs = hasattr(trimesh, 'vertex_attributes') and len(trimesh.vertex_attributes) > 0
has_face_attrs = hasattr(trimesh, 'face_attributes') and len(trimesh.face_attributes) > 0
if hasattr(trimesh, 'vertex_attributes'):
print(f"[PreviewMeshVTKPointCloud] trimesh.vertex_attributes: {trimesh.vertex_attributes.keys()}")
print(f"[PreviewMeshVTKPointCloud] Number of vertex attributes: {len(trimesh.vertex_attributes)}")
if not has_vertex_attrs:
print(f"[PreviewMeshVTKPointCloud] WARNING: No vertex attributes found. This node is for scalar field visualization.")
if hasattr(trimesh, 'face_attributes'):
print(f"[PreviewMeshVTKPointCloud] trimesh.face_attributes: {trimesh.face_attributes.keys()}")
print(f"[PreviewMeshVTKPointCloud] Number of face attributes: {len(trimesh.face_attributes)}")
if not has_vertex_attrs and not has_face_attrs:
print(f"[PreviewMeshVTKPointCloud] WARNING: No vertex or face attributes found. This node is for scalar field visualization.")
# Always use VTP format for this node (designed for scalar fields)
filename = f"preview_vtk_pointcloud_{uuid.uuid4().hex[:8]}.vtp"
@@ -72,11 +77,12 @@ class PreviewMeshVTKPointCloudNode:
else:
filepath = os.path.join(tempfile.gettempdir(), filename)
# Export mesh with vertex attributes as VTP
# Export mesh with vertex and face attributes as VTP
try:
export_mesh_with_scalars_vtp(trimesh, filepath)
num_fields = len(trimesh.vertex_attributes) if has_vertex_attrs else 0
print(f"[PreviewMeshVTKPointCloud] Exported VTP with {num_fields} scalar fields to: {filepath}")
num_vertex_fields = len(trimesh.vertex_attributes) if has_vertex_attrs else 0
num_face_fields = len(trimesh.face_attributes) if has_face_attrs else 0
print(f"[PreviewMeshVTKPointCloud] Exported VTP with {num_vertex_fields} vertex fields and {num_face_fields} face fields to: {filepath}")
except Exception as e:
print(f"[PreviewMeshVTKPointCloud] Export failed: {e}")
import traceback
+70 -19
View File
@@ -456,15 +456,36 @@
for (let i = 0; i < numArrays; i++) {
const array = pointData.getArray(i);
const arrayName = array.getName();
console.log(`[GeomPack VTK Fields Viewer] Found scalar field: ${arrayName}`);
console.log(`[GeomPack VTK Fields Viewer] Found point field: ${arrayName}`);
const option = document.createElement('option');
option.value = arrayName;
option.textContent = arrayName;
option.value = `point:${arrayName}`;
option.textContent = `${arrayName} (vertex)`;
fieldSelector.appendChild(option);
}
fieldLabel.style.display = 'flex';
console.log('[GeomPack VTK Fields Viewer] Scalar field selector populated with', numArrays, 'fields');
} else {
console.log('[GeomPack VTK Fields Viewer] Point data selector populated with', numArrays, 'fields');
}
// Check for scalar fields in cell data (face attributes)
const cellData = polydata.getCellData();
const numCellArrays = cellData.getNumberOfArrays();
console.log('[GeomPack VTK Fields Viewer] Cell data arrays:', numCellArrays);
if (numCellArrays > 0) {
for (let i = 0; i < numCellArrays; i++) {
const array = cellData.getArray(i);
const arrayName = array.getName();
console.log(`[GeomPack VTK Fields Viewer] Found face field: ${arrayName}`);
const option = document.createElement('option');
option.value = `cell:${arrayName}`;
option.textContent = `${arrayName} (face)`;
fieldSelector.appendChild(option);
}
fieldLabel.style.display = 'flex';
console.log('[GeomPack VTK Fields Viewer] Cell data selector populated with', numCellArrays, 'fields');
}
if (numArrays === 0 && numCellArrays === 0) {
fieldLabel.style.display = 'none';
console.log('[GeomPack VTK Fields Viewer] No scalar fields found');
}
@@ -472,7 +493,7 @@
// Populate colormap selector
const colormapSelector = document.getElementById('colormapSelector');
const colormapLabel = document.getElementById('colormapLabel');
if (numArrays > 0) {
if (numArrays > 0 || numCellArrays > 0) {
colormapSelector.innerHTML = '';
// Add popular colormaps first
@@ -929,24 +950,54 @@
if (fieldName && currentReader) {
// Get the polydata with scalar field (from mapper's input, not reader)
const polydata = mapper.getInputData();
const pointData = polydata.getPointData();
const scalarArray = pointData.getArrayByName(fieldName);
if (scalarArray) {
// Set the scalar array as active scalars
pointData.setActiveScalars(fieldName);
// Check if this is a cell data field (face attribute) or point data field (vertex attribute)
if (fieldName.startsWith('cell:')) {
// Handle cell data (face attributes)
const actualFieldName = fieldName.substring(5); // Remove "cell:" prefix
const cellData = polydata.getCellData();
const scalarArray = cellData.getArrayByName(actualFieldName);
// Configure mapper for scalar coloring
mapper.setScalarVisibility(true);
mapper.setScalarModeToUsePointData();
mapper.setScalarRange(scalarArray.getRange());
if (scalarArray) {
// Set the scalar array as active scalars
cellData.setActiveScalars(actualFieldName);
// Apply colormap using the updateColormap function
updateColormap();
// Configure mapper for scalar coloring using cell data
mapper.setScalarVisibility(true);
mapper.setScalarModeToUseCellData();
mapper.setScalarRange(scalarArray.getRange());
console.log(`[GeomPack VTK Fields Viewer] Scalar field '${fieldName}' activated, range:`, scalarArray.getRange());
// Apply colormap using the updateColormap function
updateColormap();
console.log(`[GeomPack VTK Fields Viewer] Face field '${actualFieldName}' activated, range:`, scalarArray.getRange());
} else {
console.warn(`[GeomPack VTK Fields Viewer] Face field '${actualFieldName}' not found`);
}
} else if (fieldName.startsWith('point:')) {
// Handle point data (vertex attributes)
const actualFieldName = fieldName.substring(6); // Remove "point:" prefix
const pointData = polydata.getPointData();
const scalarArray = pointData.getArrayByName(actualFieldName);
if (scalarArray) {
// Set the scalar array as active scalars
pointData.setActiveScalars(actualFieldName);
// Configure mapper for scalar coloring using point data
mapper.setScalarVisibility(true);
mapper.setScalarModeToUsePointData();
mapper.setScalarRange(scalarArray.getRange());
// Apply colormap using the updateColormap function
updateColormap();
console.log(`[GeomPack VTK Fields Viewer] Vertex field '${actualFieldName}' activated, range:`, scalarArray.getRange());
} else {
console.warn(`[GeomPack VTK Fields Viewer] Vertex field '${actualFieldName}' not found`);
}
} else {
console.warn(`[GeomPack VTK Fields Viewer] Scalar field '${fieldName}' not found`);
console.warn(`[GeomPack VTK Fields Viewer] Unknown field type: ${fieldName}`);
}
} else {
// Disable scalar coloring, use solid color
+70 -19
View File
@@ -575,15 +575,36 @@
for (let i = 0; i < numArrays; i++) {
const array = pointData.getArray(i);
const arrayName = array.getName();
console.log(`[GeomPack VTK Filters] Found scalar field: ${arrayName}`);
console.log(`[GeomPack VTK Filters] Found point field: ${arrayName}`);
const option = document.createElement('option');
option.value = arrayName;
option.textContent = arrayName;
option.value = `point:${arrayName}`;
option.textContent = `${arrayName} (vertex)`;
scalarSelector.appendChild(option);
}
scalarLabel.style.display = 'flex';
console.log('[GeomPack VTK Filters] Scalar field selector populated with', numArrays, 'fields');
} else {
console.log('[GeomPack VTK Filters] Point data selector populated with', numArrays, 'fields');
}
// Check for scalar fields in cell data (face attributes)
const cellData = polydata.getCellData();
const numCellArrays = cellData.getNumberOfArrays();
console.log('[GeomPack VTK Filters] Cell data arrays:', numCellArrays);
if (numCellArrays > 0) {
for (let i = 0; i < numCellArrays; i++) {
const array = cellData.getArray(i);
const arrayName = array.getName();
console.log(`[GeomPack VTK Filters] Found face field: ${arrayName}`);
const option = document.createElement('option');
option.value = `cell:${arrayName}`;
option.textContent = `${arrayName} (face)`;
scalarSelector.appendChild(option);
}
scalarLabel.style.display = 'flex';
console.log('[GeomPack VTK Filters] Cell data selector populated with', numCellArrays, 'fields');
}
if (numArrays === 0 && numCellArrays === 0) {
scalarLabel.style.display = 'none';
console.log('[GeomPack VTK Filters] No scalar fields found');
}
@@ -591,7 +612,7 @@
// Populate colormap selector
const colormapSelector = document.getElementById('colormapSelector');
const colormapLabel = document.getElementById('colormapLabel');
if (numArrays > 0) {
if (numArrays > 0 || numCellArrays > 0) {
colormapSelector.innerHTML = '';
// Add popular colormaps first
@@ -1096,24 +1117,54 @@
if (fieldName && currentReader) {
// Get the polydata with scalar field (from mapper's input, not reader)
const polydata = mapper.getInputData();
const pointData = polydata.getPointData();
const scalarArray = pointData.getArrayByName(fieldName);
if (scalarArray) {
// Set the scalar array as active scalars
pointData.setActiveScalars(fieldName);
// Check if this is a cell data field (face attribute) or point data field (vertex attribute)
if (fieldName.startsWith('cell:')) {
// Handle cell data (face attributes)
const actualFieldName = fieldName.substring(5); // Remove "cell:" prefix
const cellData = polydata.getCellData();
const scalarArray = cellData.getArrayByName(actualFieldName);
// Configure mapper for scalar coloring
mapper.setScalarVisibility(true);
mapper.setScalarModeToUsePointData();
mapper.setScalarRange(scalarArray.getRange());
if (scalarArray) {
// Set the scalar array as active scalars
cellData.setActiveScalars(actualFieldName);
// Apply colormap using the updateColormap function
updateColormap();
// Configure mapper for scalar coloring using cell data
mapper.setScalarVisibility(true);
mapper.setScalarModeToUseCellData();
mapper.setScalarRange(scalarArray.getRange());
console.log(`[GeomPack VTK Filters] Scalar field '${fieldName}' activated, range:`, scalarArray.getRange());
// Apply colormap using the updateColormap function
updateColormap();
console.log(`[GeomPack VTK Filters] Face field '${actualFieldName}' activated, range:`, scalarArray.getRange());
} else {
console.warn(`[GeomPack VTK Filters] Face field '${actualFieldName}' not found`);
}
} else if (fieldName.startsWith('point:')) {
// Handle point data (vertex attributes)
const actualFieldName = fieldName.substring(6); // Remove "point:" prefix
const pointData = polydata.getPointData();
const scalarArray = pointData.getArrayByName(actualFieldName);
if (scalarArray) {
// Set the scalar array as active scalars
pointData.setActiveScalars(actualFieldName);
// Configure mapper for scalar coloring using point data
mapper.setScalarVisibility(true);
mapper.setScalarModeToUsePointData();
mapper.setScalarRange(scalarArray.getRange());
// Apply colormap using the updateColormap function
updateColormap();
console.log(`[GeomPack VTK Filters] Vertex field '${actualFieldName}' activated, range:`, scalarArray.getRange());
} else {
console.warn(`[GeomPack VTK Filters] Vertex field '${actualFieldName}' not found`);
}
} else {
console.warn(`[GeomPack VTK Filters] Scalar field '${fieldName}' not found`);
console.warn(`[GeomPack VTK Filters] Unknown field type: ${fieldName}`);
}
} else {
// Disable scalar coloring, use solid color