# Divide a Surface in Rectangles

Hello community

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
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
10 Likes

Hi again !

Now works with openings ! 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
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 :

# @arch_laboratory, http://archi-lab.net

import clr
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:
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
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
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
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