Jump to content
DavidJr.

Parsing and rendering contents of a 3MF file

Recommended Posts

I have a VCL app and I am able to draw cross sections at any specific Z height using SDL Components (RChart) just fine, but I need to view solid objects.  Some of the methods here are resused from that app.

Share this post


Link to post

Hi David, 

 

I do not look reading/parsing part, but 3d can not work as is : It missed several things.

 

Not specially in order : 

- There are no viewport : If you do 3d app on "standart" FMX window (2d canvas), you must add a viewport (TViewport3D).

- You can not use TModel as is, it is a pure loading object : In fact, do not use TModel3d 🙂 use mesh collection instead.

- The dpr try to include a "*.windows.fmx" which break the project on opening step. Not a nice way for onboarding 🙂

 

- After corrections in 3d part, here what I can say : 
---> I believe there are some caveat in your "multi object reading". It is loading, but object are broken (missed processing init for sure) 
---> In single mesh, There are some issues in scaling (which is certainly in the file !)

---> Beside that, In Single mesh, it generally load a the object correctly !

 

- Above, some pictures, far to be perfect, but hope that help.

- Note that, surprisingly, MSPaint3D handle very well this kind of file. I use it for my test.

- You will find somes FMX3D examples under my github here, could perhaps help you on certain FMX3D behaviours.

 

- Note for next time :

--> Please indicate your dependancy the OXmlSAX unit came certainly from here ? - It should be cool to indicate that, in order to easely find dependancy.

--> Why not included a basic 3mf file to reproduce your problem ? for testing your "multiobject" ?

 

 

Vincent


image.thumb.png.6e9cbc294609c495186d1b63e5a62aa6.pngimage.thumb.png.cd9ae48afbb279867c38b1a72611da80.png

Unit1.fmx

Unit1.pas

Share this post


Link to post

Hi,

 

Sorry for the delay.  I decided to try and use the Lib3MF SDK for the consortium but the existing Pascal unit did not work even after adding in type QWork:  Uint64 (see attached (Unit_Lib3MF.pas).  So I started on my own one (renamed with _custom) (also attached)..  but I have a feeling that I should be really using the original Unit_Lib3MF.pas file but not sure what I am doing wrong.  this is where I got the unit: GitHub - 3MFConsortium/lib3mf: lib3mf is an implementation of the 3D Manufacturing Format file standard

 

This actually renders the 3D model but I am not able to get the metadata just as Object name from the file using their lib.

Unit_Lib3MF.pas

GLCADViewer.dpr

GLCADViewer.dproj

ProjectGroup1.groupproj

Unit_Lib3MF_custom.pas

Unit1.fmx

Unit1.pas

Unit1.Windows.fmx

Share this post


Link to post
Posted (edited)

Hi

As I see, you have now correct FMX 3d project, good point. 🙂

 

I understand your direction, but now it is not the same problem : You tryed to rebuild an "official" header unit and it is not a way that I would fellow personnaly. (in a update/support effort long-term perspective)

--> Have you try to use directly  the *original* unit with fpc instead of delphi (as it is apparently write for fpc, actually) to see if you succeded to read your file correctly (I would make a simple fpc console app, which read and convert the 3MF file as needed) ? And if it work, you have several solutions :

- Adapt this unit to work with delphi (beware of type - perhaps it is better to try conversion tools such as Yunkot one or Chet one .

- Alternatively, work on a personalized and simplified dll in C or FPC directy, which consume the *original* dll with correct and, above all, well supported header.

 

Edited by Vincent Gsell

Share this post


Link to post
7 hours ago, Vincent Gsell said:

Hi

As I see, you have now correct FMX 3d project, good point. 🙂

 

I understand your direction, but now it is not the same problem : You tryed to rebuild an "official" header unit and it is not a way that I would fellow personnaly. (in a update/support effort long-term perspective)

--> Have you try to use directly  the *original* unit with fpc instead of delphi (as it is apparently write for fpc, actually) to see if you succeded to read your file correctly (I would make a simple fpc console app, which read and convert the 3MF file as needed) ? And if it work, you have several solutions :

- Adapt this unit to work with delphi (beware of type - perhaps it is better to try conversion tools such as Yunkot one or Chet one .

- Alternatively, work on a personalized and simplified dll in C or FPC directy, which consume the *original* dll with correct and, above all, well supported header.

 

actually I am testing a unit I converted from FPC now.  (see attached)

Unit_Lib3MF.zip

Share this post


Link to post

Sounds nice !

 

I look further into the unit's Wrapper, and it is quite complicated ! Honestly, the TLib3MFPolymorphicFactory made my day. 🙂

 

--> Please, It will be cool if you can post your basic demo again using this "final" Wrapper ? Perhaps only the code to build an FMX3D object with this wrapper ? 


thx, 

Vincent

Share this post


Link to post

juste a side note

I tryed to just initialized the wrapper with last unit, and found somes glitches very quickly (AV on D12 on GetReleaseInformation) 

 

For exemples : 

 

	function TLib3MFWrapper.GetPrereleaseInformation(out APrereleaseInfo: String): Boolean;
	var
		ResultHasPrereleaseInfo: Byte;
		bytesNeededPrereleaseInfo: Cardinal;
		bytesWrittenPrereleaseInfo: Cardinal;
		bufferPrereleaseInfo: array of Char;
	begin
		ResultHasPrereleaseInfo := 0;
		bytesNeededPrereleaseInfo:= 0;
		bytesWrittenPrereleaseInfo:= 0;
		CheckError(nil, Lib3MFGetPrereleaseInformationFunc(ResultHasPrereleaseInfo, 0, bytesNeededPrereleaseInfo, nil));
		SetLength(bufferPrereleaseInfo, bytesNeededPrereleaseInfo);
		CheckError(nil, Lib3MFGetPrereleaseInformationFunc(ResultHasPrereleaseInfo, bytesNeededPrereleaseInfo, bytesWrittenPrereleaseInfo, @bufferPrereleaseInfo[0]));
		Result := (ResultHasPrereleaseInfo <> 0);
		APrereleaseInfo := String(@bufferPrereleaseInfo[0]);
	end;

 

Before the last line, i'll put a basic 

 

    begin

       [...]

        Result := (ResultHasPrereleaseInfo <> 0);

        If Result then

              APrereleaseInfo := String(@bufferPrereleaseInfo[0]);
    end;

 

This should avoid AV if you access the string passed by var in this call...

 

Share this post


Link to post
Posted (edited)
On 8/9/2024 at 6:16 AM, Vincent Gsell said:

juste a side note

I tryed to just initialized the wrapper with last unit, and found somes glitches very quickly (AV on D12 on GetReleaseInformation) 

 

For exemples : 

 


	function TLib3MFWrapper.GetPrereleaseInformation(out APrereleaseInfo: String): Boolean;
	var
		ResultHasPrereleaseInfo: Byte;
		bytesNeededPrereleaseInfo: Cardinal;
		bytesWrittenPrereleaseInfo: Cardinal;
		bufferPrereleaseInfo: array of Char;
	begin
		ResultHasPrereleaseInfo := 0;
		bytesNeededPrereleaseInfo:= 0;
		bytesWrittenPrereleaseInfo:= 0;
		CheckError(nil, Lib3MFGetPrereleaseInformationFunc(ResultHasPrereleaseInfo, 0, bytesNeededPrereleaseInfo, nil));
		SetLength(bufferPrereleaseInfo, bytesNeededPrereleaseInfo);
		CheckError(nil, Lib3MFGetPrereleaseInformationFunc(ResultHasPrereleaseInfo, bytesNeededPrereleaseInfo, bytesWrittenPrereleaseInfo, @bufferPrereleaseInfo[0]));
		Result := (ResultHasPrereleaseInfo <> 0);
		APrereleaseInfo := String(@bufferPrereleaseInfo[0]);
	end;

 

Before the last line, i'll put a basic 

 

    begin

       [...]

        Result := (ResultHasPrereleaseInfo <> 0);

        If Result then

              APrereleaseInfo := String(@bufferPrereleaseInfo[0]);
    end;

 

This should avoid AV if you access the string passed by var in this call...

 

I actually fixed a lot of that unit already...      thanks!

Edited by DavidJr.

Share this post


Link to post
Posted (edited)

by the way,  I am having a problem trying to add each mesh and having the meshes not sit on top of each other.  I attached the test project,  try loading a 3MF file with multiple objects you will see what I mean.  This is just a test app.  I was hoping to share this so other can use it,  but its worthless if I cannot specify a different color per object (mesh) in one TModel3D instance.

Unit_Lib3MF.pas

Test_lib3mf_app.zip

 

if I add all vertices and triangles into one mesh and add then the positions and scale of each object (all merged into one mesh are perfect, but then I cannot apply different colors.

Edited by DavidJr.

Share this post


Link to post

I finally got time to go back to this code and test.  I was able to correct scaling,  but the positioning is wrong.  It seems that when adding multiple tmesh objects to one tdummy manual scaling is required,  and positioning.  Is there something I am missing about TDummy and/or TMesh that can make this a little easier?  IF you run this and do not select the checkbox it will add a Lib3MF Mesh objects to one TMesh (FMX) object and then everything renders at the correct scale and position, its when I try to add each 3Mf Mesh Object to its own TMesh (FMX object) that I get this problem.  I attached the full project with the DLL.

unit Unit1;

interface

uses
  Windows, System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  System.Math.Vectors, System.Generics.Collections, System.Math,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Objects,
  FMX.Controls.Presentation, FMX.StdCtrls, FMX.Viewport3D, FMX.Memo.Types,
  FMX.ScrollBox, FMX.Memo, FMX.Objects3D, FMX.Types3D, Unit_Lib3MF, FMX.Edit,
  FMX.Layouts, FMX.ListBox, FMX.Controls3D, FMX.MaterialSources;

const
  DLLName = 'lib3mf.dll';
  RCLASS = '3mf';
  WheelSensitive = 0.01;

type
  TPoint3DArray = TArray<TPoint3D>;
  TIndexArray = TArray<Integer>;
  TColorArray = array[1..6] of TAlphaColor;

  TForm1 = class(TForm)
    Viewport3D1: TViewport3D;
    btnLoadModel: TButton;
    OpenDialog1: TOpenDialog;
    Memo1: TMemo;
    ListBox1: TListBox;
    Camera: TCamera;
    Light: TLight;
    cbMultiColoredObjects: TCheckBox;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure btnLoadModelClick(Sender: TObject);
    procedure Viewport3D1MouseWheel(Sender: TObject; Shift: TShiftState; WheelDelta: Integer; var Handled: Boolean);
    procedure Viewport3D1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
    procedure Viewport3D1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Single);
    procedure Viewport3D1MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
    procedure ListBox1Change(Sender: TObject);
    procedure cbMultiColoredObjectsChange(Sender: TObject);
  private
    ModelLoad: Boolean;
    GlobalCenter, GlobalMinPoint, GlobalMaxPoint: TPoint3D;
    FLastMousePos: TPointF;
    FCameraDistance: Single;
    FMouseDown: TPointF;
    FIsPanning: Boolean;
    FIsRotating: Boolean;
    FColors: TColorArray;
    FLargestCenterPoint: TPoint3D;
    FMeshes: TDictionary<Integer, TMesh>;
    FObjectToBuildItemMap: TDictionary<Integer, TLib3MFBuildItem>;
    Lib3MF: TLib3MFWrapper;
    Lib3MFReader: TLib3MFReader;
    Model3MF: TLib3MFModel;
    Model3D: TDummy;//TModel3D;
    ObjectIterator: TLib3MFObjectIterator;
    Obj: TLib3MFObject;
    BuildItemIterator: TLib3MFBuildItemIterator;
    BuildItem: TLib3MFBuildItem;
    SliceStackIterator: TLib3MFSliceStackIterator;
    SliceStack: TLib3MFSliceStack;
    ObjRange: TArray<Integer>;
    FilePath3MF: String;

    function OutputLib3MFVersion: Boolean;
    function OpenFile3MF(FN: AnsiString): Boolean;
    procedure ShowThumbnailInformation(Model: TLib3MFModel);
    procedure ShowMetaDataInformation(MetaDataGroup: TLib3MFMetaDataGroup);
    procedure ShowSliceStack(SliceStack: TLib3MFSliceStack; Indent: string);
    procedure ShowObjectProperties(cadObj: TLib3MFObject);
    procedure ShowMeshObjectInformation(MeshObj: TLib3MFMeshObject);
    procedure ShowTransform(Transform: TLib3MFTransform; Indent: string);
    procedure ShowComponentsObjectInformation(ComponentsObj: TLib3MFComponentsObject);
    procedure SelectAllListBoxItems;
    function ExtractInfoFrom3MF(FileName: string): Boolean;

    // GRAPH METHODS:
    procedure ClearModel3D;
    procedure RenderSelectedObjects;

    procedure RenderCompositeMeshWithGhost;
    // All Meshes added as indiviual objects:
    procedure RenderCompositeMesh;
    // All Vertices and Trianlges (from each object) added into one Mesh:
    procedure RenderCompositeToOneMesh;
    procedure CalculateGlobalBoundingBox;
    procedure CreateGhostMesh;

    procedure SetupCamera;

  public

  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

procedure TForm1.btnLoadModelClick(Sender: TObject);
begin
  if OpenDialog1.Execute then
  begin
    FilePath3MF := OpenDialog1.FileName;
    ModelLoad := ExtractInfoFrom3MF(FilePath3MF);
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  ModelLoad := False;
  FColors[1] := TAlphaColorRec.Red;
  FColors[2] := TAlphaColorRec.Green;
  FColors[3] := TAlphaColorRec.Blue;
  FColors[4] := TAlphaColorRec.Yellow;
  FColors[5] := TAlphaColorRec.Cyan;
  FColors[6] := TAlphaColorRec.Magenta;

  Lib3MF := TLib3MFWrapper.Create(DLLName);

  FObjectToBuildItemMap := TDictionary<Integer, TLib3MFBuildItem>.Create;
  FMeshes := TDictionary<Integer, TMesh>.Create;

  Model3D := TDummy.Create(Self);
  Model3D.Parent := Viewport3D1;
  Model3D.HitTest := False;


  ListBox1.MultiSelect := True;

  if Assigned(Lib3MF) AND (Lib3MF is TLib3MFWrapper) then
  begin
    btnLoadModel.Enabled := OutputLib3MFVersion;
  end;
  SetupCamera;

end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  if Assigned(Lib3MF) then
    Lib3MF.Free;

  FObjectToBuildItemMap.Free;
end;

procedure TForm1.ListBox1Change(Sender: TObject);
begin
  RenderSelectedObjects;
end;

procedure TForm1.Viewport3D1MouseWheel(Sender: TObject; Shift: TShiftState; WheelDelta: Integer; var Handled: Boolean);
var
  NewScale: Single;
begin
  if Assigned(Model3D) then
  begin
    NewScale := Model3D.Scale.X + WheelDelta * WheelSensitive; // Use uniform scaling
    if NewScale > 0.01 then // Prevent scaling to zero or negative
    begin
      Model3D.Scale.X := NewScale;
      Model3D.Scale.Y := NewScale;
      Model3D.Scale.Z := NewScale;
      Form1.Caption := '3MF View (Scale: ' + NewScale.ToString + ')';
    end;
    Viewport3D1.Repaint;
    Handled := True;
  end;
end;

procedure TForm1.Viewport3D1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
begin
  FMouseDown := PointF(X, Y);
  if Button = TMouseButton.mbLeft then
  begin
    FIsPanning := True;
  end
  else if Button = TMouseButton.mbRight then
  begin
    FIsRotating := True;
  end;
end;

procedure TForm1.Viewport3D1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Single);
var
  DeltaX, DeltaY: Single;
begin
  if FIsRotating or FIsPanning then
  begin
    DeltaX := (X - FMouseDown.X) / Viewport3D1.Width;
    DeltaY := (Y - FMouseDown.Y) / Viewport3D1.Height;
    FMouseDown := PointF(X, Y);

    if FIsRotating then
    begin
      Model3D.RotationAngle.X := Model3D.RotationAngle.X + DeltaY * 360;
      Model3D.RotationAngle.Y := Model3D.RotationAngle.Y + DeltaX * 360;
      Viewport3D1.Repaint;
    end
    else if FIsPanning then
    begin
      Model3D.Position.X := Model3D.Position.X - DeltaX * 10;
      Model3D.Position.Y := Model3D.Position.Y + DeltaY * 10;
      Viewport3D1.Repaint;
    end;
  end;
end;

procedure TForm1.Viewport3D1MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
begin
  FIsPanning := False;
  FIsRotating := False;
end;

procedure TForm1.SetupCamera;
begin
  FCameraDistance := 2000;  // Initial camera distance
  Camera.Parent := Viewport3D1;

  // Position the camera
  Camera.Position.Point := Point3D(0, 0, FCameraDistance);
  Camera.RotationAngle.X := 0;
  Camera.RotationAngle.Y := 0;
  Camera.RotationAngle.Z := 0;

  Viewport3D1.Camera := Camera;

  // Set up a light
  Light := TLight.Create(Self);
  Light.Parent := Viewport3D1;
  Light.Position.Point := Point3D(0, 100, -100);
  Light.LightType := TLightType.Directional;

  Viewport3D1.AddObject(Light);
end;

function TForm1.OutputLib3MFVersion: Boolean;
var
  Major, Minor, Micro: Cardinal;
  PreReleaseInfo, BuildInfo: AnsiString;
begin
  Result := False;
  try
    Lib3MF.GetLibraryVersion(Major, Minor, Micro);
    Memo1.Lines.Add(Format('Lib3MF.Version = %d.%d.%d', [Major, Minor, Micro]));
    if Lib3MF.GetPrereleaseInformation(PreReleaseInfo) then
      Memo1.Lines.Add('-' + PreReleaseInfo);
    if Lib3MF.GetBuildInformation(BuildInfo) then
      Memo1.Lines.Add('+' + BuildInfo);
    Result := True;
  except
    Result := False;
  end;
end;

function TForm1.OpenFile3MF(FN: AnsiString): Boolean;
var
  i: Integer;
  WarningCount: Cardinal;
  WarningCode: Cardinal;
  WarningMessage: Widestring;
  ReaderClass: AnsiString;
begin
  Result := False;
  if Assigned(Lib3MF) and FileExists(FN) then
  begin
    try
      ReaderClass := RCLASS;
      Model3MF := Lib3MF.CreateModel;

      if Assigned(Model3MF) then
      begin
        Memo1.Lines.Add('Lib3MFModel created successfully.');
        Memo1.Lines.Add('Querying reader for class: ' + ReaderClass);

        Lib3MFReader := Model3MF.QueryReader(ReaderClass);

        if Assigned(Lib3MFReader) then
        begin
          Memo1.Lines.Add('Lib3MFReader created successfully.');

          Lib3MFReader.SetStrictModeActive(False);
          Lib3MFReader.ReadFromFile(FN);

          WarningCount := Lib3MFReader.GetWarningCount;

          if WarningCount > 0 then
          begin
            for i := 0 to WarningCount - 1 do
            begin
              WarningMessage := Lib3MFReader.GetWarning(i, WarningCode);
              Memo1.Lines.Add(Format('Encountered warning #%d : %s', [WarningCode, WarningMessage]));
            end;
          end
          else
          begin
            Memo1.Lines.Add('Encountered '+WarningCount.ToString+' warnings');
          end;

          Result := True;
        end
        else
          Memo1.Lines.Add('Failed to create Lib3MFReader.');
      end
      else
        Memo1.Lines.Add('Failed to create Lib3MFModel.');

    except
      on E: ELib3MFException do
      begin
        Memo1.Lines.Add(Format('Error loading 3MF file: %s', [E.Message]));
      end;
      on E: Exception do
      begin
        Memo1.Lines.Add(Format('Unexpected error: %s', [E.Message]));
      end;
    end;
  end
  else
    Memo1.Lines.Add('Library not initialized or file not found.');
end;

procedure TForm1.ShowThumbnailInformation(Model: TLib3MFModel);
begin
  // TODO: Implement this when available in Lib3MF
end;

procedure TForm1.ShowMetaDataInformation(MetaDataGroup: TLib3MFMetaDataGroup);
var
  i, MetaDataCount: Cardinal;
  MetaData: TLib3MFMetaData;
  MetaDataName, MetaDataValue: Widestring;
begin
  MetaDataCount := MetaDataGroup.GetMetaDataCount;

  if MetaDataCount > 0 then
  begin
    for i := 0 to MetaDataCount - 1 do
    begin
      MetaData := MetaDataGroup.GetMetaData(i);
      MetaDataName := MetaData.GetName;
      MetaDataValue := MetaData.GetValue;
      Memo1.Lines.Add(Format('Metadatum: %d:', [i]));
      Memo1.Lines.Add(Format('Name  = "%s"', [MetaDataName]));
      Memo1.Lines.Add(Format('Value = "%s"', [MetaDataValue]));
    end;
  end
  else
  begin
    Memo1.Lines.Add('No Metadata!');
  end;
end;

procedure TForm1.ShowSliceStack(SliceStack: TLib3MFSliceStack; Indent: string);
var
  i, SliceCount, SliceRefCount: Cardinal;
begin
  Memo1.Lines.Add(Format('%sSliceStackID: %d', [Indent, SliceStack.GetResourceID]));
  SliceCount := SliceStack.GetSliceCount;
  if SliceCount > 0 then
    Memo1.Lines.Add(Format('%s  Slice count: %d', [Indent, SliceCount]));

  SliceRefCount := SliceStack.GetSliceRefCount;
  if SliceRefCount > 0 then
  begin
    Memo1.Lines.Add(Format('%s  Slice ref count: %d', [Indent, SliceRefCount]));
    for i := 0 to SliceRefCount - 1 do
      Memo1.Lines.Add(Format('%s  Slice ref : %d', [Indent, SliceStack.GetSliceStackReference(i).GetResourceID]));
  end;
end;

procedure TForm1.ShowObjectProperties(cadObj: TLib3MFObject);
begin
  Memo1.Lines.Add(Format('   Name: "%s"', [cadObj.GetName]));
  Memo1.Lines.Add(Format('   PartNumber: "%s"', [cadObj.GetPartNumber]));

  case cadObj.GetType of
    eObjectTypeModel:
      Memo1.Lines.Add('   Object type: model');
    eObjectTypeSupport:
      Memo1.Lines.Add('   Object type: support');
    eObjectTypeSolidSupport:
      Memo1.Lines.Add('   Object type: solidsupport');
    eObjectTypeOther:
      Memo1.Lines.Add('   Object type: other');
  else
    Memo1.Lines.Add('   Object type: invalid');
  end;

  if cadObj.HasSlices(False) then
    ShowSliceStack(cadObj.GetSliceStack, '   ');

  if cadObj.GetMetaDataGroup.GetMetaDataCount > 0 then
    ShowMetaDataInformation(cadObj.GetMetaDataGroup);
end;

procedure TForm1.ShowMeshObjectInformation(MeshObj: TLib3MFMeshObject);
var
  BeamLattice: TLib3MFBeamLattice;
  VertexCount, TriangleCount, BeamCount, i: Cardinal;
  RepresentationMesh, ClippingMesh: Cardinal;
  ClipMode: TLib3MFBeamLatticeClipMode;
begin
  Memo1.Lines.Add(Format('mesh object #%d:', [MeshObj.GetResourceID]));
  ShowObjectProperties(MeshObj);

  VertexCount := MeshObj.GetVertexCount;
  TriangleCount := MeshObj.GetTriangleCount;
  BeamLattice := MeshObj.BeamLattice;

  Memo1.Lines.Add(Format('   Vertex count: %d', [VertexCount]));
  Memo1.Lines.Add(Format('   Triangle count: %d', [TriangleCount]));

  BeamCount := BeamLattice.GetBeamCount;
  if BeamCount > 0 then
  begin
    Memo1.Lines.Add(Format('   Beam count: %d', [BeamCount]));
    if BeamLattice.GetRepresentation(RepresentationMesh) then
      Memo1.Lines.Add(Format('   |_Representation Mesh ID: %d', [RepresentationMesh]));

    BeamLattice.GetClipping(ClipMode, ClippingMesh);
    if ClipMode <> eBeamLatticeClipModeNoClipMode then
      Memo1.Lines.Add(Format('   |_Clipping Mesh ID: %d (mode=%d)', [ClippingMesh, Ord(ClipMode)]));

    if BeamLattice.GetBeamSetCount > 0 then
      Memo1.Lines.Add(Format('   |_BeamSet count: %d', [BeamLattice.GetBeamSetCount]));
  end;
end;

procedure TForm1.ShowTransform(Transform: TLib3MFTransform; Indent: string);
begin
  Memo1.Lines.Add(Format('%sTransformation: [ %f %f %f %f ]', [Indent, Transform.FFields[0, 0], Transform.FFields[1, 0], Transform.FFields[2, 0], Transform.FFields[3, 0]]));
  Memo1.Lines.Add(Format('%s                [ %f %f %f %f ]', [Indent, Transform.FFields[0, 1], Transform.FFields[1, 1], Transform.FFields[2, 1], Transform.FFields[3, 1]]));
  Memo1.Lines.Add(Format('%s                [ %f %f %f %f ]', [Indent, Transform.FFields[0, 2], Transform.FFields[1, 2], Transform.FFields[2, 2], Transform.FFields[3, 2]]));
end;

procedure TForm1.ShowComponentsObjectInformation(ComponentsObj: TLib3MFComponentsObject);
var
  i: Cardinal;
  Component: TLib3MFComponent;
begin
  Memo1.Lines.Add(Format('components object #%d:', [ComponentsObj.GetResourceID]));
  ShowObjectProperties(ComponentsObj);

  Memo1.Lines.Add(Format('   Component count: %d', [ComponentsObj.GetComponentCount]));
  for i := 0 to ComponentsObj.GetComponentCount - 1 do
  begin
    Component := ComponentsObj.GetComponent(i);
    Memo1.Lines.Add(Format('   Component %d: Object ID: %d', [i, Component.GetObjectResourceID]));
    if Component.HasTransform then
      ShowTransform(Component.GetTransform, '                   ')
    else
      Memo1.Lines.Add('                   Transformation: none');
  end;
end;

procedure TForm1.SelectAllListBoxItems;
var
  i: Integer;
begin
  ListBox1.OnChange := nil;

  ListBox1.BeginUpdate;
  try
    for i := 0 to ListBox1.Items.Count - 1 do
    begin
      ListBox1.ListItems[i].IsSelected := True;
    end;
  finally
    ListBox1.EndUpdate;
  end;

  ListBox1.OnChange := ListBox1Change;
end;

function TForm1.ExtractInfoFrom3MF(FileName: string): Boolean;
var
  ObjName: Widestring;
  MeshObj: TLib3MFMeshObject;
  MinPoint, MaxPoint, CenterPoint: TPoint3D;
  I, VertexCount: Integer;
  Vertices: array of TPoint3D;
  CurrentObjectSize, LargestObjectSize: Single;
  LargestMinPoint, LargestMaxPoint: TPoint3D;
  ObjID: Integer;
begin
  Result := False;
  ClearModel3D;
  Memo1.Lines.Clear;
  LargestObjectSize := 0;
  LargestMinPoint := TPoint3D.Zero;
  LargestMaxPoint := TPoint3D.Zero;

  // Initialize global min and max points with extreme values
  GlobalMinPoint := TPoint3D.Create(MaxSingle, MaxSingle, MaxSingle);
  GlobalMaxPoint := TPoint3D.Create(-MaxSingle, -MaxSingle, -MaxSingle);

  Model3MF := Lib3MF.CreateModel;

  if (Not OpenFile3MF(FileName)) then
  begin
    Memo1.Lines.Add('Failed to load 3MF file.');
    Exit;
  end;

  ListBox1.Clear;

  // Populate the FObjectToBuildItemMap and determine the largest object
  BuildItemIterator := Model3MF.GetBuildItems;
  while BuildItemIterator.MoveNext do
  begin
    BuildItem := BuildItemIterator.GetCurrent;
    ObjID := BuildItem.GetObjectResourceID;
    FObjectToBuildItemMap.AddOrSetValue(ObjID, BuildItem);
  end;

  // Iterate through the objects to find the largest object and update global boundaries
  ObjectIterator := Model3MF.GetObjects;
  while ObjectIterator.MoveNext do
  begin
    Obj := ObjectIterator.GetCurrentObject;
    ObjName := Obj.GetName;
    if ObjName = '' then
      ObjName := 'Object ' + Obj.GetResourceID.ToString;

    // Store object ID in ListBox item
    ListBox1.Items.Add(ObjName);
    ListBox1.ListItems[ListBox1.Items.Count - 1].Tag := Obj.GetResourceID;

    if Obj.IsMeshObject then
    begin
      MeshObj := TLib3MFMeshObject(Obj);

      VertexCount := MeshObj.GetVertexCount;
      SetLength(Vertices, VertexCount);

      // Extract vertices from the 3MF mesh object
      for I := 0 to VertexCount - 1 do
      begin
        Vertices[I].X := MeshObj.GetVertex(I).FCoordinates[0];
        Vertices[I].Y := MeshObj.GetVertex(I).FCoordinates[1];
        Vertices[I].Z := MeshObj.GetVertex(I).FCoordinates[2];
      end;

      // Calculate bounding box and center point
      if Length(Vertices) > 0 then
      begin
        MinPoint := Vertices[0];
        MaxPoint := Vertices[0];

        for I := 1 to High(Vertices) do
        begin
          MinPoint.X := Min(MinPoint.X, Vertices[I].X);
          MinPoint.Y := Min(MinPoint.Y, Vertices[I].Y);
          MinPoint.Z := Min(MinPoint.Z, Vertices[I].Z);

          MaxPoint.X := Max(MaxPoint.X, Vertices[I].X);
          MaxPoint.Y := Max(MaxPoint.Y, Vertices[I].Y);
          MaxPoint.Z := Max(MaxPoint.Z, Vertices[I].Z);
        end;

        CenterPoint.X := (MinPoint.X + MaxPoint.X) / 2;
        CenterPoint.Y := (MinPoint.Y + MaxPoint.Y) / 2;
        CenterPoint.Z := (MinPoint.Z + MaxPoint.Z) / 2;

        // Determine the size of the current object
        CurrentObjectSize := Max(MaxPoint.X - MinPoint.X, Max(MaxPoint.Y - MinPoint.Y, MaxPoint.Z - MinPoint.Z));

        // Update global boundaries
        GlobalMinPoint.X := Min(GlobalMinPoint.X, MinPoint.X);
        GlobalMinPoint.Y := Min(GlobalMinPoint.Y, MinPoint.Y);
        GlobalMinPoint.Z := Min(GlobalMinPoint.Z, MinPoint.Z);

        GlobalMaxPoint.X := Max(GlobalMaxPoint.X, MaxPoint.X);
        GlobalMaxPoint.Y := Max(GlobalMaxPoint.Y, MaxPoint.Y);
        GlobalMaxPoint.Z := Max(GlobalMaxPoint.Z, MaxPoint.Z);

        // Check if this object is the largest
        if CurrentObjectSize > LargestObjectSize then
        begin
          LargestObjectSize := CurrentObjectSize;
          FLargestCenterPoint := CenterPoint;
          LargestMinPoint := MinPoint;
          LargestMaxPoint := MaxPoint;
        end;
      end;
    end;
  end;

  // After processing all objects, render them
  SelectAllListBoxItems;

  if cbMultiColoredObjects.IsChecked then
    begin
      RenderSelectedObjects;
    end
  else
    begin
      RenderCompositeToOneMesh;
    end;

  Memo1.Lines.Add('done');

  Result := (ListBox1.Items.Count > 0);
end;


// GRAPH METHODS:
procedure TForm1.ClearModel3D;
var
  i: Integer;
begin
  // Iterate through all the children and free each one
  for i := Model3D.ChildrenCount - 1 downto 0 do
  begin
    Model3D.Children[i].Free;
  end;

  // Alternatively, you can clear all children at once
  //Model3D.Clear;
end;

procedure TForm1.RenderSelectedObjects;
var
  i: Integer;
  ObjID: Integer;
  MeshObj: TLib3MFMeshObject;
begin
  ClearModel3D;
  FMeshes.Clear;

  CalculateGlobalBoundingBox;

  RenderCompositeMeshWithGhost;

  Viewport3D1.Repaint;
end;


procedure TForm1.CalculateGlobalBoundingBox;
var
  I, J: Integer;
  MeshObj: TLib3MFMeshObject;
begin
  // Initialize global bounding box
  GlobalMinPoint := TPoint3D.Create(MaxSingle, MaxSingle, MaxSingle);
  GlobalMaxPoint := TPoint3D.Create(-MaxSingle, -MaxSingle, -MaxSingle);

  // Iterate through all objects (regardless of selection) to calculate the global bounding box
  for I := 0 to ListBox1.Count - 1 do
  begin
    MeshObj := TLib3MFMeshObject(FObjectToBuildItemMap[ListBox1.ListItems[I].Tag].GetObjectResource);

    for J := 0 to MeshObj.GetVertexCount - 1 do
    begin
      GlobalMinPoint.X := Min(GlobalMinPoint.X, MeshObj.GetVertex(J).FCoordinates[0]);
      GlobalMinPoint.Y := Min(GlobalMinPoint.Y, MeshObj.GetVertex(J).FCoordinates[1]);
      GlobalMinPoint.Z := Min(GlobalMinPoint.Z, MeshObj.GetVertex(J).FCoordinates[2]);

      GlobalMaxPoint.X := Max(GlobalMaxPoint.X, MeshObj.GetVertex(J).FCoordinates[0]);
      GlobalMaxPoint.Y := Max(GlobalMaxPoint.Y, MeshObj.GetVertex(J).FCoordinates[1]);
      GlobalMaxPoint.Z := Max(GlobalMaxPoint.Z, MeshObj.GetVertex(J).FCoordinates[2]);
    end;
  end;

  // Calculate the global center of the entire model
  GlobalCenter := (GlobalMinPoint + GlobalMaxPoint) * 0.5;
end;

procedure TForm1.cbMultiColoredObjectsChange(Sender: TObject);
begin
  if ModelLoad then
    begin
      ClearModel3D;

      if cbMultiColoredObjects.IsChecked then
        begin
          RenderCompositeMesh;
        end
      else
        begin
          RenderCompositeToOneMesh;
        end;

    end;
end;

procedure TForm1.CreateGhostMesh;
var
  GhostMesh: TMesh;
  Material: TColorMaterialSource;
  Vertices: array[0..7] of TPoint3D;
  Indices: array of Integer;
  I: Integer;
begin
  GhostMesh := TMesh.Create(Self);

  // Clear any transformations on TDummy to avoid scaling/positioning issues
  Model3D.Position.Point := TPoint3D.Zero;
  Model3D.Scale.Point := TPoint3D.Create(1.0, 1.0, 1.0);
  Model3D.RotationAngle.Point := TPoint3D.Zero;

  GhostMesh.Parent := Model3D;

  // Define the 8 corners of the bounding box based on the global min/max points
  Vertices[0] := TPoint3D.Create(GlobalMinPoint.X, GlobalMinPoint.Y, GlobalMinPoint.Z);
  Vertices[1] := TPoint3D.Create(GlobalMaxPoint.X, GlobalMinPoint.Y, GlobalMinPoint.Z);
  Vertices[2] := TPoint3D.Create(GlobalMaxPoint.X, GlobalMaxPoint.Y, GlobalMinPoint.Z);
  Vertices[3] := TPoint3D.Create(GlobalMinPoint.X, GlobalMaxPoint.Y, GlobalMinPoint.Z);
  Vertices[4] := TPoint3D.Create(GlobalMinPoint.X, GlobalMinPoint.Y, GlobalMaxPoint.Z);
  Vertices[5] := TPoint3D.Create(GlobalMaxPoint.X, GlobalMinPoint.Y, GlobalMaxPoint.Z);
  Vertices[6] := TPoint3D.Create(GlobalMaxPoint.X, GlobalMaxPoint.Y, GlobalMaxPoint.Z);
  Vertices[7] := TPoint3D.Create(GlobalMinPoint.X, GlobalMaxPoint.Y, GlobalMaxPoint.Z);

  // Define the indices for the triangles to form the 12 edges of the bounding box
  SetLength(Indices, 36);
  Indices[0] := 0; Indices[1] := 1; Indices[2] := 2;
  Indices[3] := 2; Indices[4] := 3; Indices[5] := 0;
  Indices[6] := 4; Indices[7] := 5; Indices[8] := 6;
  Indices[9] := 6; Indices[10] := 7; Indices[11] := 4;
  Indices[12] := 0; Indices[13] := 1; Indices[14] := 5;
  Indices[15] := 5; Indices[16] := 4; Indices[17] := 0;
  Indices[18] := 2; Indices[19] := 3; Indices[20] := 7;
  Indices[21] := 7; Indices[22] := 6; Indices[23] := 2;
  Indices[24] := 0; Indices[25] := 3; Indices[26] := 7;
  Indices[27] := 7; Indices[28] := 4; Indices[29] := 0;
  Indices[30] := 1; Indices[31] := 2; Indices[32] := 6;
  Indices[33] := 6; Indices[34] := 5; Indices[35] := 1;

  // Set the vertex and index data to the ghost mesh
  GhostMesh.Data.VertexBuffer.Length := Length(Vertices);
  GhostMesh.Data.IndexBuffer.Length := Length(Indices);

  for I := 0 to High(Vertices) do
    GhostMesh.Data.VertexBuffer.Vertices[I] := Vertices[I];

  for I := 0 to High(Indices) do
    GhostMesh.Data.IndexBuffer.Indices[I] := Indices[I];

  GhostMesh.Visible := False;

  // Position and scale the ghost mesh
  GhostMesh.Position.Point := TPoint3D.Zero;
  GhostMesh.RotationAngle.Point := TPoint3D.Zero;
  GhostMesh.Scale.Point := TPoint3D.Create(1.0, 1.0, 1.0);

  Model3D.AddObject(GhostMesh);
end;

procedure TForm1.RenderCompositeMeshWithGhost;
begin
  //CalculateGlobalBoundingBox; // Step 1: Calculate the bounding box
  //CreateGhostMesh;            // Step 2: Create the ghost mesh

  if cbMultiColoredObjects.IsChecked then
    begin
      RenderCompositeMesh;
    end
  else
    begin
      RenderCompositeToOneMesh;
    end;
end;

procedure TForm1.RenderCompositeMesh;
var
  Mesh: TMesh;
  Material: TColorMaterialSource;
  VertexCount, TriangleCount, I, J: Cardinal;
  Vertices: array of TPoint3D;
  Indices: array of Integer;
  MeshObj: TLib3MFMeshObject;
  ColorIndex: Integer;
  LocalPosition, ObjectMinPoint, ObjectMaxPoint,
  ObjectCenter, LargestCenter, Offset, GlobalPosition: TPoint3D;
  ObjectVolume, LargestVolume, ScaleFactor: Single;
begin
  ColorIndex := 1;
  ClearModel3D;  // Clear previous meshes

  LargestVolume := 0;
  LargestCenter := TPoint3D.Zero;

  // First Pass: Find the largest mesh object by volume and calculate the bounding box for each
  for I := 0 to ListBox1.Count - 1 do
  begin
    MeshObj := TLib3MFMeshObject(FObjectToBuildItemMap[ListBox1.ListItems[I].Tag].GetObjectResource);

    // Calculate the bounding box of the mesh object
    ObjectMinPoint := TPoint3D.Create(MaxSingle, MaxSingle, MaxSingle);
    ObjectMaxPoint := TPoint3D.Create(-MaxSingle, -MaxSingle, -MaxSingle);

    VertexCount := MeshObj.GetVertexCount;
    for J := 0 to VertexCount - 1 do
    begin
      LocalPosition.X := MeshObj.GetVertex(J).FCoordinates[0];
      LocalPosition.Y := MeshObj.GetVertex(J).FCoordinates[1];
      LocalPosition.Z := MeshObj.GetVertex(J).FCoordinates[2];

      ObjectMinPoint.X := Min(ObjectMinPoint.X, LocalPosition.X);
      ObjectMinPoint.Y := Min(ObjectMinPoint.Y, LocalPosition.Y);
      ObjectMinPoint.Z := Min(ObjectMinPoint.Z, LocalPosition.Z);

      ObjectMaxPoint.X := Max(ObjectMaxPoint.X, LocalPosition.X);
      ObjectMaxPoint.Y := Max(ObjectMaxPoint.Y, LocalPosition.Y);
      ObjectMaxPoint.Z := Max(ObjectMaxPoint.Z, LocalPosition.Z);
    end;

    // Compute object volume as width * height * depth
    ObjectVolume := (ObjectMaxPoint.X - ObjectMinPoint.X) *
                    (ObjectMaxPoint.Y - ObjectMinPoint.Y) *
                    (ObjectMaxPoint.Z - ObjectMinPoint.Z);

    // Update the largest object volume and center if necessary
    if ObjectVolume > LargestVolume then
    begin
      LargestVolume := ObjectVolume;
      LargestCenter := (ObjectMinPoint + ObjectMaxPoint) * 0.5;  // Find the center of the largest object
    end;
  end;

  // Second Pass: Create, scale, and position each mesh
  for I := 0 to ListBox1.Count - 1 do
  begin
    MeshObj := TLib3MFMeshObject(FObjectToBuildItemMap[ListBox1.ListItems[I].Tag].GetObjectResource);

    VertexCount := MeshObj.GetVertexCount;
    TriangleCount := MeshObj.GetTriangleCount;

    SetLength(Vertices, VertexCount);
    SetLength(Indices, TriangleCount * 3);

    // Create a new TMesh for this object
    Mesh := TMesh.Create(Self);
    Mesh.Parent := Model3D;  // Parent the mesh to the TDummy

    // Extract vertices for the mesh object
    for J := 0 to VertexCount - 1 do
    begin
      LocalPosition.X := MeshObj.GetVertex(J).FCoordinates[0];
      LocalPosition.Y := MeshObj.GetVertex(J).FCoordinates[1];
      LocalPosition.Z := MeshObj.GetVertex(J).FCoordinates[2];

      Vertices[J] := LocalPosition;
    end;

    // Set up indices for the mesh
    for J := 0 to TriangleCount - 1 do
    begin
      Indices[J * 3] := MeshObj.GetTriangle(J).FIndices[0];
      Indices[J * 3 + 1] := MeshObj.GetTriangle(J).FIndices[1];
      Indices[J * 3 + 2] := MeshObj.GetTriangle(J).FIndices[2];
    end;

    // Assign vertices and indices to the mesh
    Mesh.Data.VertexBuffer.Length := VertexCount;
    Mesh.Data.IndexBuffer.Length := TriangleCount * 3;

    for J := 0 to High(Vertices) do
      Mesh.Data.VertexBuffer.Vertices[J] := Vertices[J];

    for J := 0 to High(Indices) do
      Mesh.Data.IndexBuffer.Indices[J] := Indices[J];

    // Calculate volume of the current mesh
    ObjectMinPoint := TPoint3D.Create(MaxSingle, MaxSingle, MaxSingle);
    ObjectMaxPoint := TPoint3D.Create(-MaxSingle, -MaxSingle, -MaxSingle);

    for J := 0 to VertexCount - 1 do
    begin
      ObjectMinPoint.X := Min(ObjectMinPoint.X, Vertices[J].X);
      ObjectMinPoint.Y := Min(ObjectMinPoint.Y, Vertices[J].Y);
      ObjectMinPoint.Z := Min(ObjectMinPoint.Z, Vertices[J].Z);

      ObjectMaxPoint.X := Max(ObjectMaxPoint.X, Vertices[J].X);
      ObjectMaxPoint.Y := Max(ObjectMaxPoint.Y, Vertices[J].Y);
      ObjectMaxPoint.Z := Max(ObjectMaxPoint.Z, Vertices[J].Z);
    end;

    ObjectVolume := (ObjectMaxPoint.X - ObjectMinPoint.X) *
                    (ObjectMaxPoint.Y - ObjectMinPoint.Y) *
                    (ObjectMaxPoint.Z - ObjectMinPoint.Z);

    // Calculate scale factor relative to the largest object using cubic root of volume ratio
    if LargestVolume > 0 then
    begin
      ScaleFactor := Power(ObjectVolume / LargestVolume, 1/3);
      Mesh.Scale.Point := TPoint3D.Create(ScaleFactor, ScaleFactor, ScaleFactor);
    end;

    // Calculate object center
    ObjectCenter := (ObjectMinPoint + ObjectMaxPoint) * 0.5;

    // Calculate the offset to position the object relative to the largest object's center
    Offset := (ObjectCenter - LargestCenter) * ScaleFactor;
    GlobalPosition := Mesh.LocalToAbsolute3D(Offset);

    Mesh.Position.Point := GlobalPosition;

    // Apply material and color
    Material := TColorMaterialSource.Create(Self);

    if ListBox1.ListItems[I].IsSelected then
    begin
      Material.Color := FColors[ColorIndex];
    end
    else
    begin
      Material.Color := TAlphaColorRec.White;
    end;

    Mesh.MaterialSource := Material;

    // Cycle through colors for each mesh
    ColorIndex := (ColorIndex mod Length(FColors)) + 1;

    // Add the mesh to the TDummy
    Model3D.AddObject(Mesh);
  end;

  // Repaint the viewport to reflect the changes
  Viewport3D1.Repaint;
end;

{
procedure TForm1.RenderCompositeMesh;
var
  Mesh: TMesh;
  Material: TColorMaterialSource;
  VertexCount, TriangleCount, I, J: Cardinal;
  Vertices: array of TPoint3D;
  Indices: array of Integer;
  MeshObj: TLib3MFMeshObject;
  ColorIndex: Integer;
  ObjectMinPoint, ObjectMaxPoint, ObjectCenter, MeshPosition: TPoint3D;
  GlobalMinPoint, GlobalMaxPoint, GlobalCenter: TPoint3D;
  ScaleFactor: Single;
  LocalPosition: TPoint3D;
begin
  ColorIndex := 1;
  ClearModel3D;  // Clear previous meshes in TDummy

  // Initialize global bounding box for all objects
  GlobalMinPoint := TPoint3D.Create(MaxSingle, MaxSingle, MaxSingle);
  GlobalMaxPoint := TPoint3D.Create(-MaxSingle, -MaxSingle, -MaxSingle);

  // First, calculate global bounding box of all objects
  for I := 0 to ListBox1.Count - 1 do
  begin
    if ListBox1.ListItems[I].IsSelected then
    begin
      MeshObj := TLib3MFMeshObject(FObjectToBuildItemMap[ListBox1.ListItems[I].Tag].GetObjectResource);

      VertexCount := MeshObj.GetVertexCount;
      SetLength(Vertices, VertexCount);

      // Update the global bounding box based on each object
      for J := 0 to VertexCount - 1 do
      begin
        LocalPosition.X := MeshObj.GetVertex(J).FCoordinates[0];
        LocalPosition.Y := MeshObj.GetVertex(J).FCoordinates[1];
        LocalPosition.Z := MeshObj.GetVertex(J).FCoordinates[2];

        // Update global bounding box
        GlobalMinPoint.X := Min(GlobalMinPoint.X, LocalPosition.X);
        GlobalMinPoint.Y := Min(GlobalMinPoint.Y, LocalPosition.Y);
        GlobalMinPoint.Z := Min(GlobalMinPoint.Z, LocalPosition.Z);

        GlobalMaxPoint.X := Max(GlobalMaxPoint.X, LocalPosition.X);
        GlobalMaxPoint.Y := Max(GlobalMaxPoint.Y, LocalPosition.Y);
        GlobalMaxPoint.Z := Max(GlobalMaxPoint.Z, LocalPosition.Z);
      end;
    end;
  end;

  // Calculate global center and size
  GlobalCenter := (GlobalMinPoint + GlobalMaxPoint) * 0.5;
  ScaleFactor := 1.0 / Max(GlobalMaxPoint.X - GlobalMinPoint.X, Max(GlobalMaxPoint.Y - GlobalMinPoint.Y, GlobalMaxPoint.Z - GlobalMinPoint.Z));

  // Re-center the camera based on global bounding box
  Camera.Position.Point := Point3D(GlobalCenter.X, GlobalCenter.Y, GlobalMaxPoint.Z + 500); // Adjust the Z distance to keep it visible
  Camera.RotationAngle.Point := Point3D(0, 0, 0); // Ensure the camera is facing straight

  // Now render each object individually with correct translation and scaling
  for I := 0 to ListBox1.Count - 1 do
  begin
    if ListBox1.ListItems[I].IsSelected then
    begin
      MeshObj := TLib3MFMeshObject(FObjectToBuildItemMap[ListBox1.ListItems[I].Tag].GetObjectResource);

      VertexCount := MeshObj.GetVertexCount;
      TriangleCount := MeshObj.GetTriangleCount;

      SetLength(Vertices, VertexCount);
      SetLength(Indices, TriangleCount * 3);

      // Create a new TMesh for this object
      Mesh := TMesh.Create(Self);
      Mesh.Parent := Model3D;

      // Calculate local bounding box for this object
      ObjectMinPoint := TPoint3D.Create(MaxSingle, MaxSingle, MaxSingle);
      ObjectMaxPoint := TPoint3D.Create(-MaxSingle, -MaxSingle, -MaxSingle);

      for J := 0 to VertexCount - 1 do
      begin
        LocalPosition.X := MeshObj.GetVertex(J).FCoordinates[0];
        LocalPosition.Y := MeshObj.GetVertex(J).FCoordinates[1];
        LocalPosition.Z := MeshObj.GetVertex(J).FCoordinates[2];

        Vertices[J] := (LocalPosition - GlobalCenter) * ScaleFactor; // Scale and translate vertices

        // Update local bounding box
        ObjectMinPoint.X := Min(ObjectMinPoint.X, LocalPosition.X);
        ObjectMinPoint.Y := Min(ObjectMinPoint.Y, LocalPosition.Y);
        ObjectMinPoint.Z := Min(ObjectMinPoint.Z, LocalPosition.Z);

        ObjectMaxPoint.X := Max(ObjectMaxPoint.X, LocalPosition.X);
        ObjectMaxPoint.Y := Max(ObjectMaxPoint.Y, LocalPosition.Y);
        ObjectMaxPoint.Z := Max(ObjectMaxPoint.Z, LocalPosition.Z);
      end;

      // Calculate center for this mesh object and adjust position
      ObjectCenter := (ObjectMinPoint + ObjectMaxPoint) * 0.5;
      Mesh.Position.Point := (ObjectCenter - GlobalCenter) * ScaleFactor;

      // Set up indices for the mesh
      for J := 0 to TriangleCount - 1 do
      begin
        Indices[J * 3] := MeshObj.GetTriangle(J).FIndices[0];
        Indices[J * 3 + 1] := MeshObj.GetTriangle(J).FIndices[1];
        Indices[J * 3 + 2] := MeshObj.GetTriangle(J).FIndices[2];
      end;

      // Assign vertices and indices to the mesh
      Mesh.Data.VertexBuffer.Length := VertexCount;
      Mesh.Data.IndexBuffer.Length := TriangleCount * 3;

      for J := 0 to High(Vertices) do
        Mesh.Data.VertexBuffer.Vertices[J] := Vertices[J];

      for J := 0 to High(Indices) do
        Mesh.Data.IndexBuffer.Indices[J] := Indices[J];

      // Apply material and color
      Material := TColorMaterialSource.Create(Self);
      Material.Color := FColors[ColorIndex];
      Mesh.MaterialSource := Material;

      // Cycle through colors for each mesh
      ColorIndex := (ColorIndex mod Length(FColors)) + 1;

      // Add the mesh to the TDummy
      Model3D.AddObject(Mesh);
    end;
  end;

  // Repaint the viewport to reflect the changes
  Viewport3D1.Repaint;
end; }


procedure TForm1.RenderCompositeToOneMesh;
var
  CombinedMesh: TMesh;
  Material: TColorMaterialSource;
  VertexCount, TriangleCount, I, J, K, GlobalVertexIndex: Cardinal;
  Vertices: array of TPoint3D;
  Indices: array of Integer;
  MeshObj: TLib3MFMeshObject;
  CombinedVertices: array of TPoint3D;
  CombinedIndices: array of Integer;
  TotalVertexCount, TotalTriangleCount: Cardinal;
begin
  TotalVertexCount := 0;
  TotalTriangleCount := 0;

  // First pass: calculate total vertex and triangle count
  for I := 0 to ListBox1.Count - 1 do
  begin
    if ListBox1.ListItems[I].IsSelected then
    begin
      MeshObj := TLib3MFMeshObject(FObjectToBuildItemMap[ListBox1.ListItems[I].Tag].GetObjectResource);
      TotalVertexCount := TotalVertexCount + MeshObj.GetVertexCount;
      TotalTriangleCount := TotalTriangleCount + MeshObj.GetTriangleCount;
    end;
  end;

  // Initialize arrays for combined vertices and indices
  SetLength(CombinedVertices, TotalVertexCount);
  SetLength(CombinedIndices, TotalTriangleCount * 3);

  GlobalVertexIndex := 0;  // To track the global vertex index as we combine

  // Second pass: copy vertices and indices into the combined arrays
  for I := 0 to ListBox1.Count - 1 do
  begin
    if ListBox1.ListItems[I].IsSelected then
    begin
      MeshObj := TLib3MFMeshObject(FObjectToBuildItemMap[ListBox1.ListItems[I].Tag].GetObjectResource);

      VertexCount := MeshObj.GetVertexCount;
      TriangleCount := MeshObj.GetTriangleCount;

      // Get the vertices from the current object
      SetLength(Vertices, VertexCount);
      for J := 0 to VertexCount - 1 do
      begin
        Vertices[J].X := MeshObj.GetVertex(J).FCoordinates[0];
        Vertices[J].Y := MeshObj.GetVertex(J).FCoordinates[1];
        Vertices[J].Z := MeshObj.GetVertex(J).FCoordinates[2];

        // Store the vertex into the combined array
        CombinedVertices[GlobalVertexIndex + J] := Vertices[J];
      end;

      // Get the indices from the current object and update with global vertex index
      SetLength(Indices, TriangleCount * 3);
      for J := 0 to TriangleCount - 1 do
      begin
        Indices[J * 3] := MeshObj.GetTriangle(J).FIndices[0] + GlobalVertexIndex;
        Indices[J * 3 + 1] := MeshObj.GetTriangle(J).FIndices[1] + GlobalVertexIndex;
        Indices[J * 3 + 2] := MeshObj.GetTriangle(J).FIndices[2] + GlobalVertexIndex;
      end;

      // Copy the indices into the combined array
      for K := 0 to High(Indices) do
        CombinedIndices[(GlobalVertexIndex div VertexCount) * (TriangleCount * 3) + K] := Indices[K];

      // Update the global vertex index for the next object
      GlobalVertexIndex := GlobalVertexIndex + VertexCount;
    end;
  end;

  // Create the combined mesh and add it to the viewport
  CombinedMesh := TMesh.Create(Self);
  CombinedMesh.Parent := Model3D;
  CombinedMesh.Data.VertexBuffer.Length := TotalVertexCount;
  CombinedMesh.Data.IndexBuffer.Length := TotalTriangleCount * 3;

  // Copy vertices and indices into the mesh
  for I := 0 to High(CombinedVertices) do
    CombinedMesh.Data.VertexBuffer.Vertices[I] := CombinedVertices[I];

  for I := 0 to High(CombinedIndices) do
    CombinedMesh.Data.IndexBuffer.Indices[I] := CombinedIndices[I];

  // Apply material and color to the combined mesh
  Material := TColorMaterialSource.Create(Self);
  Material.Color := TAlphaColors.Skyblue;  // Set to a default color for now
  CombinedMesh.MaterialSource := Material;

  // Add the mesh to the scene
  Model3D.AddObject(CombinedMesh);
end;

end.

 

Test_lib3mf_app.zip

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×