Un corrigé pédagogique pour comprendre et justifier vos réponses. Les conseils de rédaction ne constituent pas un barème officiel détaillé.
Application
Objets 3D, arêtes et références partagées
Un fichier OBJ décrit un objet par son nom (o), ses sommets (v) et ses faces (f). Exemple : quatre sommets du plan z=0, puis f 1 2 3 4 décrivent une face carrée. Dans les classes Python fournies, les faces contiennent en réalité des références vers des instances de Sommet, malgré la description initiale en termes d’indices. Cette différence est centrale pour comprendre le défaut de changement d’échelle.
o cube
v 0.0 0.0 0.0
v 0.0 1.0 0.0
v 1.0 1.0 0.0
v 1.0 0.0 0.0
f 1 2 3 4Le sujet utilise des coordonnées entières et une unité géométrique de 1 mm. Le volume est estimé par le cube de la longueur de la plus longue arête. Ce n’est pas, pour un objet quelconque, une garantie de véritable cube englobant ni un calcul exact du volume : on respecte ici cette approximation imposée. L’affichage utilise matplotlib.
Le dossier comporte le sujet et les fichiers listés dans les téléchargements. Les codes de départ sont reproduits ci-dessous. Travaillez sur une copie. Les appels professeur signalés dans les questions servent à présenter votre démarche ; vous pouvez aussi demander de l’aide en cas de difficulté.
Code fourni : Face.py
class Face:
"""
Représente une face d'un objet 3D.
"""
def __init__(self, sommets):
self.sommets = sommets
Code fourni : Imprimante3D.py
from Objet3D import Objet3D
#############################################################################
# Variables et fonctions fournies pour la question 3 #
#############################################################################
class Imprimante3D:
def __init__(self, remplissage, vitesse_extrusion):
self.remplissage = remplissage
self.vitesse_extrusion = vitesse_extrusion
#############################################################################
# Écrire le code de la méthode estimation_impression de la question 3 #
#############################################################################
def estimation_impression(self, objet):
pass
rhombi = Objet3D()
# Définition des 24 sommets
rhombi.ajouter_sommet(1, 1, 3) # s0
rhombi.ajouter_sommet(1, 1, -3) # s1
rhombi.ajouter_sommet(1, -1, 3) # s2
rhombi.ajouter_sommet(1, -1, -3) # s3
rhombi.ajouter_sommet(-1, 1, 3) # s4
rhombi.ajouter_sommet(-1, 1, -3) # s5
rhombi.ajouter_sommet(-1, -1, 3) # s6
rhombi.ajouter_sommet(-1, -1, -3) # s7
rhombi.ajouter_sommet(1, 3, 1) # s8
rhombi.ajouter_sommet(1, 3, -1) # s9
rhombi.ajouter_sommet(1, -3, 1) # s10
rhombi.ajouter_sommet(1, -3, -1) # s11
rhombi.ajouter_sommet(-1, 3, 1) # s12
rhombi.ajouter_sommet(-1, 3, -1) # s13
rhombi.ajouter_sommet(-1, -3, 1) # s14
rhombi.ajouter_sommet(-1, -3, -1) # s15
rhombi.ajouter_sommet(3, 1, 1) # s16
rhombi.ajouter_sommet(3, 1, -1) # s17
rhombi.ajouter_sommet(3, -1, 1) # s18
rhombi.ajouter_sommet(3, -1, -1) # s19
rhombi.ajouter_sommet(-3, 1, 1) # s20
rhombi.ajouter_sommet(-3, 1, -1) # s21
rhombi.ajouter_sommet(-3, -1, 1) # s22
rhombi.ajouter_sommet(-3, -1, -1) # s23
# Faces carrées principales (axiales)
rhombi.ajouter_face([0, 2, 6, 4]) # Z+
rhombi.ajouter_face([1, 5, 7, 3]) # Z-
rhombi.ajouter_face([16, 17, 19, 18]) # X+
rhombi.ajouter_face([20, 22, 23, 21]) # X-
rhombi.ajouter_face([8, 12, 13, 9]) # Y+
rhombi.ajouter_face([10, 11, 15, 14]) # Y-
# Faces carrées de jonction (arêtes)
rhombi.ajouter_face([0, 16, 18, 2]) # Z+/X+
rhombi.ajouter_face([4, 6, 22, 20]) # Z+/X-
rhombi.ajouter_face([1, 3, 19, 17]) # Z-/X+
rhombi.ajouter_face([5, 21, 23, 7]) # Z-/X-
rhombi.ajouter_face([0, 4, 12, 8]) # Z+/Y+
rhombi.ajouter_face([2, 10, 14, 6]) # Z+/Y-
rhombi.ajouter_face([1, 9, 13, 5]) # Z-/Y+
rhombi.ajouter_face([3, 7, 15, 11]) # Z-/Y-
rhombi.ajouter_face([8, 16, 17, 9]) # Y+/X+
rhombi.ajouter_face([12, 20, 21, 13]) # Y+/X-
rhombi.ajouter_face([10, 18, 19, 11]) # Y-/X+
rhombi.ajouter_face([14, 22, 23, 15]) # Y-/X-
# Faces triangulaires (sommets)
rhombi.ajouter_face([0, 8, 16]) # X+Y+Z+
rhombi.ajouter_face([4, 12, 20]) # X-Y+Z+
rhombi.ajouter_face([2, 10, 18]) # X+Y-Z+
rhombi.ajouter_face([6, 14, 22]) # X-Y-Z+
rhombi.ajouter_face([1, 17, 9]) # X+Y+Z-
rhombi.ajouter_face([5, 13, 21]) # X-Y+Z-
rhombi.ajouter_face([3, 19, 11]) # X+Y-Z-
rhombi.ajouter_face([7, 15, 23]) # X-Y-Z-
# rhombi.afficher() # à décommenter pour afficher le rhombicuboctaèdre
imprimante = Imprimante3D(20, 1.2)
print(imprimante.estimation_impression(rhombi))
Code fourni : Objet3D.py
from Sommet import Sommet
from Face import Face
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
class Objet3D:
"""
Représente un objet 3D composé de sommets, de faces et d'un nom.
"""
def __init__(self):
"""
Initialise un objet 3D vide.
"""
self.sommets = []
self.faces = []
self.nom = ""
def ajouter_sommet(self, x, y, z):
"""
Ajoute un sommet à l'objet 3D.
"""
self.sommets.append(Sommet(x, y, z))
def ajouter_face(self, liste_sommets):
"""
Ajoute une face à l'objet 3D.
"""
self.faces.append(Face([self.sommets[i] for i in liste_sommets]))
def __str__(self):
"""
Renvoie une représentation textuelle de l'objet 3D.
"""
return str({'nom': self.nom, 'sommets': len(self.sommets), 'faces': len(self.faces)})
def afficher(self):
"""
Affiche l'objet 3D à l'aide de matplotlib.
"""
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
f = []
for face in self.faces:
x = [(s.x, s.y, s.z) for s in face.sommets]
f.append(x)
mesh = Poly3DCollection(f, alpha=0.4, edgecolor='black')
ax.add_collection3d(mesh)
plt.show()
#############################################################################
# Méthode à modifier de la question 5 #
#############################################################################
def transformer(self, rapport):
"""
Applique une transformation d'échelle à l'objet 3D en modifiant directement ses sommets.
"""
sommets = []
for sommet in self.sommets:
sommets.append(
Sommet(sommet.x * rapport,
sommet.y * rapport, sommet.z * rapport))
self.sommets = sommets
#############################################################################
# Écrire le code de la méthode sommets_adjacents de la question 2 #
#############################################################################
def sommets_adjacents(self, s1, s2):
pass
def longueur_plus_longue_arete(self):
max_longueur = 0
for s1 in self.sommets:
for s2 in self.sommets:
if self.sommets_adjacents(s1, s2):
d = s1.distance(s2)
if d > max_longueur:
max_longueur = d
return max_longueur
def volume_cube_englobant(self):
longueur_max = self.longueur_plus_longue_arete()
return longueur_max ** 3
#############################################################################
# Cube pour tester votre méthode de la question 2 #
#############################################################################
cube = Objet3D()
cube.ajouter_sommet(0, 0, 0) # s0
cube.ajouter_sommet(1, 2, 2) # s1
cube.ajouter_sommet(3, 3, 0) # s2
cube.ajouter_sommet(2, 1, -2) # s3
cube.ajouter_sommet(-2, 2, -1) # s4
cube.ajouter_sommet(-1, 4, 1) # s5
cube.ajouter_sommet(1, 5, -1) # s6
cube.ajouter_sommet(0, 3, -3) # s7
cube.ajouter_face([0, 1, 2, 3])
cube.ajouter_face([4, 5, 6, 7])
cube.ajouter_face([0, 1, 5, 4])
cube.ajouter_face([1, 2, 6, 5])
cube.ajouter_face([2, 3, 7, 6])
cube.ajouter_face([3, 0, 4, 7])
# cube.afficher() # à décommenter pour afficher le cube en 3d
Code fourni : Sommet.py
import math
class Sommet:
"""
Représente un sommet (point) dans l'espace 3D.
"""
def __init__(self, x, y, z):
"""
Initialise un sommet avec ses coordonnées.
"""
self.x = x
self.y = y
self.z = z
#############################################################################
# Écrire le code de la méthode distance de la question 1 #
#############################################################################
def distance(self, s):
return ((s.x-self.x)**2+(s.y-self.y)**2+(s.z-self.z)**2)**0.5
#############################################################################
# Programme pour tester votre méthode de la question 1 #
#############################################################################
s1 = Sommet(0, 0, 0)
s2 = Sommet(3, 4, 0)
Question 1
#Écrire la méthode distance de Sommet : renvoyer la distance euclidienne entre l’instance courante et un autre Sommet. On utilise la racine carrée de la somme des carrés des écarts de coordonnées.
Indice
Trois différences, trois carrés, une seule racine finale.
Comprendre la correction
def distance(self, s):
return math.sqrt((self.x-s.x)**2 + (self.y-s.y)**2 + (self.z-s.z)**2)assert Sommet(0,0,0).distance(Sommet(3,4,0)) == 5.0
assert Sommet(1,2,3).distance(Sommet(1,2,3)) == 0.0Le fichier livré contient déjà une implémentation équivalente avec **0.5. Il faut la comprendre et la vérifier, pas la remplacer pour le principe. Les carrés rendent la distance symétrique et éliminent le signe des différences.
Question 2
#Écrire sommets_adjacents(s 1, s 2) dans Objet3D. Renvoyer True si les deux points constituent une arête, et False sinon. Les arêtes ne sont pas orientées.
PythonReconnaître une arête sans accepter une diagonaleÉcrivez votre solution et mettez-la à l’épreuve
Complétez sommets_adjacents(self, s1, s2). Une arête relie deux sommets consécutifs d’une face, y compris le dernier et le premier. Les arêtes sont non orientées. Comparez les coordonnées : deux instances différentes peuvent désigner un même point. Nous isolons la géométrie, sans l’affichage Matplotlib.
class Objet3D:
def __init__(self):
"""
Initialise un objet 3D vide.
"""
self.sommets = []
self.faces = []
self.nom = ""
def ajouter_sommet(self, x, y, z):
"""
Ajoute un sommet à l'objet 3D.
"""
self.sommets.append(Sommet(x, y, z))
def ajouter_face(self, liste_sommets):
"""
Ajoute une face à l'objet 3D.
"""
self.faces.append(Face([self.sommets[i] for i in liste_sommets]))
def sommets_adjacents(self, s1, s2):
# Complétez cette méthode
pass
Les cas de test proposés :
- Première arête : Deux sommets consécutifs forment une arête.
- Sens inverse : Une arête n’est pas un arc orienté.
- Fermeture de la face : La dernière paire relie la fin et le début du contour.
- Une diagonale : Appartenir à la même face n’est pas suffisant.
- Même point, autre instance : Le sujet demande une relation entre points, pas seulement entre identités Python.
- Aucune face : Sans contour, aucune arête ne peut être trouvée.
Indice
Lire la liste d’une face comme un contour fermé.
Comprendre la correction
def sommets_adjacents(self, s1, s2):
def coord(s):
return (s.x, s.y, s.z)
a, b = coord(s1), coord(s2)
for face in self.faces:
n = len(face.sommets)
for i in range(n):
u = coord(face.sommets[i])
v = coord(face.sommets[(i + 1) % n])
if (a == u and b == v) or (a == v and b == u):
return True
return FalseDans une face polygonale, seules les paires consécutives et la paire dernier-premier sont des arêtes. Deux sommets de la même face peuvent former une diagonale : tester seulement leur appartenance à cette face serait faux. Le modulo referme le contour. La comparaison des coordonnées respecte la formulation du sujet ; elle accepte deux instances distinctes désignant le même point.
assert cube.sommets_adjacents(cube.sommets[0], cube.sommets[1])
assert cube.sommets_adjacents(cube.sommets[1], cube.sommets[0])
assert cube.sommets_adjacents(cube.sommets[0], cube.sommets[3])
assert not cube.sommets_adjacents(cube.sommets[0], cube.sommets[2])Pour une face contenant quatre sommets dans l’ordre A, B, C, D, le parcours examine A-B, B-C, C-D puis D-A. L’expression (i + 1) % n ne sert qu’à refermer le contour lorsque i désigne le dernier sommet. Une paire A-C appartient bien à la face, mais elle traverse son intérieur : c’est une diagonale. Si elle était prise pour une arête, la longueur maximale et son cube seraient surestimés, ce qui fausserait la durée d’impression. Un petit défaut d’adjacence se propagerait donc jusqu’au résultat final.
Question 3
#Écrire estimation_impression(objet) dans Imprimante3D : multiplier le volume par le taux de remplissage, puis diviser par la vitesse d’extrusion, pour obtenir des secondes.
Comprendre la correction
def estimation_impression(self, objet):
assert 0 <= self.remplissage <= 1
assert self.vitesse_extrusion > 0
volume = objet.volume_cube_englobant() * self.remplissage
return volume / self.vitesse_extrusionLe cube fourni possède des arêtes de 3 mm ; son volume vaut 27 mm³. Avec un remplissage 0,2 et une vitesse 1,2 mm³/s, le temps estimé vaut 4,5 s. Le fichier fourni crée Imprimante3D(20,1.2), alors que le texte définit un taux entre 0 et 1 : pour 20%, il faut corriger l’appel en Imprimante3D(0.2,1.2). La formule ne doit pas compenser silencieusement cette incohérence.
imprimante = Imprimante3D(0.2, 1.2)
assert abs(imprimante.estimation_impression(cube) - 4.5) < 1e-10Question 4
#Afficher le cube avant et après transformer(2), analyser pourquoi sa dimension ne double pas à l’affichage, puis corriger transformer.
PythonAgrandir le sommet vu par toutes les facesÉcrivez votre solution et mettez-la à l’épreuve
Corrigez transformer(self, rapport). Multipliez x, y et z de chaque sommet existant par rapport. Les faces doivent continuer à désigner ces mêmes objets ; remplacer seulement self.sommets ne suffit pas. L’atelier vérifie les références et les coordonnées, sans ouvrir de fenêtre 3D.
class Objet3D:
def __init__(self):
"""
Initialise un objet 3D vide.
"""
self.sommets = []
self.faces = []
self.nom = ""
def ajouter_sommet(self, x, y, z):
"""
Ajoute un sommet à l'objet 3D.
"""
self.sommets.append(Sommet(x, y, z))
def ajouter_face(self, liste_sommets):
"""
Ajoute une face à l'objet 3D.
"""
self.faces.append(Face([self.sommets[i] for i in liste_sommets]))
def transformer(self, rapport):
# Complétez cette méthode
pass
Les cas de test proposés :
- Une face voit le déplacement : Les listes de sommets et de faces doivent observer la même instance transformée.
- Deux faces partagent un sommet : Un partage entre faces ne doit pas provoquer deux transformations du même sommet.
- Origine et coordonnées négatives : Chaque axe et chaque signe sont conservés par le facteur d’échelle.
- Transformation identité : Le facteur 1 conserve valeurs et identités ; la méthode agit en place.
- Objet vide : L’absence de sommets ne demande aucune transformation.
Indice
Quelles instances sont lues par afficher ?
Comprendre la correction
La méthode crée de nouveaux Sommet dans self.sommets, mais chaque Face conserve des références aux anciens objets. afficher lit les coordonnées depuis les faces : elle voit donc les anciens sommets. La correction la plus simple modifie les objets existants, afin que toutes les références observent les mêmes coordonnées.
def transformer(self, rapport):
for sommet in self.sommets:
sommet.x *= rapport
sommet.y *= rapport
sommet.z *= rapportcube.afficher()
ancien = cube.faces[0].sommets[1]
cube.transformer(2)
assert cube.sommets[1] is ancien
assert (ancien.x, ancien.y, ancien.z) == (2, 4, 4)
cube.afficher()Doubler les longueurs multiplie le volume par 8, pas par 2. Contrôler les coordonnées et le volume est plus fiable que juger seulement la taille apparente dans une fenêtre 3D, dont les axes peuvent être réajustés. Recréer le cube avant chaque test pour éviter de multiplier plusieurs fois son échelle.
Dessinez deux flèches vers le même sommet : l’une depuis la liste de l’objet, l’autre depuis la liste d’une face. La version défaillante déplace seulement la première flèche vers un nouveau sommet agrandi. La seconde reste sur l’ancienne instance. La version corrigée conserve les deux flèches et change les coordonnées de l’objet qu’elles désignent. Un test avec is vérifie cette identité partagée ; un test de coordonnées vérifie la transformation numérique. Les deux sont nécessaires pour expliquer pourquoi le prochain affichage utilise enfin les nouvelles valeurs.
Repérer une référence périméeUn atelier pour expérimenter
Choisissez une réponse puis confrontez votre intuition à l’explication. Vous pouvez modifier vos choix.
Lire les réponses et leurs explications
- Remplacer
self.sommetsmet-il à jour automatiquement chaque Face ?Non, les faces gardent leurs anciennes références.. Le lien porte sur l’objet, pas sur son emplacement dans la liste.
- Une face [0,1,2,3] contient-elle l’arête 0-2 ?
Non, c’est une diagonale.. Les arêtes sont 0-1,1-2,2-3 et 3-0.
Appuyez votre explication sur les valeurs et les conditions exactes du programme.
Transformer le cube en gardant ses référencesUn atelier pour expérimenter
Changez le rapport et la stratégie. Le tableau compare le sommet 1 vu par la liste principale et par la première face. Les valeurs initiales sont celles du fichier.
Lire le résultat de l’expérience initiale
La liste et la face voient les mêmes coordonnées.
Les faces contiennent des références. Modifier un objet partagé est différent de remplacer une référence dans une seule liste.
| Vue | x | y | z |
|---|---|---|---|
| Liste principale | 2 | 4 | 4 |
| Première face | 2 | 4 | 4 |
Pour comprendre un bug d’objet, dessinez qui référence quelle instance.
Du sujet à la méthode
Votre prochaine séance de révision
- Tester les arêtes en excluant les diagonales.
- Pour une transformation, vérifier données et références, pas uniquement l’affichage.
Retrouver ces notions dans d’autres sujets
Toutes les annales de NSI · Le guide pour préparer le bac NSI 2027
Énoncé : sujet 26_BCG_NSI_9 (PDF). Corrigé et explications pédagogiques proposés par Sofien.
