git: fea222604fc0 - main - graphics/f3d: upgrade to 2.3.0

From: Thierry Thomas <thierry_at_FreeBSD.org>
Date: Thu, 21 Mar 2024 18:02:14 UTC
The branch main has been updated by thierry:

URL: https://cgit.FreeBSD.org/ports/commit/?id=fea222604fc0b3ed6bcdd48324d1a7a462128dd7

commit fea222604fc0b3ed6bcdd48324d1a7a462128dd7
Author:     Thierry Thomas <thierry@FreeBSD.org>
AuthorDate: 2024-03-08 10:07:26 +0000
Commit:     Thierry Thomas <thierry@FreeBSD.org>
CommitDate: 2024-03-21 18:01:17 +0000

    graphics/f3d: upgrade to 2.3.0
    
    Release notes at <https://github.com/f3d-app/f3d/releases/tag/v2.3.0>.
    
    Also fix with OpenCascade 7.8.0:
    See <https://github.com/f3d-app/f3d/issues/1304>.
    
    PR:             277308
    Approved by:    yuri (maintainer)
---
 graphics/f3d/Makefile                              |  11 +-
 graphics/f3d/distinfo                              |   6 +-
 .../f3d/files/patch-plugins_occt_CMakeLists.txt    |  35 ++
 .../patch-plugins_occt_module_vtkF3DOCCTReader.cxx | 381 +++++++++++++++++++++
 graphics/f3d/pkg-plist                             |   5 +-
 5 files changed, 432 insertions(+), 6 deletions(-)

diff --git a/graphics/f3d/Makefile b/graphics/f3d/Makefile
index 981ed2bb3181..482fcc314e1f 100644
--- a/graphics/f3d/Makefile
+++ b/graphics/f3d/Makefile
@@ -1,6 +1,6 @@
 PORTNAME=	f3d
 DISTVERSIONPREFIX=	v
-DISTVERSION=	2.2.1
+DISTVERSION=	2.3.0
 CATEGORIES=	graphics
 
 MAINTAINER=	yuri@FreeBSD.org
@@ -11,17 +11,24 @@ LICENSE=	BSD3CLAUSE
 LICENSE_FILE=	${WRKSRC}/LICENSE.md
 
 LIB_DEPENDS=	libavcodec.so:multimedia/ffmpeg4 \
+		libexpat.so:textproc/expat2 \
 		libfontconfig.so:x11-fonts/fontconfig \
 		libfreeimage.so:graphics/freeimage \
 		libfreetype.so:print/freetype2 \
 		libhdf5.so:science/hdf5 \
 		libImath-3_1.so:math/Imath \
+		libjpeg.so:graphics/jpeg-turbo \
 		libnetcdf.so:science/netcdf \
+		libpng.so:graphics/png \
+		libtbbmalloc.so:devel/onetbb \
+		libtiff.so:graphics/tiff \
 		libvtkCommonMisc-${VTK_VER}.so:math/vtk${VTK_VER:R}
 
-USES=		cmake:testing compiler:c++17-lang desktop-file-utils gl shared-mime-info tcl tk xorg
+USES=		cmake:testing compiler:c++17-lang desktop-file-utils gl mpi	\
+		shared-mime-info tcl tk xorg
 USE_GL=		gl glu
 USE_XORG=	ice sm x11 xt
+USE_LDCONFIG=	yes
 
 USE_GITHUB=	yes
 GH_ACCOUNT=	f3d-app
diff --git a/graphics/f3d/distinfo b/graphics/f3d/distinfo
index c29c4501f9a2..3560f0ee0555 100644
--- a/graphics/f3d/distinfo
+++ b/graphics/f3d/distinfo
@@ -1,3 +1,3 @@
-TIMESTAMP = 1704769349
-SHA256 (f3d-app-f3d-v2.2.1_GH0.tar.gz) = 4d3a73b0107c8db7f0556107c74087d3748232a73981f65f7c5186ac1003ec8d
-SIZE (f3d-app-f3d-v2.2.1_GH0.tar.gz) = 29785716
+TIMESTAMP = 1709891821
+SHA256 (f3d-app-f3d-v2.3.0_GH0.tar.gz) = 9c2906b62f3066f075effbabd6501964391e8a8ffad6ed773c33db12580cc466
+SIZE (f3d-app-f3d-v2.3.0_GH0.tar.gz) = 40606315
diff --git a/graphics/f3d/files/patch-plugins_occt_CMakeLists.txt b/graphics/f3d/files/patch-plugins_occt_CMakeLists.txt
new file mode 100644
index 000000000000..922c86cde9d6
--- /dev/null
+++ b/graphics/f3d/files/patch-plugins_occt_CMakeLists.txt
@@ -0,0 +1,35 @@
+--- plugins/occt/CMakeLists.txt.orig	2024-01-21 15:29:01 UTC
++++ plugins/occt/CMakeLists.txt
+@@ -17,18 +17,30 @@ endif()
+   message(FATAL_ERROR "Plugin: OpenCASCADE: ${OpenCASCADE_VERSION} is not supported by F3D, please update your OpenCASCADE installation.")
+ endif()
+ 
+-if((NOT TARGET "TKSTEP") AND (NOT TARGET "TKIGES") AND (NOT TARGET "TKMesh"))
++if("${OpenCASCADE_VERSION}" VERSION_LESS "7.8.0")
++  if((NOT TARGET "TKSTEP") AND (NOT TARGET "TKIGES") AND (NOT TARGET "TKMesh"))
+     message(FATAL_ERROR "Plugin: OpenCASCADE does not contain required modules")
++  endif()
+ endif()
+ 
++if((NOT TARGET "TKDESTEP") AND (NOT TARGET "TKDEIGES") AND (NOT TARGET "TKMesh"))
++    message(FATAL_ERROR "Plugin: OpenCASCADE does not contain required modules")
++endif()
++
+ message(STATUS "Plugin: OpenCASCADE ${OpenCASCADE_VERSION} found")
+ 
+ option(F3D_PLUGIN_OCCT_COLORING_SUPPORT "Enable coloring support in occt plugin" ON)
+ mark_as_advanced(F3D_PLUGIN_OCCT_COLORING_SUPPORT)
+ 
+ if(F3D_PLUGIN_OCCT_COLORING_SUPPORT)
+-  if (NOT (TARGET "TKXDESTEP") OR NOT (TARGET "TKXDEIGES"))
++  if("${OpenCASCADE_VERSION}" VERSION_LESS "7.8.0")
++    if (NOT (TARGET "TKXDESTEP") OR NOT (TARGET "TKXDEIGES"))
+       message(FATAL_ERROR "occt plugin: TKXDESTEP and TKXDEIGES OCCT modules are not found. Turn off F3D_PLUGIN_OCCT_COLORING_SUPPORT or enable them in your OpenCascade build.")
++    endif()
++  else()
++    if (NOT (TARGET "TKXSDRAWSTEP") OR NOT (TARGET "TKXSDRAWIGES"))
++        message(FATAL_ERROR "occt plugin: TKXSDRAWSTEP and TKXSDRAWIGES OCCT modules are not found. Turn off F3D_PLUGIN_OCCT_COLORING_SUPPORT or enable them in your OpenCascade build.")
++    endif()
+   endif()
+ endif()
+ 
diff --git a/graphics/f3d/files/patch-plugins_occt_module_vtkF3DOCCTReader.cxx b/graphics/f3d/files/patch-plugins_occt_module_vtkF3DOCCTReader.cxx
new file mode 100644
index 000000000000..270c6e9fd49b
--- /dev/null
+++ b/graphics/f3d/files/patch-plugins_occt_module_vtkF3DOCCTReader.cxx
@@ -0,0 +1,381 @@
+--- plugins/occt/module/vtkF3DOCCTReader.cxx.orig	2024-01-21 15:29:01 UTC
++++ plugins/occt/module/vtkF3DOCCTReader.cxx
+@@ -31,14 +31,17 @@
+ #if F3D_PLUGIN_OCCT_XCAF
+ #include <IGESCAFControl_Reader.hxx>
+ #include <STEPCAFControl_Reader.hxx>
++#include <Standard_Version.hxx>
+ #include <TDF_ChildIterator.hxx>
+ #include <TDataStd_Name.hxx>
+ #include <TDocStd_Document.hxx>
+ #include <XCAFApp_Application.hxx>
+-#include <XCAFDoc_ColorTool.hxx>
+ #include <XCAFDoc_DocumentTool.hxx>
+ #include <XCAFDoc_Location.hxx>
+ #include <XCAFDoc_ShapeTool.hxx>
++#include <XCAFPrs.hxx>
++#include <XCAFPrs_IndexedDataMapOfShapeStyle.hxx>
++#include <XCAFPrs_Style.hxx>
+ #endif
+ 
+ #if defined(__GNUC__)
+@@ -71,6 +74,10 @@ class vtkF3DOCCTReader::vtkInternals
+ 
+ class vtkF3DOCCTReader::vtkInternals
+ {
++#if F3D_PLUGIN_OCCT_XCAF
++  using StyleMap = XCAFPrs_IndexedDataMapOfShapeStyle;
++#endif
++
+ public:
+   //----------------------------------------------------------------------------
+   explicit vtkInternals(vtkF3DOCCTReader* parent)
+@@ -79,7 +86,11 @@ class vtkF3DOCCTReader::vtkInternals
+   }
+ 
+   //----------------------------------------------------------------------------
++#if F3D_PLUGIN_OCCT_XCAF
++  vtkSmartPointer<vtkPolyData> CreateShape(const TopoDS_Shape& shape, const TDF_Label& label)
++#else
+   vtkSmartPointer<vtkPolyData> CreateShape(const TopoDS_Shape& shape)
++#endif
+   {
+     vtkNew<vtkPoints> points;
+     vtkNew<vtkFloatArray> normals;
+@@ -88,32 +99,49 @@ class vtkF3DOCCTReader::vtkInternals
+     vtkNew<vtkFloatArray> uvs;
+     uvs->SetNumberOfComponents(2);
+     uvs->SetName("UV");
++#if F3D_PLUGIN_OCCT_XCAF
+     vtkNew<vtkUnsignedCharArray> colors;
+     colors->SetNumberOfComponents(3);
+     colors->SetName("Colors");
++#endif
+     vtkNew<vtkCellArray> trianglesCells;
+     vtkNew<vtkCellArray> linesCells;
+ 
+     Standard_Integer shift = 0;
+ 
++#if F3D_PLUGIN_OCCT_XCAF
++    const StyleMap inheritedStyles = this->CollectInheritedStyles(label, shape);
++#endif
++
++    /* Mesh the whole shape. This only affect faces, edges have to be handled separately. */
++    BRepMesh_IncrementalMesh(shape, this->Parent->GetLinearDeflection(),
++      this->Parent->GetRelativeDeflection(), this->Parent->GetAngularDeflection(), Standard_True);
++
+     if (this->Parent->GetReadWire())
+     {
+-      // Add all edges to polydata
+-      for (TopExp_Explorer exEdge(shape, TopAbs_EDGE); exEdge.More(); exEdge.Next())
++      std::vector<TopoDS_Edge> edges;
+       {
+-        TopoDS_Edge edge = TopoDS::Edge(exEdge.Current());
++        /* add all edges to a compound to remesh them all at once */
++        TopoDS_Builder builder;
++        TopoDS_Compound compound;
++        builder.MakeCompound(compound);
++        for (TopExp_Explorer exEdge(shape, TopAbs_EDGE); exEdge.More(); exEdge.Next())
++        {
++          const TopoDS_Edge edge = TopoDS::Edge(exEdge.Current());
++          builder.Add(compound, edge);
++          edges.push_back(edge);
++        }
++        BRepMesh_IncrementalMesh(compound, this->Parent->GetLinearDeflection(),
++          this->Parent->GetRelativeDeflection(), this->Parent->GetAngularDeflection(),
++          Standard_True);
++      }
+ 
++      // Add all edges to polydata
++      for (const TopoDS_Edge& edge : edges)
++      {
+         TopLoc_Location location;
+         const auto& poly = BRep_Tool::Polygon3D(edge, location);
+ 
+-        if (poly.IsNull() || poly->Nodes().Length() <= 0)
+-        {
+-          // meshing
+-          BRepMesh_IncrementalMesh(edge, this->Parent->GetLinearDeflection(),
+-            this->Parent->GetRelativeDeflection(), this->Parent->GetAngularDeflection(),
+-            Standard_True);
+-        }
+-
+         if (poly.IsNull())
+         {
+           continue;
+@@ -134,23 +162,29 @@ class vtkF3DOCCTReader::vtkInternals
+           uvs->InsertNextTypedTuple(fn);
+         }
+ 
+-        std::vector<vtkIdType> polyline(nbV - 1);
++        std::vector<vtkIdType> polyline(nbV);
+         std::iota(polyline.begin(), polyline.end(), shift);
+         linesCells->InsertNextCell(polyline.size(), polyline.data());
+ 
+ #if F3D_PLUGIN_OCCT_XCAF
+-        if (this->ColorTool)
++        std::array<unsigned char, 3> rgb = { 0, 0, 0 };
++        try
+         {
+-          std::array<unsigned char, 3> rgb = { 0, 0, 0 };
+-          Quantity_Color aColor;
+-          if (this->ColorTool->GetColor(edge, XCAFDoc_ColorCurv, aColor))
++          const auto& style = inheritedStyles.FindFromKey(edge);
++          if (style.IsSetColorCurv())
+           {
+-            rgb[0] = static_cast<unsigned char>(255.0 * aColor.Red());
+-            rgb[1] = static_cast<unsigned char>(255.0 * aColor.Green());
+-            rgb[2] = static_cast<unsigned char>(255.0 * aColor.Blue());
++            Quantity_Color color = style.GetColorCurv();
++            rgb[0] = static_cast<unsigned char>(255.0 * color.Red());
++            rgb[1] = static_cast<unsigned char>(255.0 * color.Green());
++            rgb[2] = static_cast<unsigned char>(255.0 * color.Blue());
+           }
+-          colors->InsertNextTypedTuple(rgb.data());
+         }
++        catch (Standard_NoSuchObject&)
++        {
++          /* edge has no style, safe to ignore */
++        }
++
++        colors->InsertNextTypedTuple(rgb.data());
+ #endif
+ 
+         shift += nbV;
+@@ -165,14 +199,6 @@ class vtkF3DOCCTReader::vtkInternals
+       TopLoc_Location location;
+       const auto& poly = BRep_Tool::Triangulation(face, location);
+ 
+-      if (poly.IsNull() || poly->NbTriangles() <= 0)
+-      {
+-        // meshing
+-        BRepMesh_IncrementalMesh(face, this->Parent->GetLinearDeflection(),
+-          this->Parent->GetRelativeDeflection(), this->Parent->GetAngularDeflection(),
+-          Standard_True);
+-      }
+-
+       if (poly.IsNull())
+       {
+         continue;
+@@ -243,23 +269,32 @@ class vtkF3DOCCTReader::vtkInternals
+           std::swap(cell[0], cell[2]);
+         }
+         trianglesCells->InsertNextCell(3, cell);
++      }
+ 
+ #if F3D_PLUGIN_OCCT_XCAF
+-        if (this->ColorTool)
++      std::array<unsigned char, 3> rgb = { 255, 255, 255 };
++      try
++      {
++        const auto& style = inheritedStyles.FindFromKey(face);
++        if (style.IsSetColorSurf())
+         {
+-          std::array<unsigned char, 3> rgb = { 255, 255, 255 };
+-          Quantity_Color aColor;
+-          if (this->ColorTool->GetColor(face, XCAFDoc_ColorSurf, aColor))
+-          {
+-            rgb[0] = static_cast<unsigned char>(255.0 * aColor.Red());
+-            rgb[1] = static_cast<unsigned char>(255.0 * aColor.Green());
+-            rgb[2] = static_cast<unsigned char>(255.0 * aColor.Blue());
+-          }
+-          colors->InsertNextTypedTuple(rgb.data());
++          Quantity_Color color = style.GetColorSurf();
++          rgb[0] = static_cast<unsigned char>(255.0 * color.Red());
++          rgb[1] = static_cast<unsigned char>(255.0 * color.Green());
++          rgb[2] = static_cast<unsigned char>(255.0 * color.Blue());
+         }
+-#endif
+       }
++      catch (Standard_NoSuchObject&)
++      {
++        /* face has no style, safe to ignore */
++      }
+ 
++      for (int i = 1; i <= nbT; i++)
++      {
++        colors->InsertNextTypedTuple(rgb.data());
++      }
++#endif
++
+       shift += nbV;
+     }
+ 
+@@ -271,11 +306,7 @@ class vtkF3DOCCTReader::vtkInternals
+     polydata->SetLines(linesCells);
+ 
+ #if F3D_PLUGIN_OCCT_XCAF
+-    /* colors may be left empty if this->ColorTool has not been initialized */
+-    if (colors->GetSize() > 0)
+-    {
+-      polydata->GetCellData()->SetScalars(colors);
+-    }
++    polydata->GetCellData()->SetScalars(colors);
+ #endif
+ 
+     polydata->Squeeze();
+@@ -283,7 +314,99 @@ class vtkF3DOCCTReader::vtkInternals
+   }
+ 
+ #if F3D_PLUGIN_OCCT_XCAF
++  StyleMap CollectInheritedStyles(const TDF_Label& rootLabel, const TopoDS_Shape& rootShape)
++  {
++    StyleMap inheritedStyles;
++
++    if (rootLabel.IsNull())
++    {
++      return inheritedStyles;
++    }
++
++    /* collect styled shapes from the document */
++    StyleMap collectedStyles;
++    XCAFPrs::CollectStyleSettings(rootLabel, TopLoc_Location(), collectedStyles);
++
++    /* iterate styled shapes and collect sorted by ascending shape type depth */
++    const auto cmp = [](const TopoDS_Shape& a, const TopoDS_Shape& b)
++    { return a.ShapeType() > b.ShapeType(); };
++    std::multimap<TopoDS_Shape, XCAFPrs_Style, decltype(cmp)> styledShapes(cmp);
++
++    const TopAbs_ShapeEnum leafType = this->Parent->GetReadWire() ? TopAbs_EDGE : TopAbs_FACE;
++    for (StyleMap::Iterator iter(collectedStyles); iter.More(); iter.Next())
++    {
++      const TopoDS_Shape& shape = iter.Key();
++      if (shape.ShapeType() <= leafType)
++      {
++        styledShapes.insert({ shape, iter.Value() });
++      }
++    }
++
++    /* pass down each parent style props to descendent edge/face leaves */
++    const auto passDownToLeaves = [&](TopAbs_ShapeEnum type)
++    {
++      for (const auto& styledShape : styledShapes)
++      {
++        for (TopExp_Explorer iter(styledShape.first, type); iter.More(); iter.Next())
++        {
++          try
++          {
++            this->PassDownStyleProps(
++              styledShape.second, inheritedStyles.ChangeFromKey(iter.Current()));
++          }
++          catch (Standard_NoSuchObject&)
++          {
++            inheritedStyles.Add(iter.Current(), styledShape.second);
++          }
++        }
++      }
++    };
++
++    passDownToLeaves(TopAbs_FACE);
++
++    if (this->Parent->GetReadWire())
++    {
++      passDownToLeaves(TopAbs_EDGE);
++    }
++
++    /* pass down default style (if any) to all leaves */
++    try
++    {
++      const XCAFPrs_Style& defaultStyle = collectedStyles.FindFromKey(rootShape);
++      for (StyleMap::Iterator iter(inheritedStyles); iter.More(); iter.Next())
++      {
++        XCAFPrs_Style style = iter.Value();
++        this->PassDownStyleProps(defaultStyle, style);
++      }
++    }
++    catch (Standard_NoSuchObject&)
++    {
++      /* root shape has no style, safe to ignore */
++    }
++
++    return inheritedStyles;
++  }
++
+   //----------------------------------------------------------------------------
++  void PassDownStyleProps(const XCAFPrs_Style& parent, XCAFPrs_Style& child)
++  {
++    if (!child.IsSetColorCurv() && parent.IsSetColorCurv())
++    {
++      child.SetColorCurv(parent.GetColorCurv());
++    }
++
++    if (!child.IsSetColorSurf() && parent.IsSetColorSurf())
++    {
++      child.SetColorSurf(parent.GetColorSurfRGBA());
++    }
++
++    if (child.Material().IsNull() && !parent.Material().IsNull())
++    {
++      child.SetMaterial(parent.Material());
++    }
++  };
++
++  //----------------------------------------------------------------------------
+   void AddLabel(const TDF_Label& label, vtkMatrix4x4* position, vtkMultiBlockDataSet* mb)
+   {
+     if (this->ShapeTool->IsSimpleShape(label) && this->ShapeTool->IsTopLevel(label))
+@@ -365,7 +488,11 @@ class vtkF3DOCCTReader::vtkInternals
+   int GetHash(const TDF_Label& label)
+   {
+     TopoDS_Shape aShape;
++#if OCC_VERSION_HEX < 0x070800
+     return this->ShapeTool->GetShape(label, aShape) ? aShape.HashCode(INT_MAX) : 0;
++#else
++    return this->ShapeTool->GetShape(label, aShape) ? std::hash<TopoDS_Shape>{}(aShape) : 0;
++#endif
+   }
+ 
+   //----------------------------------------------------------------------------
+@@ -401,7 +528,6 @@ class vtkF3DOCCTReader::vtkInternals
+ 
+   std::unordered_map<int, vtkSmartPointer<vtkPolyData> > ShapeMap;
+   Handle(XCAFDoc_ShapeTool) ShapeTool;
+-  Handle(XCAFDoc_ColorTool) ColorTool;
+ #endif
+ 
+   vtkF3DOCCTReader* Parent;
+@@ -424,7 +550,10 @@ class ProgressIndicator : public Message_ProgressIndic
+ class ProgressIndicator : public Message_ProgressIndicator
+ {
+ public:
+-  explicit ProgressIndicator(vtkF3DOCCTReader* reader) { this->Reader = reader; }
++  explicit ProgressIndicator(vtkF3DOCCTReader* reader)
++  {
++    this->Reader = reader;
++  }
+ 
+ protected:
+   void Show(const Message_ProgressScope&, const Standard_Boolean) override
+@@ -493,7 +622,12 @@ int vtkF3DOCCTReader::RequestData(
+     if (success)
+     {
+       output->SetNumberOfBlocks(1);
++#if F3D_PLUGIN_OCCT_XCAF
++      const vtkSmartPointer<vtkPolyData> polydata =
++        this->Internals->CreateShape(shape, TDF_Label());
++#else
+       const vtkSmartPointer<vtkPolyData> polydata = this->Internals->CreateShape(shape);
++#endif
+       if (polydata && polydata->GetNumberOfCells() > 0)
+       {
+         output->SetBlock(1, polydata);
+@@ -522,7 +656,6 @@ int vtkF3DOCCTReader::RequestData(
+   }
+ 
+   this->Internals->ShapeTool = XCAFDoc_DocumentTool::ShapeTool(doc->Main());
+-  this->Internals->ColorTool = XCAFDoc_DocumentTool::ColorTool(doc->Main());
+ 
+   TDF_LabelSequence topLevelShapes;
+ 
+@@ -537,9 +670,9 @@ int vtkF3DOCCTReader::RequestData(
+     this->Internals->ShapeTool->GetShape(label, shape);
+ 
+     this->Internals->ShapeMap[this->Internals->GetHash(label)] =
+-      this->Internals->CreateShape(shape);
++      this->Internals->CreateShape(shape, label);
+ 
+-    double progress = 0.5 + static_cast<double>(iLabel) / topLevelShapes.Length();
++    double progress = 0.5 + (static_cast<double>(iLabel) / topLevelShapes.Length()) / 2;
+     this->InvokeEvent(vtkCommand::ProgressEvent, &progress);
+   }
+ 
diff --git a/graphics/f3d/pkg-plist b/graphics/f3d/pkg-plist
index fed49a57c97b..adc827863ddd 100644
--- a/graphics/f3d/pkg-plist
+++ b/graphics/f3d/pkg-plist
@@ -47,10 +47,11 @@ lib/libVTKExtensionsReaders.a
 lib/libVTKExtensionsRendering.a
 lib/libf3d.so
 lib/libf3d.so.2
-lib/libf3d.so.2.2
+lib/libf3d.so.2.3
 %%ALEMBIC%%share/applications/f3d-plugin-alembic.desktop
 %%ASSIMP%%share/applications/f3d-plugin-assimp.desktop
 %%DRACO%%share/applications/f3d-plugin-draco.desktop
+%%EXODUS%%share/applications/f3d-plugin-exodus.desktop
 share/applications/f3d-plugin-native.desktop
 %%OCCT%%share/applications/f3d-plugin-occt.desktop
 share/applications/f3d.desktop
@@ -69,6 +70,7 @@ share/icons/hicolor/48x48/apps/f3d.png
 share/icons/hicolor/64x64/apps/f3d.png
 share/icons/hicolor/scalable/apps/f3d.svg
 share/metainfo/app.f3d.F3D.metainfo.xml
+%%EXODUS%%share/mime/packages/f3d-exodus-formats.xml
 share/mime/packages/f3d-3d-formats.xml
 share/mime/packages/f3d-3d-image-formats.xml
 %%ALEMBIC%%share/mime/packages/f3d-alembic-formats.xml
@@ -79,6 +81,7 @@ share/mime/packages/f3d-vtk-formats.xml
 %%ALEMBIC%%share/thumbnailers/f3d-plugin-alembic.thumbnailer
 %%ASSIMP%%share/thumbnailers/f3d-plugin-assimp.thumbnailer
 %%DRACO%%share/thumbnailers/f3d-plugin-draco.thumbnailer
+%%EXODUS%%share/thumbnailers/f3d-plugin-exodus.thumbnailer
 share/thumbnailers/f3d-plugin-native.thumbnailer
 %%OCCT%%share/thumbnailers/f3d-plugin-occt.thumbnailer
 share/zsh/site-functions/_f3d