[recovery mode] Slicer: нарезка твердотельных объектов под раскрой

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

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

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

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

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

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

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

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

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

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

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

71d3c097d57f6d90a8c036ef8d12eb26.png

И наконец, его величество код! Исполнять в консоли редактора 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»))

dca8654eef9cecdca147eb8445c276c5.png

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

ac5141636e4c76f966211008fe5b54d0.png

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

7398e648414cfd7f96d00a419a816d19.png

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

f5f6870cece7bcd84a5b0080fddecf9b.png

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

cf3d63c797cb45504e9007940ae9da8c.png

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

68e2ba77c3cf168452b85656007b624e.png

Видео-версия:

© Habrahabr.ru