Différences

Ci-dessous, les différences entre deux révisions de la page.

Lien vers cette vue comparative

Les deux révisions précédentes Révision précédente
Prochaine révision
Révision précédente
recherche:residence_polygones:mesh2svg2paper [2025/11/09 12:56]
emoc [Utilisation de Simplify]
recherche:residence_polygones:mesh2svg2paper [2025/11/09 20:44] (Version actuelle)
emoc [Conversion de formats 3D en ligne de commande]
Ligne 7: Ligne 7:
 **Conseil de Laurent : utiliser «ln» de Michael Fogleman** : https://​github.com/​fogleman/​ln **Conseil de Laurent : utiliser «ln» de Michael Fogleman** : https://​github.com/​fogleman/​ln
 C'est programmé en Go, jamais utilisé C'est programmé en Go, jamais utilisé
 +
 +Pour la suite j'​utilise l'​objet teapot.obj extrait du [[https://​www.cs.utah.edu/​~natevm/​newell_teaset/​newell_teaset.zip|newell_teaset.zip]]
 +
 +{{:​recherche:​residence_polygones:​teapot.obj.png?​direct&​800|}}
  
 ===== Conversion de formats 3D en ligne de commande ===== ===== Conversion de formats 3D en ligne de commande =====
Ligne 22: Ligne 26:
   * Geomview object file format (.off),   * Geomview object file format (.off),
   * VRML 2.0 - export only (.wrl).   * VRML 2.0 - export only (.wrl).
 +Exemple :
 +  ctmconv parasect.obj parasect.stl
 +
 +===== Infos sur un objet 3D en ligne de commande =====
 +
 +Nombre de points, de faces, etc.
 +
 +Avec **assimp-utils**
 +  sudo apt install assimp-utils
 +  assimp info teapot.obj
 +  ​
 +Assimp pour Open Asset Import Library
 +  * https://​github.com/​assimp/​assimp
 +  * https://​the-asset-importer-lib-documentation.readthedocs.io/​en/​latest/​
 +===== Affichage d'​objets STL =====
 +
 +Avec GMSH : https://​gmsh.info/​ qui est aussi capable d'une multitude d'​autres choses (en GUI ou CLI)
 +
 +{{:​recherche:​residence_polygones:​gmsh.png?​direct&​600|}}
 +
  
 ===== Installation de Go ===== ===== Installation de Go =====
Ligne 59: Ligne 83:
 Maintenant on peut déclencher la commande avec  Maintenant on peut déclencher la commande avec 
   ~/​go/​bin/​helloworld   ~/​go/​bin/​helloworld
-==== Utilisation de Simplify ====+   
 +===== Utilisation de Simplify ​=====
  
-Simplify est un logiciel en ligne de commande de Michael Fogleman qui permet de réduire le nombre de faces d'un objet 3D. Simplify est programmé en Go+Simplify est un logiciel en ligne de commande de Michael Fogleman qui permet de réduire le nombre de faces d'un objet 3D **au format .STL**. Simplify est programmé en Go
  
 https://​github.com/​fogleman/​simplify https://​github.com/​fogleman/​simplify
Ligne 74: Ligne 99:
  
 {{:​recherche:​residence_polygones:​parasect_comparaison_reduction_de_faces.png?​direct&​800|}} {{:​recherche:​residence_polygones:​parasect_comparaison_reduction_de_faces.png?​direct&​800|}}
-==== Utilisation de ln ====+ 
 + 
 +===== Utilisation de ln =====
  
 Pour transformer un objet 3D au format .OBJ en fichier .SVG Pour transformer un objet 3D au format .OBJ en fichier .SVG
Ligne 111: Ligne 138:
   go run teapot.go   go run teapot.go
 Ça marche! Le fichier svg est créé, en fonction du point de vue défini dans le script go, les faces qui doivent l'​être sont cachées. Ça marche! Le fichier svg est créé, en fonction du point de vue défini dans le script go, les faces qui doivent l'​être sont cachées.
 +
 +**Transformer en exécutable.** \\
 +
 +La commande est lancée depuis le répertoire courant dans lequel se trouve le fichier teapot.obj, les fichiers résultants (teapot.png et teapot.svg) sont créés dans le répertoire courant.
 +
 +  go build -o teapot ​           # construire le binaire
 +  mv teapot ../​bin/​teapot ​      # déplacer dans le dossier ~/go/bin
 +  ~/​go/​bin/​teapot ​              # lancer la commande depuis le répertoire courant
 +  ​
 +On obtient
 +
 +{{:​recherche:​residence_polygones:​teapot_dans_inkscape.png?​direct&​800|}}
 +
 +Extrait du fichier svg
 +<code svg>
 +<svg width="​1024.000000"​ height="​1024.000000"​ version="​1.1"​ baseProfile="​full"​ xmlns="​http://​www.w3.org/​2000/​svg">​
 +<g transform="​translate(0,​1024.000000) scale(1,​-1)">​
 +<​polyline stroke="​black"​ fill="​none"​ points="​628.113702,​626.372774 630.057369,​626.470582"​ />
 +<​polyline stroke="​black"​ fill="​none"​ points="​630.057369,​626.470582 612.007059,​629.402582"​ />
 +<​polyline stroke="​black"​ fill="​none"​ points="​646.867425,​619.146177 645.594080,​623.083557"​ />
 +<​polyline stroke="​black"​ fill="​none"​ points="​645.594080,​623.083557 641.262088,​622.941587"​ />
 +<​polyline stroke="​black"​ fill="​none"​ points="​639.714178,​622.890858 645.594080,​623.083557"​ />
 +<​polyline stroke="​black"​ fill="​none"​ points="​645.594080,​623.083557 630.057369,​626.470582"​ />
 +<​polyline stroke="​black"​ fill="​none"​ points="​646.867425,​619.146177 659.738739,​615.250381"​ />
 +<​polyline stroke="​black"​ fill="​none"​ points="​659.738739,​615.250381 658.331336,​619.276179"​ />
 +... etc.
 +</​code>​
 +
 +En manipulant, on dirait bien que les tracés sont doublés
 +
 +===== obj2svg =====
 +
 +Je cherche à créer une commande qui soit accessible de n'​importe où qui permette de transformer un objet 3D au format .OBJ en image png et fichier SVG __du maillage__
 +
 +Créer le dossier et le fichier
 +  mkdir test_obj2svg
 +  cd test_obj2svg
 +  touch obj2svg.go ​  # puis l'​éditer
 +
 +<​accordion>​
 +<panel title="​obj2svg.go (cliquer pour afficher le code)">​
 +<code go obj2svg.go>​
 +
 +<code go>
 +package main
 +
 +import (
 + "​fmt"​
 + "​flag"​
 +
 + "​github.com/​fogleman/​ln/​ln"​
 +)
 +
 +func main() {
 +
 + // Parsing des arguments ​
 +
 + flag.Parse()
 + args := flag.Args()
 + if len(args) != 1 {
 + fmt.Println("​Usage:​ obj2svg input.obj -> créera 2 fichiers input.obj.png et input.obj.svg"​)
 + return
 + }
 +
 + pngfilename := args[0] + "​.png"​
 + svgfilename := args[0] + "​.svg"​
 +
 + fmt.Printf("​pngfilename %s\n", pngfilename)
 + fmt.Printf("​svgfilename %s\n", svgfilename)
 +
 + scene := ln.Scene{}
 + fmt.Printf("​Loading %s\n", args[0])
 + mesh, err := ln.LoadOBJ(args[0])
 + if err != nil {
 + panic(err)
 + }
 + mesh.UnitCube()
 + scene.Add(ln.NewTransformedShape(mesh,​ ln.Rotate(ln.Vector{0,​ 1, 0}, 0.5)))
 + // scene.Add(mesh)
 + eye := ln.Vector{-0.5,​ 0.5, 2}
 + center := ln.Vector{}
 + up := ln.Vector{0,​ 1, 0}
 + width := 1024.0
 + height := 1024.0
 + paths := scene.Render(eye,​ center, up, width, height, 35, 0.1, 100, 0.01)
 + paths.WriteToPNG(pngfilename,​ width, height)
 + paths.WriteToSVG(svgfilename,​ width, height)
 +}
 +</​code>​
 +</​panel>​
 +</​accordion>​
 +
 +Puis
 +  go mod init example/​obj2svg ​              # initialiser le module
 +  go mod tidy                               # charger les dépendances
 +  go run obj2svg.go teapot.obj ​             # ok, tout fonctionne ​
 +  go build -o obj2svg ​                      # construire l'​exécutable
 +  mv obj2svg ../​bin/​obj2svg ​                # le placer dans le bon dossier
 +  # Maintenant on peut exécuter la commande suivante dans n'​importe quel dossier
 +  ~/​go/​bin/​obj2svg teapot.obj
 +
 +**TODO : permettre la rotation de la vue**
 +
 +===== rendu wireframe avec blender CLI + gif =====
 +
 +{{:​recherche:​residence_polygones:​teapot_wire.gif?​direct|}}
 +
 +Script python blender à utiliser en ligne de commande avec 
 +  blender --background --python blender_teapot_wireframe_views.py
 +
 +<​accordion>​
 +<panel title="​blender_teapot_wireframe_views.py (cliquer pour afficher le code)">​
 +<code python blender_teapot_wireframe_views.py>​
 +# Blender 3.4.1 
 +# Debian 12 @ tenko
 +#​ 20251109,​ résidence polygones @ Fablab des portes logiques
 +
 +import bpy
 +import math
 +
 +# -------------------------------
 +# Rendu wireframe "​propre"​ 600x600
 +# -------------------------------
 +
 +# Supprimer tous les objets existants
 +bpy.ops.wm.read_factory_settings(use_empty=True)
 +
 +# Importer le STL
 +bpy.ops.import_mesh.stl(filepath="​teapot.stl"​)
 +obj = bpy.context.selected_objects[0]
 +
 +# Supprimer tous les matériaux existants
 +obj.data.materials.clear()
 +
 +# Ajouter un modifier wireframe
 +mod = obj.modifiers.new(name="​WireframeMod",​ type='​WIREFRAME'​)
 +mod.thickness = 0.02  # épaisseur des lignes
 +
 +# Créer un matériau noir shadeless pour le wireframe
 +mat = bpy.data.materials.new(name="​WireMat"​)
 +mat.diffuse_color = (0, 0, 0, 1)
 +mat.use_nodes = True
 +bsdf = mat.node_tree.nodes.get("​Principled BSDF")
 +bsdf.inputs['​Base Color'​].default_value = (0, 0, 0, 1)
 +bsdf.inputs['​Specular'​].default_value = 0
 +bsdf.inputs['​Roughness'​].default_value = 1
 +obj.data.materials.append(mat)
 +
 +# Ajouter une caméra
 +cam_data = bpy.data.cameras.new(name="​Camera"​)
 +cam_object = bpy.data.objects.new("​Camera",​ cam_data)
 +bpy.context.collection.objects.link(cam_object)
 +bpy.context.scene.camera = cam_object
 +
 +# Paramètres de rendu
 +scene = bpy.context.scene
 +scene.render.image_settings.file_format = '​PNG'​
 +scene.render.resolution_x = 600
 +scene.render.resolution_y = 600
 +scene.render.film_transparent = False  # fond blanc
 +# scene.render.film_transparent_glass = False
 +
 +# Désactiver l’anti-aliasing
 +# scene.render.use_antialiasing = False
 +scene.render.engine = '​BLENDER_EEVEE' ​ # moteur Eevee plus simple
 +# Eevee anti-aliasing quasi désactivé
 +scene.eevee.taa_render_samples = 1
 +
 +# Récupérer la scène
 +scene = bpy.context.scene
 +
 +# Créer un monde si nécessaire
 +if scene.world is None:
 +    world = bpy.data.worlds.new("​World"​)
 +    scene.world = world
 +
 +# Couleur de fond blanc
 +scene.world.use_nodes = True
 +bg = scene.world.node_tree.nodes['​Background'​]
 +bg.inputs['​Color'​].default_value = (1, 1, 1, 1)  # blanc
 +
 +# Centrer la caméra autour de l'​objet
 +center = obj.location
 +
 +# Paramètres rotation
 +n_views = 30
 +radius = 10   # distance caméra
 +elevation = 5
 +
 +for i in range(n_views):​
 +    angle = 2 * math.pi * i / n_views
 +    cam_object.location.x = center.x + radius * math.cos(angle)
 +    cam_object.location.y = center.y + radius * math.sin(angle)
 +    cam_object.location.z = center.z + elevation
 +    ​
 +    # Orienter la caméra vers le centre
 +    direction = center - cam_object.location
 +    rot_quat = direction.to_track_quat('​-Z',​ '​Y'​)
 +    cam_object.rotation_euler = rot_quat.to_euler()
 +    ​
 +    # Nom du fichier
 +    scene.render.filepath = f"​teapot_wire_{i:​02d}.png"​
 +    ​
 +    # Rendu
 +    bpy.ops.render.render(write_still=True)
 +</​code>​
 +</​panel>​
 +</​accordion>​
 +
 +Ensuite on peut assembler les images avec 
 +  convert teapot_wire_*.png -threshold 50% -colors 2 -resize 600x600 teapot_wire.gif
 +
 +{{:​recherche:​residence_polygones:​teapot_facewire.gif?​direct|}}
 +  ​
 +Version alternative qui affiche également les faces (et masque les faces cachées)
 +  blender --background --python blender_teapot_facewire.py ​                                      # calculer les rendus d'​image
 +  convert teapot_facewire_*.png -threshold 50% -colors 2 -resize 300x300 teapot_facewire.gif ​    # préparer l'​animation
 +
 +<​accordion>​
 +<panel title="​blender_teapot_facewire.py (cliquer pour afficher le code)">​
 +<code python blender_teapot_facewire.py>​
 +# Blender 3.4.1 
 +# Debian 12 @ tenko
 +#​ 20251109,​ résidence polygones @ Fablab des portes logiques
 +
 +import bpy
 +import math
 +
 +# -------------------------------
 +# Configuration de la scène
 +# -------------------------------
 +
 +# Supprimer tous les objets existants
 +bpy.ops.wm.read_factory_settings(use_empty=True)
 +
 +# Importer le STL
 +bpy.ops.import_mesh.stl(filepath="​teapot.stl"​)
 +obj = bpy.context.selected_objects[0]
 +
 +# Supprimer tous les matériaux existants
 +obj.data.materials.clear()
 +
 +# -------------------------------
 +# Matériau blanc pour les faces
 +# -------------------------------
 +mat = bpy.data.materials.new("​FaceWhite"​)
 +mat.use_nodes = True
 +bsdf = mat.node_tree.nodes["​Principled BSDF"]
 +bsdf.inputs['​Base Color'​].default_value = (1, 1, 1, 1)  # blanc
 +bsdf.inputs['​Specular'​].default_value = 0
 +obj.data.materials.append(mat)
 +
 +# -------------------------------
 +# Matériau Wireframe noir
 +# -------------------------------
 +# Ajouter un modifier wireframe
 +mod = obj.modifiers.new(name="​WireframeMod",​ type='​WIREFRAME'​)
 +mod.thickness = 0.02
 +mod.use_replace = False  # conserve faces originales
 +
 +# Création d’un second matériau pour le wireframe
 +wire_mat = bpy.data.materials.new("​WireBlack"​)
 +wire_mat.use_nodes = True
 +nodes = wire_mat.node_tree.nodes
 +bsdf_wire = nodes.get("​Principled BSDF")
 +bsdf_wire.inputs['​Base Color'​].default_value = (0, 0, 0, 1)  # noir
 +bsdf_wire.inputs['​Specular'​].default_value = 0
 +obj.data.materials.append(wire_mat)
 +
 +# Associer le modifier wireframe au matériau noir
 +mod.material_offset = 1  # utilise le second matériau
 +
 +# -------------------------------
 +# Caméra
 +# -------------------------------
 +cam_data = bpy.data.cameras.new(name="​Camera"​)
 +cam_object = bpy.data.objects.new("​Camera",​ cam_data)
 +bpy.context.collection.objects.link(cam_object)
 +bpy.context.scene.camera = cam_object
 +
 +# Paramètres de rendu
 +scene = bpy.context.scene
 +scene.render.image_settings.file_format = '​PNG'​
 +scene.render.resolution_x = 600
 +scene.render.resolution_y = 600
 +scene.render.film_transparent = False  # fond blanc
 +scene.render.engine = '​BLENDER_EEVEE'​
 +scene.eevee.taa_render_samples = 1  # anti-aliasing minimal
 +
 +# Fond blanc
 +if scene.world is None:
 +    world = bpy.data.worlds.new("​World"​)
 +    scene.world = world
 +scene.world.use_nodes = True
 +bg = scene.world.node_tree.nodes['​Background'​]
 +bg.inputs['​Color'​].default_value = (1, 1, 1, 1)  # blanc
 +
 +# -------------------------------
 +# Paramètres rotation
 +# -------------------------------
 +center = obj.location
 +n_views = 30
 +radius = 10
 +elevation = 5
 +
 +# -------------------------------
 +# Générer les images
 +# -------------------------------
 +for i in range(n_views):​
 +    angle = 2 * math.pi * i / n_views
 +    cam_object.location.x = center.x + radius * math.cos(angle)
 +    cam_object.location.y = center.y + radius * math.sin(angle)
 +    cam_object.location.z = center.z + elevation
 +    ​
 +    # Orienter la caméra vers le centre de l'​objet
 +    direction = center - cam_object.location
 +    rot_quat = direction.to_track_quat('​-Z',​ '​Y'​)
 +    cam_object.rotation_euler = rot_quat.to_euler()
 +    ​
 +    # Nom du fichier
 +    scene.render.filepath = f"​teapot_facewire_{i:​02d}.png"​
 +    ​
 +    # Rendu
 +    bpy.ops.render.render(write_still=True)
 +</​code>​
 +</​panel>​
 +</​accordion>​
 +===== Autres trucs intéressants à essayer =====
 +
 +**removeduplicatelines** : une extension inkscape qui enlève les segments dupliqués : https://​cutlings.datafil.no/​inkscape-extension-removeduplicatelines/​ \\
 +
 +**deduplicate** plugin vpype pour enlever les lignes en doublon dans un fichier svg https://​github.com/​LoicGoulefert/​deduplicate
 +
 +**occult** plugin vpype pour masquer les faces cachées d'un fichier svg https://​github.com/​LoicGoulefert/​occult
 +
 +**vpype** «vpype is an extensible CLI pipeline utility which aims to be the Swiss Army knife for creating, modifying and/or optimizing plotter-ready vector graphics» https://​vpype.readthedocs.io/​en/​latest/​install.html#​linux
  • recherche/residence_polygones/mesh2svg2paper.1762689367.txt.gz
  • Dernière modification: 2025/11/09 12:56
  • par emoc