Divide a Surface in Rectangles

Hello community :slight_smile:

I wanted to share with you a quick script a did on Python that allows you to divide a given surface into rectangles, maybe this could be useful to somebody…

Restrictions : (It is possible to remove them, but currently don’t have the time to do so, maybe i’ll do it sometime)

  • The surface must be contained in one of the 3 main planes : (XY), (XZ), (YZ)
  • The surface must only have edges that are parallel to one of the 2 axis of the plane it is contained in.

Notes :

  • If the surface is too detailed, you may have to wait for about a minute to get the result. The example below took about 3 seconds.

Divide Surface in Rectangles.dyn (15.9 KB)

#Author : Mustapha ELLOUZE
#Date : 25-10-2018
#Contact : mellouze (Dynamo Forum username)

import clr
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *

surf = IN[0] #surface to divide in rectangles
polyg = IN[1] #polygon created from the surface
Xs = IN[2] #Unique and sorted X coordinates
Ys = IN[3] #Unique and sorted Y coordinates
Zs = IN[4] #Unique and sorted Zs coordinates


class Rect :
	#Class : rectangles that compose the surface
	
	def __init__(self, u_min, u_max, v_min, v_max, w, direction):
		#Constructor : u_min,u_max,v_min and v_max are the extreme coordinates on both directions, w is the coordinate in the third direction (all extreme points share this coordinate), direction is the plane that contains the Rect
		
		self.direction = direction; #defines the direction of the Rect
		
		self.w = w; #defines the third coordinate of the Rect
		
		self.u_min = u_min;
		self.u_max = u_max;
		self.v_min = v_min;
		self.v_max = v_max;
		
		#Definition of the 4 points that define the Rect, plus the center of the Rect (pt0)
		if(direction=="X"):
			self.pt1 = Point.ByCoordinates(w,u_min,v_min);
			self.pt2 = Point.ByCoordinates(w,u_min,v_max);
			self.pt3 = Point.ByCoordinates(w,u_max,v_max);
			self.pt4 = Point.ByCoordinates(w,u_max,v_min);
			self.pt0 = Point.ByCoordinates(w,0.5*u_min+0.5*u_max,0.5*v_min+0.5*v_max);
		elif(direction == "Y"):
			self.pt1 = Point.ByCoordinates(u_min,w,v_min);
			self.pt2 = Point.ByCoordinates(u_min,w,v_max);
			self.pt3 = Point.ByCoordinates(u_max,w,v_max);
			self.pt4 = Point.ByCoordinates(u_max,w,v_min);
			self.pt0 = Point.ByCoordinates(0.5*u_min+0.5*u_max,w,0.5*v_min+0.5*v_max);
		else:
			self.pt1 = Point.ByCoordinates(u_min,v_min,w);
			self.pt2 = Point.ByCoordinates(u_min,v_max,w);
			self.pt3 = Point.ByCoordinates(u_max,v_max,w);
			self.pt4 = Point.ByCoordinates(u_max,v_min,w);
			self.pt0 = Point.ByCoordinates(0.5*u_min+0.5*u_max,0.5*v_min+0.5*v_max,w);
	
	def contenu(self, polyg):
		#Determines if the Rect is contained by a polygon
		return Polygon.ContainmentTest(polyg,self.pt0)
		
	def fusionnable(self,autre_rect):
		#Determines if two Rect can be merged, i.e. if they share exactly a side
		bool1 = (self.u_max == autre_rect.u_min or self.u_min == autre_rect.u_max) and (self.v_min == autre_rect.v_min and self.v_max == autre_rect.v_max)
		bool2 = (self.v_max == autre_rect.v_min or self.v_min == autre_rect.v_max) and (self.u_min == autre_rect.u_min and self.u_max == autre_rect.u_max)
		return (bool1 or bool2);
		
	def fusion(self, autre_rect):
		#Returns a new Rect that is the fusion of two mergeable Rect
		u_m = min(self.u_min,autre_rect.u_min);
		u_M = max(self.u_max,autre_rect.u_max);
		v_m = min(self.v_min,autre_rect.v_min);
		v_M = max(self.v_max,autre_rect.v_max);
		
		return Rect(u_m,u_M,v_m,v_M,self.w,self.direction);



#Determination of the direction of the initial surface, then compute the extreme coordinates of the axis (the chosen axis depends on the direction)
if(len(Xs)==1):
	direction = "X";
	u_min = min(Ys);
	u_max = max(Ys);
	v_min = min(Zs);
	v_max = max(Zs);
	w = Xs[0];
	u_range = Ys;
	v_range = Zs;
elif(len(Ys)==1):
	direction = "Y";
	u_min = min(Xs);
	u_max = max(Xs);
	v_min = min(Zs);
	v_max = max(Zs);
	w = Ys[0];
	u_range = Xs;
	v_range = Zs;
else:
	direction = "Z";
	u_min = min(Xs);
	u_max = max(Xs);
	v_min = min(Ys);
	v_max = max(Ys);
	w = Zs[0];
	u_range = Xs;
	v_range = Ys;	

#Creation of the initial rectangles.
liste_rect = [];

for i in range(len(u_range)-1):
	for j in range(len(v_range)-1):
		new_rect = Rect(u_range[i],u_range[i+1],v_range[j],v_range[j+1],w,direction); #creation of a Rect that might be contained in the surface
		if(new_rect.contenu(polyg)): #if it is indeed inside the surface
			liste_rect.append(new_rect); #we add it to the list
			
#Fusion of all the Rect

i = 0 #index of the Rect we are currently looking at

while(True): #Nota : please don't mess with the break statements...
	
	#Break condition : we stop if we looked to all the Rect in the list (i.e. currently looking to the last Rect)
	if(i == len(liste_rect)):
		break;
	
	rect_courant = liste_rect[i]; #Current Rect
	
	indice = -1; #Initialization of index of a potential Rect to merge with current Rect
	
	#Try to find a Rect to merge with the current rect
	for j in range(i+1,len(liste_rect)): #for each Rect in the list that is not the current rect
		if(rect_courant.fusionnable(liste_rect[j])): #if the two Rect are mergeable, we keep the index and we get out of this for loop
			indice = j;
			break;
	
	#If we did not find anything, we go to the next Rect
	if(indice==-1):
		i = i+1;
		continue;
	
	#If we did find something, we merge the two Rect and we delete the two old Rect from the list
	liste_rect[i] = rect_courant.fusion(liste_rect[j]);
	liste_rect.pop(j);
	
	#And we go back to the very first Rect (we might have created a Rect that is mergeable)
	i = 0;

#Initialisation of the output (list of all the sub-surfaces
resultat = []
for i in range(len(liste_rect)):
	resultat.append(Surface.ByPatch(PolyCurve.ByPoints([liste_rect[i].pt1,liste_rect[i].pt2,liste_rect[i].pt3,liste_rect[i].pt4], True)))
	

OUT = resultat
12 Likes

Hi again !

Now works with openings ! :smiley: Here are the updated files. Note that this version requires the GroupCurves node made by @Konrad_K_Sobon (archi-lab package).

Divide Surface In Rectangles.dyn (23.3 KB)

#Author : Mustapha ELLOUZE
#Date : 30-10-2018
#Contact : mellouze (Dynamo Forum username)

import clr
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *

polyg = IN[0] #polygon created from the main surface
polyg_opening = IN[1] #polygon created from the openings
Xs = IN[2] #Unique and sorted X coordinates
Ys = IN[3] #Unique and sorted Y coordinates
Zs = IN[4] #Unique and sorted Zs coordinates


class Rect :
	#Class : rectangles that compose the surface
	
	def __init__(self, u_min, u_max, v_min, v_max, w, direction):
		#Constructor : u_min,u_max,v_min and v_max are the extreme coordinates on both directions, w is the coordinate in the third direction (all extreme points share this coordinate), direction is the plane that contains the Rect
		
		self.direction = direction; #defines the direction of the Rect
		
		self.w = w; #defines the third coordinate of the Rect
		
		self.u_min = u_min;
		self.u_max = u_max;
		self.v_min = v_min;
		self.v_max = v_max;
		
		#Definition of the 4 points that define the Rect, plus the center of the Rect (pt0)
		if(direction=="X"):
			self.pt1 = Point.ByCoordinates(w,u_min,v_min);
			self.pt2 = Point.ByCoordinates(w,u_min,v_max);
			self.pt3 = Point.ByCoordinates(w,u_max,v_max);
			self.pt4 = Point.ByCoordinates(w,u_max,v_min);
			self.pt0 = Point.ByCoordinates(w,0.5*u_min+0.5*u_max,0.5*v_min+0.5*v_max);
		elif(direction == "Y"):
			self.pt1 = Point.ByCoordinates(u_min,w,v_min);
			self.pt2 = Point.ByCoordinates(u_min,w,v_max);
			self.pt3 = Point.ByCoordinates(u_max,w,v_max);
			self.pt4 = Point.ByCoordinates(u_max,w,v_min);
			self.pt0 = Point.ByCoordinates(0.5*u_min+0.5*u_max,w,0.5*v_min+0.5*v_max);
		else:
			self.pt1 = Point.ByCoordinates(u_min,v_min,w);
			self.pt2 = Point.ByCoordinates(u_min,v_max,w);
			self.pt3 = Point.ByCoordinates(u_max,v_max,w);
			self.pt4 = Point.ByCoordinates(u_max,v_min,w);
			self.pt0 = Point.ByCoordinates(0.5*u_min+0.5*u_max,0.5*v_min+0.5*v_max,w);
	
	def contenu(self, polyg, polyg_opn):
		#Determines if the Rect is contained in the surface
		result = Polygon.ContainmentTest(polyg,self.pt0)
		for i in range(len(polyg_opn)):
			result = result and (not (Polygon.ContainmentTest(polyg_opn[i],self.pt0)))
		return result
		
	def fusionnable(self,autre_rect):
		#Determines if two Rect can be merged, i.e. if they share exactly a side
		bool1 = (self.u_max == autre_rect.u_min or self.u_min == autre_rect.u_max) and (self.v_min == autre_rect.v_min and self.v_max == autre_rect.v_max)
		bool2 = (self.v_max == autre_rect.v_min or self.v_min == autre_rect.v_max) and (self.u_min == autre_rect.u_min and self.u_max == autre_rect.u_max)
		return (bool1 or bool2);
		
	def fusion(self, autre_rect):
		#Returns a new Rect that is the fusion of two mergeable Rect
		u_m = min(self.u_min,autre_rect.u_min);
		u_M = max(self.u_max,autre_rect.u_max);
		v_m = min(self.v_min,autre_rect.v_min);
		v_M = max(self.v_max,autre_rect.v_max);
		
		return Rect(u_m,u_M,v_m,v_M,self.w,self.direction);



#Determination of the direction of the initial surface, then compute the extreme coordinates of the axis (the chosen axis depends on the direction)
if(len(Xs)==1):
	direction = "X";
	w = Xs[0];
	u_range = Ys;
	v_range = Zs;
elif(len(Ys)==1):
	direction = "Y";
	w = Ys[0];
	u_range = Xs;
	v_range = Zs;
else:
	direction = "Z";
	w = Zs[0];
	u_range = Xs;
	v_range = Ys;	

#Creation of the initial rectangles.
liste_rect = [];

for i in range(len(u_range)-1):
	for j in range(len(v_range)-1):
		new_rect = Rect(u_range[i],u_range[i+1],v_range[j],v_range[j+1],w,direction); #creation of a Rect that might be contained in the surface
		if(new_rect.contenu(polyg, polyg_opening)): #if it is indeed inside the surface
			liste_rect.append(new_rect); #we add it to the list
			
#Fusion of all the Rect

i = 0 #index of the Rect we are currently looking at

while(True): #Nota : please don't mess with the break statements...
	
	#Break condition : we stop if we looked to all the Rect in the list (i.e. currently looking to the last Rect)
	if(i == len(liste_rect)):
		break;
	
	rect_courant = liste_rect[i]; #Current Rect
	
	indice = -1; #Initialization of index of a potential Rect to merge with current Rect
	
	#Try to find a Rect to merge with the current rect
	for j in range(i+1,len(liste_rect)): #for each Rect in the list that is not the current rect
		if(rect_courant.fusionnable(liste_rect[j])): #if the two Rect are mergeable, we keep the index and we get out of this for loop
			indice = j;
			break;
	
	#If we did not find anything, we go to the next Rect
	if(indice==-1):
		i = i+1;
		continue;
	
	#If we did find something, we merge the two Rect and we delete the two old Rect from the list
	liste_rect[i] = rect_courant.fusion(liste_rect[j]);
	liste_rect.pop(j);
	
	#And we go back to the very first Rect (we might have created a Rect that is mergeable)
	i = 0;

#Initialisation of the output (list of all the sub-surfaces
resultat = []
for i in range(len(liste_rect)):
	resultat.append(Surface.ByPatch(PolyCurve.ByPoints([liste_rect[i].pt1,liste_rect[i].pt2,liste_rect[i].pt3,liste_rect[i].pt4], True)))
	

OUT = resultat

GroupCurves Script :

#Copyright(c) 2015, Konrad Sobon
# @arch_laboratory, http://archi-lab.net

import clr
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *

#The inputs to this node will be stored as a list in the IN variable.
dataEnteringNode = IN

inputCurves = IN[0]

#join/group curves function
def groupCurves(Line_List): 
	ignore_distance = 0.1 # Assume points this close or closer to each other are touching 
	Grouped_Lines = [] 
	Queue = set() 
	while Line_List: 
		Shape = [] 
		Queue.add(Line_List.pop()) # Move a line from the Line_List to our queue 
		while Queue: 
			Current_Line = Queue.pop() 
			Shape.append(Current_Line) 
			for Potential_Match in Line_List: 
				Points = (Potential_Match.StartPoint, Potential_Match.EndPoint)
				for P1 in Points: 
					for P2 in (Current_Line.StartPoint, Current_Line.EndPoint): 
						distance = P1.DistanceTo(P2) 
						if distance <= ignore_distance: 
							Queue.add(Potential_Match) 
			Line_List = [item for item in Line_List if item not in Queue]
		Grouped_Lines.append(Shape) 
	return Grouped_Lines

OUT = groupCurves(inputCurves)
7 Likes

Hi Mellouze!
Just wanted to say I used your code (and modified it a little to only connect rectangle right on top of each other.)
This is brilliant, thank you!!
Uploading my edited code here if someone wants to check it out in future:

My Code

#Author : Mustapha ELLOUZE
#Date : 25-10-2018
#Contact : mellouze (Dynamo Forum username)

import clr
clr.AddReference(‘ProtoGeometry’)
from Autodesk.DesignScript.Geometry import *

surf = IN[0] #surface to divide in rectangles
polyg = IN[1] #polygon created from the surface
Xs = IN[2] #Unique and sorted X coordinates
Ys = IN[3] #Unique and sorted Y coordinates
Zs = IN[4] #Unique and sorted Zs coordinates

class Rect :
#Class : rectangles that compose the surface

def __init__(self, horizontal_min, horizontal_max, vertical_min, vertical_max, w, direction):
	#Constructor : horizontal_min,horizontal_max,vertical_min and vertical_max are the extreme coordinates on both directions, w is the coordinate in the third direction (all extreme points share this coordinate), direction is the plane that contains the Rect
	
	self.direction = direction; #defines the direction of the Rect
	
	self.w = w; #defines the third coordinate of the Rect
	
	self.horizontal_min = horizontal_min;
	self.horizontal_max = horizontal_max;
	self.vertical_min = vertical_min;
	self.vertical_max = vertical_max;
	
	#Definition of the 4 points that define the Rect, plus the center of the Rect (pt0)
	if(direction=="X"):
		self.pt1 = Point.ByCoordinates(w,horizontal_min,vertical_min);
		self.pt2 = Point.ByCoordinates(w,horizontal_min,vertical_max);
		self.pt3 = Point.ByCoordinates(w,horizontal_max,vertical_max);
		self.pt4 = Point.ByCoordinates(w,horizontal_max,vertical_min);
		self.pt0 = Point.ByCoordinates(w,0.5*horizontal_min+0.5*horizontal_max,0.5*vertical_min+0.5*vertical_max);
	elif(direction == "Y"):
		self.pt1 = Point.ByCoordinates(horizontal_min,w,vertical_min);
		self.pt2 = Point.ByCoordinates(horizontal_min,w,vertical_max);
		self.pt3 = Point.ByCoordinates(horizontal_max,w,vertical_max);
		self.pt4 = Point.ByCoordinates(horizontal_max,w,vertical_min);
		self.pt0 = Point.ByCoordinates(0.5*horizontal_min+0.5*horizontal_max,w,0.5*vertical_min+0.5*vertical_max);
	else:
		self.pt1 = Point.ByCoordinates(horizontal_min,vertical_min,w);
		self.pt2 = Point.ByCoordinates(horizontal_min,vertical_max,w);
		self.pt3 = Point.ByCoordinates(horizontal_max,vertical_max,w);
		self.pt4 = Point.ByCoordinates(horizontal_max,vertical_min,w);
		self.pt0 = Point.ByCoordinates(0.5*horizontal_min+0.5*horizontal_max,0.5*vertical_min+0.5*vertical_max,w);

def doesContain(self, polyg):
	#Determines if the Rect is contained by a polygon
	return Polygon.ContainmentTest(polyg,self.pt0)
	
def fusionnable(self,other_rectangle):
	#Determines if two Rect can be merged, i.e. if they share exactly a side
	bool1 = (self.vertical_max == other_rectangle.vertical_min or self.vertical_min == other_rectangle.vertical_max) and (self.horizontal_max==other_rectangle.horizontal_max) and (self.horizontal_min==other_rectangle.horizontal_min) #and (self.vertical_min == other_rectangle.vertical_min and self.vertical_max == other_rectangle.vertical_max)
	#bool2 = (self.vertical_max == other_rectangle.vertical_min or self.vertical_min == other_rectangle.vertical_max) and (self.horizontal_min == other_rectangle.horizontal_min and self.horizontal_max == other_rectangle.horizontal_max)
	return (bool1); #or bool2);
	
def fusion(self, other_rectangle):
	#Returns a new Rect that is the fusion of two mergeable Rect
	u_m = min(self.horizontal_min,other_rectangle.horizontal_min);
	u_M = max(self.horizontal_max,other_rectangle.horizontal_max);
	v_m = min(self.vertical_min,other_rectangle.vertical_min);
	v_M = max(self.vertical_max,other_rectangle.vertical_max);
	
	return Rect(u_m,u_M,v_m,v_M,self.w,self.direction);

#Determination of the direction of the initial surface, then compute the extreme coordinates of the axis (the chosen axis depends on the direction)
if(len(Xs)==1):
direction = “X”;
horizontal_min = min(Ys);
horizontal_max = max(Ys);
vertical_min = min(Zs);
vertical_max = max(Zs);
w = Xs[0];
u_range = Ys;
v_range = Zs;
elif(len(Ys)==1):
direction = “Y”;
horizontal_min = min(Xs);
horizontal_max = max(Xs);
vertical_min = min(Zs);
vertical_max = max(Zs);
w = Ys[0];
u_range = Xs;
v_range = Zs;
else:
direction = “Z”;
horizontal_min = min(Xs);
horizontal_max = max(Xs);
vertical_min = min(Ys);
vertical_max = max(Ys);
w = Zs[0];
u_range = Xs;
v_range = Ys;

#Creation of the initial rectangles.
rectangle_list = ;

for i in range(len(u_range)-1):
for j in range(len(v_range)-1):
new_rect = Rect(u_range[i],u_range[i+1],v_range[j],v_range[j+1],w,direction); #creation of a Rect that might be contained in the surface
if(new_rect.doesContain(polyg)): #if it is indeed inside the surface
rectangle_list.append(new_rect); #we add it to the list

#Fusion of all the Rect

i = 0 #index of the Rect we are currently looking at

while(True): #Nota : please don’t mess with the break statements…

#Break condition : we stop if we looked to all the Rect in the list (i.e. currently looking to the last Rect)
if(i == len(rectangle_list)):
	break;

current_rectangle = rectangle_list[i]; #Current Rect

indice = -1; #Initialization of index of a potential Rect to merge with current Rect

#Try to find a Rect to merge with the current rect
for j in range(i+1,len(rectangle_list)): #for each Rect in the list that is not the current rect
	if(current_rectangle.fusionnable(rectangle_list[j])): #if the two Rect are mergeable, we keep the index and we get out of this for loop
		indice = j;
		break;

#If we did not find anything, we go to the next Rect
if(indice==-1):
	i = i+1;
	continue;

#If we did find something, we merge the two Rect and we delete the two old Rect from the list
rectangle_list[i] = current_rectangle.fusion(rectangle_list[j]);
rectangle_list.pop(j);

#And we go back to the very first Rect (we might have created a Rect that is mergeable)
i = 0;

#Initialisation of the output (list of all the sub-surfaces
result =
for i in range(len(rectangle_list)):
result.append(Surface.ByPatch(PolyCurve.ByPoints([rectangle_list[i].pt1,rectangle_list[i].pt2,rectangle_list[i].pt3,rectangle_list[i].pt4], True)))

OUT = result

Hi :slight_smile:
Glad it could help someone in any manner. Thanks for the share !

2 Likes

Hello! I tried your code and it works great! I am curious if you know of a way to take this exact idea, but give the new rectangles maximum width’s and heights? So if a rectangle is over a certain dimension, it would be divided up into equal segments? Thank you in advance!

Hi :slight_smile:
To be quite honest, I do not have the time right now to modify this code, but the way I would do it is by changing the Rect.fusionnable() method.
This method returns a boolean that indicates if the the two rectangles should be merged. You should modify it so it returns False if the height/width the final rectangle would have is superior to the maximum height/width.

Hope this helps.

Ps : Might take a look at it in a few days…

Hi @mellouze
Thank you for the reply! I was able to use the code just as you have it, then split the geometry with planes, so I think it’s taken care of. Thank you again for posting this and for replying to me! Much appreciated!

I am trying to made dynamo calculate some design metrics and I tryed to use it, but it report an error in Dynamo Engine X.X

Does it work in 2.xx version ?

This was the testing surface:

Hi,

Yes, the script does work on Dynamo 2 (tested on 2.0.3).
I think the problem is that your surface is not continous. Try only feeding the script continuous/contiguous surfaces.

1 Like

Hi!! Thank you all. This is a great one.

Anyone getting this to work with Dynamo 2.3?
For me is not working.

I’m getting this errors:

at Code Block:
Warning: Multiple definitions for ‘Curve’ are found as Autodesk.DesignScript.Geometry.Curve, Curve
Method ‘If()’ not found
Internal error, please report: Dereferencing a non-pointer.

at Phyton Script:
Warning: Multiple definitions for ‘Curve’ are found as Autodesk.DesignScript.Geometry.Curve, Curve
Method ‘If()’ not found
Internal error, please report: Dereferencing a non-pointer.

Can you help me updating ?

Thank you in advance.

How about multiple input surfaces? can you make that work to?

I’d suggest creating a custom node of both nodes of my method, indicate to Dynamo that the input is supposed to be a surface, then feed the custom node a list of Surfaces.

1 Like

thanks, i got it working.
another question, how could i manipulate the “direction” or prefer the more square rectangles?

I didn’t undestrand your question :confused:

  • Do you want to have as many square as possible (maximize the number of square) ?
  • Do you want to have the biggest square possible ? And the fill the rest in the best way possible ?
  • Do you want to force the direction of the rectangles ?

The second one, except it doesn’t have to be perfectly square.

I want to determine the best spot for Dynamo to place an Area Tag.

the blue lines are the areaboundaries.
the points are the chosen locations.

OK, so I have a few ideas, but I think you’ll have to test them yourselves and tweak them a little bit.
Basically, they all revolve around the fact that the shape of the output rectangles heavily depends on the ordre in which you cycle through the items of liste_rect in my Python script.

  • Idea 1 : You can create liste_rect along the other direction by switching the order of the two following lines (the main directions of the rectangles will be switched.
for i in range(len(u_range)-1):
	for j in range(len(v_range)-1):
  • Idea 2 : You could run the script two times for each surface and make the script choose the best decomposition based on the biggest surface that it could produce.
  • Idea 3 : You could choose the direction based on the ratio between the two “principal lengths”.
  • Idea 4 : You could determine the center of each of your surfaces and make the while loop start at the Rect object that is the closest to the center (to force a bigger rectangle to be created at that place).
  • Idea 5 : You could sort the Rect objects in liste_rect based on their distance to the center of your surface.

Etc.

But at the end of the day, I think that the best solution will heavily depend on your project and on the way you define “best spot” :sweat_smile: