Divide a Surface in Rectangles

geometry

#1

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

#2

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)