DavidJr. 1 Posted July 10, 2024 Hi, with verified (using 3rd party software) 3MF files I am able to load the data, however the TModel3D even with TLight I am just getting a blank window with only the TButton. What am I missing? I have attache pretty much everything I have. David Unit1.pas Unit1.fmx GLCADViewer.dpr GLCADViewer.dproj Share this post Link to post
DavidJr. 1 Posted July 10, 2024 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
Vincent Gsell 11 Posted July 16, 2024 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 Unit1.fmx Unit1.pas Share this post Link to post
DavidJr. 1 Posted August 6, 2024 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
Vincent Gsell 11 Posted August 7, 2024 (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 August 7, 2024 by Vincent Gsell Share this post Link to post
DavidJr. 1 Posted August 7, 2024 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
DavidJr. 1 Posted August 7, 2024 Well I got the Lib3mf unit (a delphi specific one I created to work. Delphi binding · Issue #385 · 3MFConsortium/lib3mf (github.com) They may include it in their repo. I discovered they had typos in their version too. Delphi seems to be overly neglected by most SDK developers. Share this post Link to post
Vincent Gsell 11 Posted August 9, 2024 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
Vincent Gsell 11 Posted August 9, 2024 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
DavidJr. 1 Posted August 29, 2024 (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 August 29, 2024 by DavidJr. Share this post Link to post
DavidJr. 1 Posted August 29, 2024 (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 August 29, 2024 by DavidJr. Share this post Link to post
DavidJr. 1 Posted October 8, 2024 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
Vincent Gsell 11 Posted November 7, 2024 Hi, I am sorry, your project does not give result on my side. (range error and va..) Back to october, I work a little on that, with the dll and your header, but restart from scratch, with my tools. With exemple provide by the site, and docs I found on it. -> Actually, It's far from complete, but I got now model rendered correctly, on scale and positionning. - I do not support beamlatice and slice. - material (indexed and ressource color, as well as textured one) should be supported later. Here is a capture bellow : Are you more or less advanced ? Share this post Link to post