Как создавать 3d модели с помощью Python

    Привет, Хабр! Однажды мне потребовалось создать 3D модель дна, подробнее в этой статье. Сегодня я хочу рассказать о том, как можно создавать 3D модели Python 3. Способов для этого достаточно много: blender python api, Vpython… Но я хочу рассказать, как делать модели используя только Python.



    Ссылка на Github

    STL


    Для этого нужно понять, как работает формат stl (популярный формат 3D файлов).
    Вся модель в этом формате состоит из множества треугольников, поэтому файл состоит из 3-х мерных координат их вершин.

    STL-файл
    solid
    facet normal 0 0 0
    outer loop
    vertex 0 0 0
    vertex 1 0 0
    vertex 1 1 0
    endloop
    endfacet
    facet normal 0 0 0
    outer loop
    vertex 1 1 0
    vertex 0 0 0
    vertex 0 1 0
    endloop
    endfacet
    endsolid

    Пример


    Я хотел бы показать, как реализовать создание 3D модели по яркостям пикселей на фотографии. Я взял вот это фото снизу.



    Изображение обрабатывается (немного размывается, чтобы не было резких скачков яркости) с помощью библиотеки opencv.

    import cv2
    import numpy as np
    
    cd_1=['0', '0', '0']# создание массива для хранения координат 1 вершины
    cd_2=['0', '0', '0']# создание массива для хранения координат 2 вершины
    cd_3=['0', '0', '0']# создание массива для хранения координат 3 вершины
    
    file_stl='new.stl'
    file_im=r'C:\Users\allex\Pictures\2.jpg'# путь к изображению
    op_stl=open(file_stl, 'w')
    op_im=cv2.imread(file_im)
    
    gray = cv2.cvtColor(op_im, cv2.COLOR_BGR2GRAY)#преобразование в чб изображение
    blur = cv2.GaussianBlur(gray,(0,0),1)# небольшое размытие для сглажеввания шумов
    res=cv2.resize(blur,(320,240))# преобразование изображения к размеру 320*240
    

    Снизу функция, принимающая 3 массива с координатами вершин треугольника и записывающая в файл 1 треугольную грань.

    
    def face_file_stl(cd_1, cd_2, cd_3):
    	op_stl.write("facet normal 0 0 0")
    	op_stl.write("outer loop")
    	op_stl.write("vertex " + "  ".join(cd_1))#запись в файл координаты 1 вершины
    	op_stl.write("vertex " + " ".join(cd_2))#запись в файл координаты 2 вершины
    	op_stl.write("vertex " + " ".join(cd_3))#запись в файл координаты 3 вершины
    	op_stl.write("endloop \n\tendfacet")
    

    Теперь самое главное — создание правильных координат вершин треугольников из которых состоит 3D модель.


    Часть кода, создающая координаты.

    
    for i in range(size.shape[1]):#перебор пикселей по ширине 
    
            for k in range(size.shape[0]-1):#перебор пикселей по высоте 
    
                if i!=size.shape[1]-1:
    
                        try: #making the first triangls  for relief
                            cd_1=[str(i),     str(k),   str(blur[k, i])   ]
                            cd_2=[str(i + 1), str(k),   str(blur[k, i+1]) ]
                            cd_3=[str(i+1),   str(k+1), str(blur[k+1,i+1])] 
                         
                        except:
                            print('er')
    
                        face_file_stl(cd_1, cd_2, cd_3)
    
                        try: #making the second triangls  for relief
                            cd_1=[str(i),     str(k),   str(blur[k, i])    ]
                            cd_2=[str(i+1),   str(k+1), str(blur[k+1, i+1])]
                            cd_3=[str(i),     str(k+1), str(blur[k+1,i])   ]   
                        except:
                            print('er')
    
                        face_file_stl(cd_1, cd_2, cd_3)
    

    Созданная 3D модель
    Собственно, это все, что я хотел написать до следующей статьи.

    Весь код
    import cv2
    cd_1=['0', '0', '0']# создание массива для хранения координат 1 вершины
    cd_2=['0', '0', '0']# создание массива для хранения координат 2 вершины
    cd_3=['0', '0', '0']# создание массива для хранения координат 3 вершины
    file_stl='new.stl'
    file_im=r'C:\Users\allex\Pictures\22.jpg'# путь к изображению
    op_stl=open(file_stl, 'w')
    op_im=cv2.imread(file_im)
    
    gray = cv2.cvtColor(op_im, cv2.COLOR_BGR2GRAY)#преобразование в чб изображение
    blur = cv2.GaussianBlur(gray,(0,0),1)# небольшое размытие для сглажеввания шумов
    blur=cv2.resize(blur,(320,240))
    x=0
    y=0
    file='STL_project-1.stl'
    o_1="\n\t"
    o_2="\n\t\t"
    o_3="\n\t\t\t"
    op_stl.write("solid")
    def face_file_stl(cd_1, cd_2, cd_3):
    	op_stl.write(o_1+"facet normal 0 0 0")
    	op_stl.write(o_2 + "outer loop")
    	op_stl.write(o_3 + "vertex " + " ".join(cd_1))#запись в файл координаты 1 вершины
    	op_stl.write(o_3 + "vertex " + " ".join(cd_2))#запись в файл координаты 2 вершины
    	op_stl.write(o_3 + "vertex " + " ".join(cd_3))#запись в файл координаты 3 вершины
    	op_stl.write(o_2 + "endloop \n\tendfacet")
    
    #making the first triangls  for base
    for i in range(blur.shape[1]-1):
          cd_1=[str(i),"0","0"]
          cd_3=[str(i+1),str(blur.shape[0]-1),"0"]
          cd_2=[str(i),str(blur.shape[0]-1),"0"]
          face_file_stl(cd_1, cd_2, cd_3)
    #making the second triangls  for base      
    for i in range(blur.shape[1]-1):
          cd_1=[str(i+1),str(blur.shape[0]-1),"0"]
          cd_3=[str(i),"0","0"]
          cd_2=[str(i+1),"0","0"]
          face_file_stl(cd_1, cd_2, cd_3)
    #base has done
    
    for i in range(blur.shape[1]):
            if i%30==0:
                print(i)
            for k in range(blur.shape[0]-1):#making the first triangls  for relief
                if i!=blur.shape[1]-1:
                        try:
                            
                            cd_1=[str(i),     str(k),   str(blur[k, i])   ]
                            cd_2=[str(i + 1), str(k),   str(blur[k, i+1]) ]
                            cd_3=[str(i+1),   str(k+1), str(blur[k+1,i+1])] 
                         
                        except:
                            print('er')
                        face_file_stl(cd_1, cd_2, cd_3)
    			        #for j in range(blur.shape[1]-1):#making the second triangls  for relief
                        try:
                            cd_1=[str(i),  str(k),   str(blur[k, i])    ]
                            cd_2=[str(i+1),str(k+1), str(blur[k+1, i+1])]
                            cd_3=[str(i),  str(k+1), str(blur[k+1,i])   ]   
                        except:
                            print('er')
                        face_file_stl(cd_1, cd_2, cd_3)
    #relief has done
    
    #making the first triangls  for right side        
    for i in range(blur.shape[1]):
         if i!=blur.shape[1]-1:
          try:
                cd_1=[str(i),str(blur.shape[0]-1),"0"]
                cd_3=[str(i+1),str(blur.shape[0]-1),str(blur[blur.shape[0]-1, i+1])]
                cd_2=[str(i),str(blur.shape[0]-1),str(blur[blur.shape[0]-1, i])]
                
          except:
                print("er")
          face_file_stl(cd_1, cd_2, cd_3)
          
    #making the second triangls  for right side     
          try:
                cd_1=[str(i),str(blur.shape[0]-1),"0"]
                cd_3=[str(i+1),str(blur.shape[0]-1),"0"]
                cd_2=[str(i+1),str(blur.shape[0]-1),str(blur[blur.shape[0]-1, i+1])]
          except:
                print("er")
          face_file_stl(cd_1, cd_2, cd_3)
    
         if i!=blur.shape[1]-1:
          try:
                cd_1=[str(i),'0',"0"]
                cd_2=[str(i+1),'0',str(blur[0, i+1])]
                cd_3=[str(i),'0',str(blur[0, i])]
                
          except:
                print("er")
          face_file_stl(cd_1, cd_2, cd_3)
          
    #making the second triangls  for right side     
          try:
                cd_1=[str(i),'0',"0"]
                cd_2=[str(i+1),'0',"0"]
                cd_3=[str(i+1),'0',str(blur[0, i+1])]
          except:
                print("er")
          face_file_stl(cd_1, cd_2, cd_3)
    
    for i in range(blur.shape[0]):
       if i!=blur.shape[0]-1:
          try:
                cd_1=['0',str(i),"0"]
                cd_3=['0',str(i+1),str(blur[ i+1,0])]
                cd_2=['0',str(i),str(blur[i,0])]
                
          except:
                print("er")
          face_file_stl(cd_1, cd_2, cd_3)
          
    #making the second triangls  for right side     
          try:
                cd_1=['0',str(i),"0"]
                cd_3=['0',str(i+1),"0"]
                cd_2=['0',str(i+1),str(blur[i+1,0])]
          except:
                print("er")
          face_file_stl(cd_1, cd_2, cd_3)
       
          try:
                cd_2=[str(blur.shape[1]-1),str(i),"0"]
                cd_3=[str(blur.shape[1]-1),str(i+1),str(blur[ i+1,blur.shape[1]-1])]
                cd_1=[str(blur.shape[1]-1),str(i),str(blur[i,blur.shape[1]-1])]
               
          except:
                print("er")
          face_file_stl(cd_1, cd_2, cd_3)
          
    #making the second triangls  for right side     
          try:
                cd_2=[str(blur.shape[1]-1),str(i),"0"]
                cd_3=[str(blur.shape[1]-1),str(i+1),"0"]
                cd_1=[str(blur.shape[1]-1),str(i+1),str(blur[i+1,blur.shape[1]-1])]
          except:
                print("er")
    
          face_file_stl(cd_1, cd_2, cd_3)
    
    op_stl.write("\nendsolid" )
    op_stl.close()
        
    print('end')
    

    Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

    Знали ли вы о том как создавать 3d с помощью Python до статьи
    Поделиться публикацией
    Комментарии 12
      +2

      А где результаты?
      Где stl файл с полученной моделью...

        +2
        Файл на github
          0

          <sceptic mode="on">
          opencv и numpy — это, строго говоря, не "только Python"
          </sceptic>


          Да и зачем вам numpy, если он нигде не используется в коде? Но работать с большими массивами точек используя стандартные list питона — это весьма неэффективно.


          А в целом, плюс API блендера разве что в более удобной работе с STL и др. файлами моделей.

            0
            numpy нужен был при отладке кода(уже убрал), у Api блендера много плюсов, но его надо устанавливать.
            +1
            Почему-то вспоминается мем про рисование совы…
              0
              Ну хоть молодежь допёрла, что файлы (особенно текстовые по сути) можно писать вручную, без библиотек. Если так дальше пойдет — глядишь, и до вменяемого (небольшого) размера программ доживем!
                0
                Излишне оптимистично.
                +5
                Падажжите, автор просто измерил яркость каждой точки, взял эту яркость как координату Z и написал об этом статью. Без практического смысла и удачного результата. Все так?
                  0

                  Похоже на то. По хорошему тут нужно что-то типа тензорного голосования использовать.

                  +1
                  я дооолго въезжала, что на stl файл результата нужно смотреть сверху
                  ![](https://habrastorage.org/webt/3x/jc/mq/3xjcmqqwapbl2gay2yf2cyeuf7i.png)

                    0
                    Спасибо, я без подсказки не въехал:)
                    0
                    Наша сегодняшняя реальность — хороший пример модели дна

                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                    Самое читаемое