DavidJr. 1 Posted July 10 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 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 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 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 (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 by Vincent Gsell Share this post Link to post
DavidJr. 1 Posted August 7 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 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 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 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 (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 by DavidJr. Share this post Link to post
DavidJr. 1 Posted August 29 (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 by DavidJr. Share this post Link to post
DavidJr. 1 Posted October 8 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 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