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 13:07]
emoc [Utilisation de ln]
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 62: Ligne 86:
 ===== 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 115: Ligne 139:
 Ç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.+**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. 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.
  
Ligne 121: Ligne 146:
   mv teapot ../​bin/​teapot ​      # déplacer dans le dossier ~/go/bin   mv teapot ../​bin/​teapot ​      # déplacer dans le dossier ~/go/bin
   ~/​go/​bin/​teapot ​              # lancer la commande depuis le répertoire courant   ~/​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.1762690043.txt.gz
  • Dernière modification: 2025/11/09 13:07
  • par emoc