Posts mit dem Label 3d werden angezeigt. Alle Posts anzeigen
Posts mit dem Label 3d werden angezeigt. Alle Posts anzeigen

Samstag, 3. Oktober 2026

Signalleuchten mit ESP-Now (ESP32-C6)

Für nächste Woche habe ich ein gezieltes Projekt, dass ich unbedingt fertig haben will, bevor mein Urlaub beginnt. Neben Hardware, die zum Teil bestellt ist, fehlt mir noch das passende Gehäuse, um einen XIAO ESP32-C6, eine RGB LED und einen Lipo Accu zu verstauen.

Statt Claude, habe ich in diesen fall mit Gemini versucht ein Inspirendes Bild zu generieren.  

Zugegeben, das Bild sieht super aus, dennoch fehlt mir was, ob wohl ich wenigere komplixität hätte. Damit meine ich nicht die Verzierungen, die könnte ich wiederum im 3D Druck mit umsetzen.

Donnerstag, 17. September 2026

Neues Druck Projekt gestartet

 

Letztes Jahr habe ich eine kleine Nachbildung des LEGO Technic Abschlepper Truck in 300% größe gedruckt. Ein fertiges Model hatte ich bereits im Internet gefunden, jedoch reichte mir das nicht und ich habe diesen noch mit einem Motor, Fahrtenregler und Servo erweitert, um dieses in ein RC Auto aufzuwerten. Funktioniert gut und reicht für innerhalb der Wohnung und man kann damit auch gut draußen fahren, solange die Fahrt nicht in der heißen Sonne ist. Warum? Das Modell ahbe ich mit PLA gedruckt.

Nun hatte ich letzten diese Kettenteile gefunden zum Ausdrucken und konnte das nicht lassen, dieses Bauteil in Maßstab 3:1 oder halt 300% groß zu drucken. Und ich will mehr und nun habe ich mit der Planung angefangen.

 

Abgesehen von der Batteriebox, dem Empfänger, die zwei Motoren und einge Modifikationen an weiteren Teilen, könnte ich anfangen zu drucken.

 

Aber bevor ich wirklich anfangen kann, muss ich noch Filament einkaufen.  Die Frage ist nur wie viel Filament benötige ich für mein Projekt.

Geht man von den Standard Einstellungen für PTEG Basic und den Einstellungen durch, komme ich allein für Schwarzes Filament auf eine gerundete Netto von 3080g. 

Da ich von Einstellungs- und Fehlerdrucke ausgehen, kann ich gleich 4Kg einplanen. Könnte reichen, denn mit dem Bambulab Drucker habe ich bisher wenig Fehldrucke gehabt mit PETG, nachdem ich mir eine Smooth PEI Platte gekauft habe.

Motor, Batteriekasten, Empfänger und Modifikationen fehlen noch im Plan. Lässt man die zunächst aus, komme ich jetzt bereits auf 188h 308h Netto Druckzeit und 5,95Kg 8,15kg Netto Filament Gewicht. Mit Sicherheit werde ich mehr als 200h Druckzeit benötigen und dann kommen noch die Bearbeitungszeit für die Modifikationen an den Teilen, um Kugellager, Motor und weitere Elektronik zu verbauen.

Ein paar Teile habe ich schon und aus reiner Neugier habe ich die Hülle des Technic Motors mit Transparenten Filament gedruckt. Wird sicherlich eine Tolle Deckolampe im Motorendesign.

Der Plan ist, bis Ende des Jahres fertig zu werden. Da ich erst nächsten Monat anfangen kann, bleiben mir also 3 Monate bis zur Fertigstellung. Ob das was wird, steht in den Sternen.

Sonntag, 10. Februar 2019

3d Karte Bewegen (MonoGame)

Zur Vollständigkeit zu meinem letzten Blog-Post wird das Beispiel in 3d Abgebildet. Abgesehen von der 3d Ansicht und Verwendung einer 3d Kamera ist die Steuerung nahe zu gleich, so das ich hier die Ausführung des Inputs auslasse.


Welches 3d Format
Normalerweise werden *.fbx und *.x 3d Formate unterstützt. Für mich war es jedoch neu, daß auch Wavefront *.obj 3d Format unterstütz wird. Das erfreuliche ist, daß z.B. viele frei verwendete 3d Modell in diesem Format vorliegen. Das erspart fehlerhafte Konvertierungen mit anderen Programmen. Wie immer verwende ich hier die Assets von Kenny.nl die nicht nur Texturen anbieten, sondern auch 3d Modell in *.obj und teilweise in *.fbx.


Pipeline 3d Importer
Vermutlich ist das noch ein Fehler in MonoGame Version 3.7.1. Wenn das 3d Model im Pipline Tool hinzugefügt wird, dann werden einige initiale Einstellungen gesetzt die bei einen Punkt manuell umgestellt werden muss.
Bei dem Wavefront Format, liegt neben der *.obj Datei auch eine *.mtl. Diese brauchen wir zunächst nicht in das Pipeline Tool hinzufügen.



Klickt ihr auf die Datei, dann werden in Properties die Einstellungen angezeigt. Hier wurde eigentlich der richtige Importer Ausgewählt. Dies las ich auch in einigen Foren Einträgen.



Fehler beim Laden des 3d Models
Falls ihr einen Ausnahmefehler erhält, das beim Laden des 3d Models auftritt.
Could not find ContentTypeReader Type. Please ensure the name of the Assembly..... Naja, ist im folgenden Bild zu sehen.


Kommt also dieser Ausnahmefehler, dann sollte der Importer von Open Asset Import Library - MonoGame umgestellt werden auf Fbx Importer - MonoGame.


Und nicht vergessen, nach dem Hinzufügen einmal auf F6 oder Build (Rebuild) klicken.


Laden des 3d Models
In das Unterverzeichnis zu Component werden die ComponentMap.cs und PlateGrassTile.cs angelegt.


Das verwendete 3d Model in dem Beispiel ist einfach eine Grüne Platte und soll dann später eine größere Fläche Abbilden. Im folgenden Code ist zu sehen, dass das 3d Objekt gedreht wird um 90 Grad. Aus der Gewohnheit her, ist Z hier die Höhe und Y die Tiefe. In Büchern wird oft Z als Tiefe und Y als Höhe angeben, was auch Technisch gesehen auch richtig ist.
Das im Konstruktor der Parameter 'Scale' angegeben wird, findet erst in im nächsten Post eine Verwendung.

public class PlateGrassTile  
 {  
   private Vector3 _offsetPosition;  
   private readonly float _scale = 1;  
   private readonly Model _model;  
   private readonly Matrix[] _transform;  
   private readonly ModelMesh _mesh;  
   private readonly bool _onlyOneMesh = false;  
   private Vector3 _offsetRotation;  
   public Vector3 Position { set; get; } = new Vector3();  
   public PlateGrassTile(Vector3 offsetPosition, float scale, Model model)  
   {  
     this._offsetPosition = offsetPosition;  
     var offsetRotateX = MathHelper.ToRadians(90);  
     this._offsetRotation = new Vector3(offsetRotateX, 0, 0);   
     this._scale = scale;  
     this._model = model;  
     this._transform = new Matrix[this._model.Bones.Count];  
     this._model.CopyAbsoluteBoneTransformsTo(this._transform);  
     this._mesh = this._model.Meshes.First();  
     this._onlyOneMesh = !this._model.Meshes.Any();  
   }  
   public void Draw(Matrix view, Matrix projection)  
   {  
     foreach (ModelMesh mesh in this._model.Meshes)  
     {  
       foreach (BasicEffect effect in mesh.Effects)  
       {  
         foreach (EffectPass effectPass in effect.CurrentTechnique.Passes)  
         {  
           effectPass.Apply();  
         }  
         effect.EnableDefaultLighting();  
         this.SetTransform(effect, mesh);  
         effect.View = view;  
         effect.Projection = projection;  
       }  
       mesh.Draw();  
     }  
   }  
   private void SetTransform(BasicEffect effect, ModelMesh mesh)  
   {  
     effect.World = this._transform[mesh.ParentBone.Index] *  
             Matrix.CreateRotationX(this._offsetRotation.X) *  
             Matrix.CreateRotationY(this._offsetRotation.Y) *  
             Matrix.CreateRotationZ(this._offsetRotation.Z) *  
             Matrix.CreateTranslation(this.Position + this._offsetPosition) *  
             Matrix.CreateScale(this._scale);  
   }  
 } 

Karte erstellen
Die Kartenerstellung wird hier simple gehalten und besteht daher aus 3x3 Felder. Für das Beispiel haben die Objekte einen kleinen Abstand zu einander. So kann man sehen, daß die neun Grasflächen erzeugt wurden. Ansonsten einfach in der Methode 'GetPosition' die Variable 'distance' auf '0f' setzten. Das 3d Objekt wird hier Initial geladen und dessen Referenz zusammen mit der Position zugewiesen in das 'PlateGrassTile' Klassen Objekt.

public class ComponentMap : GameComponent  
 {  
   private const float _speed = 0.3f;  
   private readonly ComponentInputs _componentInputs;  
   private PlateGrassTile[] _groundTiles = new PlateGrassTile[9];  
   public ComponentMap(Game game, ComponentInputs componentInputs) : base(game)  
   {  
     this._componentInputs = componentInputs;  
   }  
   public override void Initialize()  
   {  
     var grass = this.Game.Content.Load("Plate_Grass_01");  
     int index = 0;  
     for (int iY = 0; iY < 3; iY++)  
     {  
       for (int iX = 0; iX < 3; iX++)  
       {  
         this._groundTiles[index] = new PlateGrassTile(this.GetPosition(iX, iY),  
                                 0.5f,   
                                 grass);  
         index++;  
       }  
     }  
   }  
   public override void Update(GameTime gameTime)  
   {  
     foreach (var item in this._groundTiles)  
     {  
       item.Position += new Vector3(this._componentInputs.Inputs.MoveX,   
                       this._componentInputs.Inputs.MoveY, 0)   
                       * _speed;   
     }  
   }  
   public void DrawContent(Matrix view, Matrix projection)  
   {  
     foreach (var item in this._groundTiles)  
     {  
       item.Draw(view, projection);  
     }  
   }  
   private Vector3 GetPosition(int x, int y)  
   {  
     float mapLength = 3f;  
     float distance = .02f;  
     float centerMap = (mapLength + distance)   
               * (float)Math.Sqrt(this._groundTiles.Length) / 2; ;  
     return new Vector3((y * (mapLength + distance)) - centerMap,  
               (x * (mapLength + distance)) - centerMap,   
               0);  
   }  
 }  

Kamera
So richtig entscheiden konnte ich mich an der stelle nicht, wie ich die Komponente nenne. Render oder Camera. Es wird gerendert, aber auch die Kameraausrichtung vorgenommen. Auftrennen wäre hier zunächst übertrieben, dennoch macht es Sinn die wesentlichen Aspekte der Kamera zu einer Klasse zusammen zufassen.



Die Klasse 'CameraView.cs' setz alle Einstellungen in der Initialize Methode fest. Initial muß auch die AspectRatio gesetzt werden, da mit der UWP vorher nicht festgelegt wird, welche Auflösung verwendet wird.

 public class CameraView  
 {  
   private readonly Game _game;  
   private readonly ComponentMap _componentContent;  
   public Matrix View { get; private set; }  
   public Matrix Projection { get; private set; }  
   public CameraView(Game game, ComponentMap componentMap)  
   {  
     this._game = game;  
     this._componentContent = componentMap;  
   }  
   public void Initialize()  
   {  
     var aspectRatio = this.GetAspectRatio();  
     var position = new Vector3(-1f, 5f, 5f);  
     var target = new Vector3(0, 0, 0);  
     var farPlaneDistance = 10000;  
     this.View = Matrix.CreateLookAt(position, target, Vector3.Backward);  
     this.Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,  
                                   aspectRatio,  
                                   1,  
                                   farPlaneDistance);  
   }  
   public void Draw()  
   {  
     this._componentContent.DrawContent(this.View, this.Projection);  
   }  
   private float GetAspectRatio()  
   {  
     var w = (float)ApplicationView.GetForCurrentView().VisibleBounds.Width;  
     var h = (float)ApplicationView.GetForCurrentView().VisibleBounds.Height;  
     return w / h;  
   }  
 } 

Die 'ComponentRender' Klasse ist hier sehr übersichtlich und hat hier auch sonst keine Besonderheit. Mit dem nächsten Blogpost wird jedoch dieser Bereich erweitert.

 public class ComponentRender : DrawableGameComponent  
 {  
   private readonly CameraView _cameraView;  
   public ComponentRender(Game game, ComponentMap componentContent) : base(game)  
   {  
     this._cameraView = new CameraView(game, componentContent);  
     this._cameraView.Initialize();  
   }  
   public override void Draw(GameTime gameTime)  
   {  
     this.GraphicsDevice.Clear(Color.CornflowerBlue);  
     this._cameraView.Draw();  
   }  
 }  

Der Rest des Codes ist nahe zu Identisch wie bei dem letzten Post, wie man eine 2d Karte bewegt.

Nachwort
Diese Ausführung funktioniert noch gut auf dem Raspberry Pi 3. Hektische Spiele sollten man denn noch nicht anversieren.


Ameisen Simulation und andere Dinge

Wer lange sich schon mit C# beschäftig, hat sicherlich schon mal was von AntMe gelesen oder gehört. Diese Idee hatte ich aufgegriffen und mi...