Pull to refresh

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

Reading time5 min
Views4.6K

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

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

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

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

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

данный код уже не работает в 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-код для станка:

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

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

Only registered users can participate in poll. Log in, please.
ЧПУ фрезер в квартире — что это?
7.89% роскошь3
44.74% причуда17
10.53% предмет первой необходимости4
7.89% без него вообще никуда3
28.95% условие технологической сингулярности11
38 users voted. 4 users abstained.
Tags:
Hubs:
Total votes 14: ↑12 and ↓2+10
Comments2

Articles