[PYTHON] Générer cette forme du fond d'une bouteille pour animaux de compagnie

J'ai pensé faire ce pentagonal au fond d'une bouteille en PET de boisson gazeuse, que tout le monde a toujours vu, par programmation.

De la conclusion, environ 70 points ont été faits par auto-évaluation. Si vous êtes intéressé par le résultat, faites d'abord défiler vers le bas pour voir l'image terminée, puis décidez si vous souhaitez lire plus loin.

La forme du fond d'une bouteille de boisson gazeuse pour animaux de compagnie

C'est un pentagonal. pet01.jpg

Cette forme est communément appelée ** Petaloid **. Cela signifie "petal-oid". Il est ainsi appelé car il ressemble à un pétale vu de dessous.

Si vous avez une bouteille en PET gazéifiée à la maison, jetez un œil au bas.

Pourquoi cette forme?

Il existe de nombreuses formes de bouteilles en PET, mais en gros, seules les boissons gazeuses ont cette forme de fond. Les boissons gazeuses ont une forte pression interne, donc si elles ont la forme d'une bouteille de thé, le fond gonflera. On dit que les pétaloïdes sont utilisés pour maintenir la force parce que des problèmes tels que le roulement et l'éclatement se produisent tels quels.

Je voudrais présenter une expérience dans laquelle du bicarbonate de soude a été mis dans une bouteille de thé en PET pour tester sa force.

Il y a un secret profond sous la forme des bouteilles pour animaux de compagnie - Diamond Online

Dessine un pétaloïde

Cible

L'objectif de cet article est de générer par programme des formes pétaloïdes. Visez la qualité telle qu'elle est (ne vous attendez pas trop car mon jugement est plus bâclé que celui du grand public).

Rechercher des matériaux par Google

Je me suis demandé si cela pouvait être facilement exprimé avec une seule formule, mais je n'ai obtenu aucun résultat. Bien que les données de test de pression sortiront ...

Peut-être que je conçois avec la CAO.

Observer

Si vous ne pouvez pas compter sur Internet, vous devez le faire vous-même. Heureusement, j'ai autant de vraies choses que moi. Tout d'abord, utilisez un cutter pour découper uniquement le fond de la bouteille en PET.

pet02.jpg

De plus, essayez de réduire de moitié verticalement à partir du centre. De la direction 12 heures à la direction 6 heures dans l'image ci-dessus.

pet03.jpg

Ce que j'ai trouvé

-Cinq de la même forme sont alignés sur la circonférence --Il y a des vallées et des pics alternés -La partie vallée est une courbe lisse du centre

Tout d'abord, comme vous pouvez le voir en un coup d'œil, cinq de la même forme sont alignés sur la circonférence, donc si vous pouvez reproduire l'un d'eux, vous pouvez voir que vous pouvez atteindre l'objectif simplement en copiant et en en disposant cinq.

En termes de forme, il existe une alternance de pics et de vallées, et les vallées ont une largeur d'environ 6 à 7 mm et semblent avoir des lignes parallèles du centre à la circonférence externe. La forme de la montagne est assez compliquée, mais elle ressemble à un éventail vu directement au-dessus (ou directement en dessous) et à un trapèze vu de côté. Pour être précis, il y a une courbe douce entre les pics et les vallées.

En regardant la section transversale, la section transversale de la partie vallée est une courbe intelligente qui ressemble à un quart d'une courbe elliptique. En revanche, la coupe transversale de la partie montagne est un peu compliquée.

analyze.jpgcurve.jpg

De plus, ce n'est pas très pertinent pour l'article, mais la partie inférieure était assez épaisse, environ 3 mm. Au début, je me demandais si je pouvais le couper avec des ciseaux, mais j'ai dû utiliser une scie à fil car la lame n'avait pas de dents.

Établissez une politique

À partir de ces informations, réfléchissez à la manière de reproduire la forme.

Premièrement, comme politique de base, nous considérerons les choses en fonction du centre de la bouteille. Plus précisément, comme le montre la figure ci-dessous, nous allons essayer de reproduire le solide en calculant la forme de la section transversale dans la direction de l'angle azimutal θ à chaque fois avec le centre comme référence et en l'accumulant.

concept.jpg

Ensuite, concernant la reproduction de la forme de la section transversale, il est nécessaire de générer les trois types de courbes différents suivants.

Peut-être que cela pourrait être résumé dans une formule, mais je ne peux pas y penser dans mon esprit, alors cette fois j'ai décidé d'utiliser la courbe spline (spline en cas de problème).

Reproduction de courbe par spline

Comme mentionné ci-dessus, considérons comment générer trois types de courbes différents basés sur une variable appelée angle d'azimut $ θ $.

Tout d'abord, envisagez d'exprimer la courbe de la partie vallée avec une fonction spline. Comme vous pouvez le voir, cette courbe a à peu près la forme d'un arc sur une ellipse, il n'est donc pas difficile de la représenter avec des splines.

Par souci de simplicité, nous allons l'utiliser comme un cercle parfait au lieu d'une ellipse. Si vous souhaitez le ramener à une ellipse, vous pouvez l'agrandir ou le réduire verticalement.

fig01.png

Dessinez un cercle comme indiqué ci-dessus. Disposez $ N $ points de contrôle à intervalles égaux sur l'arc du quatrième quadrant (en bas à droite) et tracez une courbe spline cubique les reliant. Cette courbe est faite pour passer par tous les points de contrôle. Cela ne correspond pas exactement à l'arc, mais c'est une courbe qui ne se distingue pas à l'œil humain. De plus, N $ = 9 $.

fig02.png

Ensuite, je vais faire une courbe pour la partie montagne. Ici, traitons la courbe de la vallée et imitons la courbe de la montagne comme grossièrement observée.

Plus précisément, le point de contrôle $ (x_i, y_i) $ sur l'arc est étendu en multipliant $ k_i $ à partir du centre $ (0,0) $ du cercle ($ 1 \ leq i \ leq N $). Faites un contour approximatif comme indiqué dans la figure ci-dessous.

x'_i = x_i * k_i \\
y'_i = y_i * k_i

fig03_.png

Tout en jetant un œil à l'image capturée, affinez la valeur de $ k_i $ pour qu'elle ait presque la même apparence. Le dernier paramètre est

\begin{align}
K &= [k_1, k_2, k_3, ... , k_9]  \\
&= [1.00, 1.05, 1.18, 1.46, 1.36, 1.18, 1.07, 1.01, 1.00]
\end{align}

C'était fabriqué. Les points de bord (k1 et k9) doivent être mis à 1.0 ou le résultat sera étrange.

Augmenter le nombre de points de contrôle $ N $ pour reproduire une courbe plus précise peut améliorer la qualité, mais il est assez difficile de l'ajuster. Considérant après la fin, il aurait peut-être été préférable d'ajouter un ou deux points supplémentaires afin d'améliorer la précision de la partie inférieure (quatrième point à partir de la gauche).

L'interpolation spline de ce contour donne ce qui suit. J'ai ajusté $ k_i $ en regardant la vraie bouteille en PET de côté, donc ça ressemble beaucoup à ça.

fig04.png

Enfin, tracez une courbe pour la pente entre les pics et les vallées. Ceci est fait en mélangeant les courbes de crête et de vallée avec une variable appelée le rapport de déformation $ D $ (ce que vous faites est juste interpolation linéaire. % B7% 9A% E5% BD% A2% E8% A3% 9C% E9% 96% 93)).

\begin{align}
x''_i &= x_i * D + x'_i * (1-D) \\
&= x_i * (D + k_i - Dk_i) \\
y''_i &= y_i * D + y'_i * (1-D) \\
&= y_i * (D+k_i - Dk_i)
\end{align}
\quad (0 \leq D \leq 1)

fig05.png

L'image ci-dessus montre la courbe de la partie inclinée de 0,0 à 1,0 par pas de 0,2. D = 0 est la ligne pourpre rougeâtre et D = 1 est la ligne vert jaunâtre. Vous pouvez créer une courbe qui comble en douceur l'espace entre les pics et les creux, tels que les lignes de contour.

Relation entre l'angle azimutal θ et le rapport de déformation D

Au moment de décider de la politique, j'ai décidé de faire une courbe de la section transversale pour chacun des multiples angles d'azimut $ θ $. De plus, puisque nous avons introduit une variable appelée rapport de déformation $ D $ pour créer une courbe, nous devons trouver la valeur de $ D $ en fonction de la valeur de $ θ $.

Je vais dessiner un graphique de θ (0 à 360 degrés) sur l'axe horizontal et D (0 à 1) sur l'axe vertical pour clarifier cette relation. Tout d'abord, pour le sommet de la montagne, définissez $ D = 1 $. La plage de l'angle considéré comme le haut est de 22 degrés à partir de la mesure visuelle (à peu près).

graphA02.png

De plus, définissez $ D = 0 $ pour le fond de la vallée. C'est environ 10 degrés. Comme il était difficile à comprendre car il n'apparaissait pas dans le graphique, la limite inférieure de l'axe Y est fixée à -0,2.

graphA01.png

La partie blanche de l'écart entre l'orange et le bleu est la pente entre le sommet et la vallée. Cette partie sera remplie entre 0 et 1 avec une courbe, mais comment la remplir? C'est cool de dire que c'est mathématiquement comme ça, mais je ne suis pas si intelligent. Je vais utiliser la fonction d'assouplissement ici pour le remplir joliment.

Référence: feuille de calcul de la fonction d'assouplissement

Après avoir essayé diverses choses, j'ai choisi ʻeaseInOutQuad` parce que ça avait l'air bien.

\begin{align}
y &= easeInOutQuad(x)  \\
&= 
\begin{cases}
2x^2  &(0 \leq x < \frac{1}{2}) \\
-2x^2 + 4x - 1 &(\frac{1}{2} \leq x \leq 1) 
\end{cases}
\end{align}

Le graphique final ressemble à ceci: Notez que la plage sur l'axe vertical est passée de 0 à 1.

graphA03.png

Génération de modèles 3D

Maintenant que nous avons les courbes dans toutes les directions, nous allons les utiliser pour créer un modèle 3D.

Tout d'abord, l'angle azimutal θ est découpé à intervalles égaux (par exemple, tous les 1 °) pour générer une courbe. Pour la courbe, calculez les coordonnées discrètes en M points. Concentrez-vous sur les points des deux courbes et sélectionnez les trois points pour former un polygone triangulaire.

polygon.jpg

Achevée!

Vous pouvez dessiner directement avec OpenGL etc. tel quel, mais cette fois je vais exporter le fichier au format DXF et l'afficher avec un logiciel de modélisation 3D.

Depuis que j'ai écrit le code en Python cette fois, j'ai utilisé la bibliothèque ezdxf qui peut gérer le format DXF en Python. L'image suivante montre le fichier de sortie avec le logiciel appelé Modo.

render1.jpg render2.jpg

Voici le résultat de l'affectation des matériaux et du rendu [^ 1].

render5.JPG

C'est le résultat affiché dans Blender. Le plug-in d'importation AutoCAD DXF doit être activé.

render4.jpg

On dirait que ça a l'air bien de côté, mais ça ne ressemble pas très bien du bas. C'est peut-être parce que les inégalités ne suffisent pas ou parce qu'elles ne sont pas arrondies. Hum ...

Code source

Je ne pense pas que la plupart des gens le liront, alors je vais le plier.

Afficher le code source Python

Nécessite les bibliothèques NumPy, SciPy, ezdxf.

petaloid.py


from typing import List, Tuple
import math
import numpy as np
from scipy.interpolate import splprep, splev
import ezdxf


#Définition de l'indice de type
Point2D = Tuple[float, float]
Point3D = Tuple[float, float, float]


def mix(a: float, b: float, x: float) -> float:
    """Division interne linéaire x=Quand 0 a, x=Quand 1 b"""
    return b * x + a * (1.0 - x)


def easeInOutQuad(x: float) -> float:
    """Fonction de détente"""
    if x < 0.5:
        return 2.0 * x * x
    else:
        return -2 * x * x + 4 * x - 1


def calcDeformRate(t: float) -> float:
    """
Trouver le rapport de déformation de la courbe D correspondant à l'angle azimutal t
    :param t:Angle de direction(L'unité est le degré).. Lorsque le reste divisé par 72 vaut 0, cela correspond à la vallée, et lorsqu'il est à 36 °, cela correspond à la partie convexe.
    :return:Rapport de déformation D(0~1)
    """
    t = math.radians(math.fmod(t, 72.0))
    M = math.radians(72.0)  #Plage d'angle pour une saillie
    k = math.radians(5.0)  #Plage d'angle de la vallée
    tL = math.radians(25.0) - k  #Une limite latérale de la montagne
    tR = (M - k) - tL  #L'autre côté de la montagne

    if t < k or t > M - k:
        return 0.0  #Partie vallée
    elif t < k + tL:
        return easeInOutQuad((t-k) / tL)  #Incliné
    elif t < tR:
        return 1.0  #Partie montagne
    else:
        return easeInOutQuad(((M-k) - t) / tL)  #Incliné(Inverser)


def curve(angle: float, div_num: int) -> List[Point2D]:
    """
Calculez la courbe extérieure en fonction de l'angle azimutal spécifié
    :param angle:Angle de direction(0~360°)
    :param div_num:Le nombre de points qui composent la courbe. Plus il est, plus il est lisse
    :return:Une séquence de points représentant une courbe
    """

    # degree -> radian
    angle = math.fmod(angle, 360.0 / 5)  #Puisqu'il s'agit d'un pentagone, 360 degrés est divisé en 5 parties égales.

    #Trouvez le rapport de déformation D correspondant à l'angle azimutal(D dans la vallée=0, D dans les montagnes=1)
    D = calcDeformRate(angle)

    #Trouvez le taux de déformation de chaque point de contrôle
    #Dans la partie vallée, tous les points de contrôle sont à gauche(1.0)Et ça devient un arc
    #Dans la partie montagne, tous les points de contrôle dessinent une courbe spécifiée avec le rapport sur la droite
    #Autres parties(Pente de montagne)Dessine une courbe interpolée en fonction de la valeur de D
    r_rate = [
        mix(1.00, 1.00, D),
        mix(1.00, 1.05, D),
        mix(1.00, 1.18, D),
        mix(1.00, 1.46, D),
        mix(1.00, 1.36, D),
        mix(1.00, 1.18, D),
        mix(1.00, 1.07, D),
        mix(1.00, 1.01, D),
        mix(1.00, 1.00, D)]
    #Trouvez la position du point de contrôle
    xs = []
    ys = []
    n = len(r_rate)
    for i in range(n):
        deg = i * 90.0 / (n - 1)
        rad = math.radians(deg)
        x = r_rate[i] * math.sin(rad)
        y = r_rate[i] * math.cos(rad)
        xs.append(x)
        ys.append(y)
    #Dessinez une courbe spline cubique pour passer à travers tous les points de contrôle
    spline = splprep([xs, ys], s=0, k=3)[0]
    detail = np.linspace(0, 1, num=div_num, endpoint=True)
    ix, iy = splev(detail, spline)
    points = list(zip(ix, iy))
    return points


def main():
    #Faire une petite saillie au centre en bas
    num_bottom_point = 8  #Score des microprojections en bas au centre
    bottom_width = 0.2  #Taille inférieure centrale
    bottom_height = 0.05  #Hauteur du fond central
    bottom_points = []
    for i in range(num_bottom_point):
        n = i / num_bottom_point
        angle = n * math.pi
        x = n * bottom_width
        #Y avec cos=[1.0,1.0+height]Faire une courbe
        y = 1.0 + (math.cos(angle) + 1) / 2 * bottom_height
        bottom_points.append((x, y))

    #Créer une liste tridimensionnelle de sommets représentant des figures pétaloïdes
    num_curve: int = 180  #Nombre de divisions azimutales(360 à chaque fois,180 est toutes les 2 fois)
    num_point: int = 50  #Nombre approximatif de courbes dans une direction(Y compris le point central)
    num_point_curve = num_point - num_bottom_point  # (Excluant le point central)
    aspect_adjust: float = 0.75  #Valeur d'ajustement du rapport d'aspect
    vertices: List[Point3D] = []
    for i in range(num_curve):
        #Trouvez la courbe pour chaque direction
        theta_deg = 360.0 / num_curve * i
        theta_rad = math.radians(theta_deg)
        points1 = curve(theta_deg, num_point_curve)

        #Connectez-vous avec la petite saillie au centre
        points2: List[Point2D] = [*bottom_points]
        for p in points1:
            #À travers la courbe(X)Écraser un peu dans le sens et faire de la partie vacante une surface plane(Quel processus délicat ...)
            x = bottom_width + p[0] * (1.0 - bottom_width)
            y = p[1]
            points2.append((x, y))

        #Convertir en coordonnées 3D
        c = math.cos(theta_rad)
        s = math.sin(theta_rad)
        for p in points2:
            x2 = p[0]
            y2 = p[1]
            x3 = x2 * c
            y3 = y2 * aspect_adjust  #Ajuster le rapport hauteur / largeur
            z3 = x2 * s
            vertices.append((x3, y3, z3))

    #Divisez en triangles
    indices: List[Tuple[int, int, int]] = []
    for angle in range(num_curve):
        a0 = angle
        a1 = (angle + 1) % num_curve
        for i in range(num_point - 1):
            i00 = a0 * num_point + i
            i01 = a0 * num_point + i + 1
            i10 = a1 * num_point + i
            i11 = a1 * num_point + i + 1
            if i != 0:  #Seul le centre est un triangle, alors excluez-le pour qu'il ne se chevauche pas
                indices.append((i00, i10, i11))
            indices.append((i11, i01, i00))

    #Sortie avec dxf
    #Je l'ai divisé en sommet et index, mais comme la face 3D de dxf n'a que des sommets,
    #Synthétiser une liste de sommets
    triangles: List[Tuple[Point3D, Point3D, Point3D]] = []
    for i in indices:
        v1 = vertices[i[0]]
        v2 = vertices[i[1]]
        v3 = vertices[i[2]]
        triangles.append((v3, v2, v1))
    doc = ezdxf.new('R2010')  # MESH requires DXF R2000 or later
    msp = doc.modelspace()
    for t in triangles:
        msp.add_3dface(t)
    doc.saveas("petaloid.dxf")


if __name__ == '__main__':
    main()

Les défis restants

Je ne suis plus motivé. Si vous avez une personne étrange qui veut défier un pétaloïde plus complet, veuillez vous y référer.

--Reproduction d'une constriction plus réaliste Lorsque vous regardez la réalité, les deux extrémités de la partie de la vallée semblent plus resserrées. En particulier, la vallée du côté central a un rétrécissement en forme de U, mais cette fois j'ai renoncé à penser que le calcul serait trop compliqué à terminer. Je ne sais pas quoi faire spécifiquement, mais ce serait bien d'avoir un élément qui change non seulement avec l'angle azimutal mais aussi avec la distance du centre ou l'angle zénithal.

À la fin

Q. Pourquoi seulement le bas? Et le corps? A. Je suis épuisé \ _ (: 3 "∠) \ _

[^ 1]: Après avoir importé DXF, Mesh CleanUp est exécuté pour combiner des polygones. De plus, une surface de subdivision est utilisée pour lisser la forme.

Recommended Posts

Générer cette forme du fond d'une bouteille pour animaux de compagnie
Une histoire qui réduit l'effort de fonctionnement / maintenance
[Python] Un programme qui compte le nombre de vallées
Créez un BOT qui raccourcit l'URL Discord
#Une fonction qui renvoie le code de caractère d'une chaîne de caractères
Une histoire qui a analysé la livraison de Nico Nama.
[Python] Un programme qui compare les positions des kangourous.
Un outil qui transforme automatiquement le gacha de Soshage
Script Python qui compare le contenu de deux répertoires
Lors de l'incrémentation de la valeur d'une clé qui n'existe pas
pandas Récupère le nom d'une colonne contenant un caractère spécifique
Une formule qui calcule simplement l'âge à partir de la date de naissance
Discrimination de la forme agari du mahjong
Une fonction qui mesure le temps de traitement d'une méthode en python
L'histoire de la création d'un site qui répertorie les dates de sortie des livres
J'ai fait un bot mou qui m'informe de la température
Générer une liste de caractères consécutifs
[python] Une note que j'ai commencé à comprendre le comportement de matplotlib.pyplot
L'histoire de l'exportation d'un programme
L'histoire de la création d'un module qui ignore le courrier avec python
[Python] Un programme qui fait pivoter le contenu de la liste vers la gauche
Une histoire qui visualise le présent de Qiita avec Qiita API + Elasticsearch + Kibana
[Python] Un programme qui calcule le nombre de segments de chocolat qui remplissent les conditions
J'ai fait un calendrier qui met à jour automatiquement le calendrier de distribution de Vtuber
[Python] Un programme qui calcule le nombre de chaussettes jumelées
Générez une liste contenant le nombre de jours du mois en cours.
Générer semi-automatiquement une description du package à enregistrer dans PyPI
J'ai écrit un lecteur de corpus qui lit les résultats de l'analyse MeCab
L'histoire du développement d'une application WEB qui génère automatiquement des copies de capture [MeCab]
Hit une méthode d'une instance de classe avec l'API Web Python Bottle
Comment créer un wrapper qui préserve la signature de la fonction à envelopper
Une note sur les fonctions de la bibliothèque Linux standard qui gère le temps
L'histoire de la création d'un package qui accélère le fonctionnement de Juman (Juman ++) & KNP
D'un livre qui apprend de manière intéressante la façon de penser du programmeur (Python)
[Python] Note: Fonction auto-conçue pour trouver la zone de distribution normale
Ceci et celui de la notation d'inclusion.
Classe qui atteint l'API de DMM
Mesurer la force de l'association dans un tableau croisé
Un mémo expliquant la spécification de l'axe de l'axe
Obtenez le nom de fichier du répertoire (glob)
L'histoire du traitement A du blackjack (python)
Notez l'achèvement d'une commande chronophage
[Python] Un programme qui arrondit le score
Disposez les nombres en forme de spirale
Collection de conseils Numpy, Pandas souvent utilisés sur le terrain
L'histoire de l'adresse IPv6 que je souhaite conserver au minimum
Faisons un clustering qui donne une belle vue d'ensemble de l'ensemble de données texte
L'histoire de la création d'une application Web qui enregistre des lectures approfondies avec Django
L'histoire de Django créant une bibliothèque qui pourrait être un peu plus utile
[Discode Bot] J'ai essayé de créer un Bot qui me dit la valeur de race de Pokemon
Mise en place d'un modèle de prédiction des taux de change (taux dollar-yen) par machine learning
L'histoire de la création d'un Line Bot qui nous raconte le calendrier de la programmation du concours
Comment faire un Raspberry Pi qui parle les tweets d'un utilisateur spécifié
Un modèle qui identifie la guitare avec fast.ai
Comment calculer la volatilité d'une marque