Slicer: нарезка твердотельных объектов под раскрой используя python.

Slicer: нарезка твердотельных объектов под раскрой используя python.



Всем привет. Красивая фэшн мебель, предметы роскоши и модный лухари интерьер - это то, что позволяет пустить пыль в глаза вашим гостям и прослыть хозяевам интересными людьми. Но как все это раздобыть, если у вас ипотека и бюджет ограничен?

Этими вопросами я и задался, когда решил построить свой личный CNC станок.

Если конкретнее, я увидел на Pinterest всяческие модные штуки типа этой:

себестоимость материала - 3 копейки

Воссоздать такую красоту нам поможет формула волны, известная из школьного курса y=sin(x), правда я модифицировал ее, добавив затухание, и спустя пару часов с момента, как я впервые увидел язык ruby, в редакторе SketchUp у меня оформился вот такой результат (листинг рассмотрим в другой раз):

данный код уже не работает в SketchUp, они плевать хотели на обратную совместимость

Но, если речь идет об объектах серьезных размеров, хотелось бы как-то сократить количество материала. Посмотрите на следующий объект:

это мебель, или просто часть стены?..

Здесь появляется следующая проблема: как преобразовать 3D объект в каркас из листового материала, части которого стыкуются между собой, желательно без крепежа, паз в паз? Не секрет, что на рынке игрушек есть разнообразные конструкторы, разработчики которых решили эту проблему:

наивный байесовский олень

в открытом виде такой код мне обнаружить не удалось, поэтому пришлось писать свой собственный высококачественный лапшекод, за который "coder-nazi" накидают мне минусов в карму, думая что этого достаточно для его автоматического улучшения. Перед тем как вы это увидите, отвлекитесь от детских конструкторов и взгляните, что могут сделать толковые ребята в более серьезных масштабах:

И наконец, его величество код! Исполнять в консоли редактора FreeCad.

App.ActiveDocument.addObject("Part::Sphere","Sphere")
App.ActiveDocument.ActiveObject.Label = "Sphere"
s_offset = 200
App.ActiveDocument.Sphere.Radius = s_offset
#App.ActiveDocument.Sphere.Placement = App.Placement(App.Vector(500,500,500),App.Rotation(App.Vector(0,0,1),0)) 
App.ActiveDocument.recompute()


def isEmpty(sh):
 if sh == None:
  return True
 if sh.Shape.BoundBox.ZMin == sys.float_info.max and sh.Shape.BoundBox.ZMax == -sys.float_info.max:
  return True
 return False


def slice(name):
 if name == "":
  name = "Sphere"
 sx = 1310
 sy = 1510

 offset = 10000

 rHeigth = 1400
 rThickness = 4
 rLength = 1500

 stepx = 80
 stepy = 80


 dx = 10
 dy = 10

 mx = {}
 my = {}
 
 n = 0  
 copy =  FreeCAD.ActiveDocument.copyObject(FreeCAD.activeDocument().getObject(name),False)
 copy.Placement = App.Placement(App.Vector(s_offset,offset+s_offset,s_offset),App.Rotation(App.Vector(0,0,1),0))  
 while dx < sx:
  box = App.ActiveDocument.addObject("Part::Box","Box"+str(n))
  box.Length=rThickness
  box.Width=rLength

  box.Height=rHeigth
  box.Placement=App.Placement(App.Vector(dx,offset,0),App.Rotation(App.Vector(0,0,1),0)) 

  common = App.activeDocument().addObject("Part::MultiCommon","CommonX"+str(n))
  common.Shapes = [copy,box]
  #common.Placement=App.Placement(App.Vector(0,0,0),App.Rotation(App.Vector(0,0,1),0)) 
  
  #copy.Visibility=False
  #box.Visibility=False
  
  #common.ShapeColor=copy.ShapeColor
  #common.DisplayMode=copy.DisplayMode
  mx[n] = common
  
  dx = dx + stepx
  n = n + 1
 
 colx = n
 n = 0
 while dy < sy:
  box = App.ActiveDocument.addObject("Part::Box","Box"+str(n))
  box.Length=rLength
  box.Width=rThickness
  box.Height=rHeigth
  box.Placement=App.Placement(App.Vector(0,offset+dy,0),App.Rotation(App.Vector(0,0,1),0)) 

  common = App.activeDocument().addObject("Part::MultiCommon","CommonY"+str(n))
  common.Shapes = [copy,box]
  #common.Placement=App.Placement(App.Vector(0,0,0),App.Rotation(App.Vector(0,0,1),0)) 
  
  #copy.Visibility=False
  #box.Visibility=False  
  #common.ShapeColor=copy.ShapeColor
  #common.DisplayMode=copy.DisplayMode
  my[n] = common
  
  dy = dy + stepy
  n = n + 1
 coly = n
 print("!!!---")
 App.ActiveDocument.recompute()
 inter = {}
 for i in range(0,colx):
  for j in range(0,coly):
   if not (isEmpty(mx[i]) or isEmpty(my[j])):
    common = App.activeDocument().addObject("Part::MultiCommon","ComX"+str(i)+"Y"+str(j))
    common.Shapes = [mx[i],my[j]]   
    inter[(i,j)] = common
    #print(str(i)+" "+ str(j)+" " + str(common.Shape.BoundBox.ZMax)+" "+str(common.Shape.BoundBox.ZMax))
 
 App.ActiveDocument.recompute() 
 for i in range(0,colx):
  for j in range(0,coly):
   if not isEmpty(inter.get((i,j))):
    com = inter.get((i,j))    
    d = (com.Shape.BoundBox.ZMax - com.Shape.BoundBox.ZMin)/2  
    
    box = App.ActiveDocument.addObject("Part::Box","Box"+str(n))
    box.Length= com.Shape.BoundBox.XMax - com.Shape.BoundBox.XMin
    box.Width= com.Shape.BoundBox.YMax - com.Shape.BoundBox.YMin
    box.Height=d+rThickness/2
    box.Placement=App.Placement(App.Vector(com.Shape.BoundBox.XMin,com.Shape.BoundBox.YMin,com.Shape.BoundBox.ZMin),App.Rotation(App.Vector(0,0,1),0)) 
    
    box1 = App.ActiveDocument.copyObject(box, False) 
    box1.Placement=App.Placement(App.Vector(com.Shape.BoundBox.XMin,com.Shape.BoundBox.YMin,com.Shape.BoundBox.ZMin+d-rThickness/2),App.Rotation(App.Vector(0,0,1),0)) 
    
    com2 = App.activeDocument().addObject("Part::MultiCommon","com2_")
    com2.Shapes = [com,box]
    
    com3 = App.activeDocument().addObject("Part::MultiCommon","com3_")
    com3.Shapes = [com,box1]
    
    comX = App.activeDocument().addObject("Part::Cut","CutX"+str(i))
    comX.Base = mx[i]
    comX.Tool = com2
    mx[i] = comX            
    comY = App.activeDocument().addObject("Part::Cut","CutY"+str(j))
    comY.Base = my[j]
    comY.Tool = com3
    my[j] = comY
           
    #print(str(i)+" "+ str(j))  
 App.ActiveDocument.recompute()
 for i in range(0,colx):
  if not (isEmpty(mx[i])):
   mx[i].Placement=App.Placement(App.Vector(stepx*i,20000,0),App.Rotation(App.Vector(0,1,0),90)) 
 for j in range(0,coly):
  if not (isEmpty(my[j])):
   my[j].Placement=App.Placement(App.Vector(20000,stepy*j,0),App.Rotation(App.Vector(1,0,0),90))        
 #App.ActiveDocument.addObject('Part::Feature','Line002').Shape=App.ActiveDocument.Line002.Shape.removeSplitter()
 #App.ActiveDocument.Common.Shape.BoundBox.ZMax 
 App.ActiveDocument.recompute()
 x1=rThickness
 y1=rThickness
 yMax = 0
 #from typing import Dict, List
 all = list()#List[DocumentObject] 
 for i in range(0,colx):
  if not (isEmpty(mx[i])):
   if (x1+(mx[i].Shape.BoundBox.XMax-mx[i].Shape.BoundBox.XMin)) > sx:
    x1 = 0 
    y1 = yMax+rThickness*3
   x=mx[i].Shape.BoundBox.XMin
   y=mx[i].Shape.BoundBox.YMin
   z=mx[i].Shape.BoundBox.ZMin 
   mx[i].Placement.move(FreeCAD.Base.Vector(-x+x1+rThickness*3,-y+y1,-z))   
   x1=mx[i].Shape.BoundBox.XMax
   if (mx[i].Shape.BoundBox.YMax>yMax):
    yMax = mx[i].Shape.BoundBox.YMax
   all.append(App.activeDocument().getObject(mx[i].Name))
 for j in range(0,coly):
  if not (isEmpty(my[j])):
   if (x1+(my[j].Shape.BoundBox.XMax-my[j].Shape.BoundBox.XMin)) > sx:
    x1 = 0 
    y1 = yMax+rThickness*3  
   x=my[j].Shape.BoundBox.XMin
   y=my[j].Shape.BoundBox.YMin
   z=my[j].Shape.BoundBox.ZMin
   my[j].Placement.move(FreeCAD.Base.Vector(-x+x1+rThickness*3,-y+y1,-z))
   #App.ActiveDocument.recompute()
   x1=my[j].Shape.BoundBox.XMax
   #print "--"+str(x1)
   #y1=my[j].Shape.BoundBox.YMax
   if (my[j].Shape.BoundBox.YMax>yMax):
    yMax = my[j].Shape.BoundBox.YMax
   all.append(App.activeDocument().getObject(my[j].Name))
 #print all
 Gui.activeDocument().getObject(name).Visibility=False
 App.activeDocument().addObject("Part::Compound","Compound")
 App.activeDocument().Compound.Links = all
 App.ActiveDocument.recompute()
 return "" 


slice("")



Для начала, создается сфера (App.ActiveDocument.addObject("Part::Sphere","Sphere"))

Далее, она нарезается горизонтально решеткой рассекающих Box'ов:

Особенность ее - в том, что на половине высоты каждого пересечения у продольных и поперечных ребер вырезается паз. Так как у меня фрезер, а не лазерный станок, пазы следует увеличить, иначе за счет радиуса фрезы при вставке деталей будет пазы окажутся недостаточными. Но ребра еще предстоит разложить на листе материала:

На основе этого compaund'а из всех деталей можно построить программу для станка. Программно я этого сделать не смог (может, кто-то подскажет как это сделать), поэтому следует вручную добавить Job, выставить параметры фрезы, и добавить тех. операцию profile (к которой было бы неплохо добавить "Перемычки для удержания деталей в заготовке", иначе детали разбросает после отделения от листа):

Отсюда уже можно экспортировать G-код для станка:

А результат уже был показан ранее:

Report Page