GE4 - room

This is the most bassic class of this program. We need to define a class Room which can contain all the useful geometric information of our given plan.


Basic class Room’s structure

A room can be defined by two geometric properties:

Hence our GHpython component entries will look something like this:

Where,


Let’s see now how we can build our python class Room:

class Room:
    def __init__(self,
                 contour,
                 obstacles=[]
                 ):
        self.contour = contour
        self._off_contour = None
        self.obstacles = obstacles

if __name__ == "__main__":
    room = Room(contour=i_contour,
                obstacles=i_obstacles)
    a = room


This is the bare minimum structure for our class of type Room


Room properties’ checks

But we can do actually better than this. In fact, we could add some checks to our input parameters, to verify that the correct type is provided. Polyline and Curve are very similar but there are exceptions of course in their class methods and the way they behave. We want to be sure to distinguish them. We also want to be sure that our room’s contour is closed. To do so we can modifiy our code like so:

from Grasshopper.Kernel import GH_RuntimeMessageLevel as RML

class Room:
    def __init__(self,
                 contour,
                 obstacles=[]
                 ):
        if not isinstance(contour, rg.Polyline):
            ghenv.Component.AddRuntimeMessage(RML.Error, "It is not a polyline")
        if not contour.IsClosed:
            ghenv.Component.AddRuntimeMessage(RML.Error, "The curve is not closed")
        self.contour = contour
        self.obstacles = obstacles

if __name__ == "__main__":
    room = Room(contour=i_contour,
                obstacles=i_obstacles)
    a = room


This will raise a small error tag:

If you want to know more about error tags have a look at the doc.

We are missing a last check for our class’s properties for which we need to write our first class method. We need to verify that the contour has the right direction. Yes it might sound strange but closed curves they have a different directions: either clockwise or counterclockwise. We need to be sure that it is the latter for reasons related to how the nester is designed. Here’s the method class in charge of this.

import Rhino.Geometry as rg

def check_curve_orientation(self):
        temp_poly_crv = self.contour.ToPolylineCurve()
        temp_crv_orientation = temp_poly_crv.ClosedCurveOrientation(rg.Vector3d.ZAxis)
        if temp_crv_orientation != rg.CurveOrientation.Clockwise:
            self.contour.Reverse()


Note that the function is called with self as argument because it refers to the class object itself.

Here’s what our class is looking like by now:

import Rhino.Geometry as rg
from Grasshopper.Kernel import GH_RuntimeMessageLevel as RML

class Room:
    def __init__(self,
                 contour,
                 obstacles=[]
                 ):
        if not isinstance(contour, rg.Polyline):
            ghenv.Component.AddRuntimeMessage(RML.Error, "It is not a polyline")
        if not contour.IsClosed:
            ghenv.Component.AddRuntimeMessage(RML.Error, "The curve is not closed")
        self.contour = contour
        self.check_curve_orientation()

        self.obstacles = obstacles
    
    def check_curve_orientation(self):
        temp_poly_crv = self.contour.ToPolylineCurve()
        temp_crv_orientation = temp_poly_crv.ClosedCurveOrientation(rg.Vector3d.ZAxis)
        if temp_crv_orientation != rg.CurveOrientation.Clockwise:
            self.contour.Reverse()

if __name__ == "__main__":
    room = Room(contour=i_contour,
                obstacles=i_obstacles)
    a = room


🦊 These checks are not necessary, your code will run without. But your code will become more defensive: it will have less risk of having bugs and your entire development will result less error-prone.


Room’s class methods

By now we are missing only the class methods. The functions that we will call outside the class. This class will have 3 methods (beside the function check we just wrote):

def add_obstacle(self, obstacle_crv):
    self.obstacles.append(obstacle_crv)


It allows to add objects to the obstacles collection.

def get_offset_contour(self):
    if self._off_contour is None:
        temp_crv = self.contour.ToNurbsCurve()
        temp_crv_off = temp_crv.Offset(rg.Plane.WorldXY,
                                       -TOLERANCE*10,
                                       TOLERANCE,
                                       rg.CurveOffsetCornerStyle.Sharp)[0]
        _, self._off_contour = temp_crv_off.TryGetPolyline()
    return self._off_contour


It makes possible to build an offset of the room needed later for the nester.

Duplicate method

⚠️This is a very important method for classes containing geometric properties. If you dont implement it, you will loose all the linked geometric properties to your newly created object class.

This is a particular method that allows you to copy the current class with all its geometric properties. You return an instance of the class by copying all the geometric properties of the class in it.

def Duplicate(self):
    room_copy = Room(contour=self.contour.Duplicate(),
                     obstacles=[o.DuplicateCurve() for o in self.obstacles])
    room_copy._off_contour = rg.Polyline(self.get_offset_contour())
    
    return room_copy


It duplicate the object and all its geometric properties. The naming is particular (it uses capital letter) but it is intended to be an exception to mimic the RhinoCommons naming convention for the function Duplicate().


The final code

import Rhino.Geometry as rg
from Grasshopper.Kernel import GH_RuntimeMessageLevel as RML
import Rhino as r

TOLERANCE = r.RhinoDoc.ActiveDoc.ModelAbsoluteTolerance

class Room:
    def __init__(self,
                 contour,
                 obstacles=[]
                 ):
        """
            __init__() is the constructor for the Room object.
            
            :param contour: (Rhino.Geometry.Curve) It is the closed contour of the room
            :param obstacles: (List(Rhino.Geometry.Curve)) A list of closed curves indicating
            openings and other obstacles in the room where furniture should not go.
            
            :return: the Room object
        """
        if not isinstance(contour, rg.Polyline):
            ghenv.Component.AddRuntimeMessage(RML.Error, "It is not a polyline")
        if not contour.IsClosed:
            ghenv.Component.AddRuntimeMessage(RML.Error, "The curve is not closed")
        self.contour = contour
        self.check_curve_orientation()
        
        self._off_contour = None
        
        self.obstacles = obstacles
     
    def check_curve_orientation(self):
        """ check curve orientation for the correct offset and furniture orientation, all curves must be counterclockwise """
        temp_poly_crv = self.contour.ToPolylineCurve()
        temp_crv_orientation = temp_poly_crv.ClosedCurveOrientation(rg.Vector3d.ZAxis)
        if temp_crv_orientation != rg.CurveOrientation.Clockwise:
            self.contour.Reverse()
            
    def add_obstacle(self, obstacle_crv):
        """
            The function add an obstacle to the list of obstacles in the room (door, window openings,
            tables, etc)
            
            :param obstacle_crv: (Rhino.Geometry.Curve) The curve of the obstacle
        """
        self.obstacles.append(obstacle_crv)
    
    def get_offset_contour(self):
        """
            The function calculate and returns the offset of the room contour.
            
            :return: The property of the contour now offset
        """
        if self._off_contour is None:
            temp_crv = self.contour.ToNurbsCurve()
            
            
            temp_crv_off = temp_crv.Offset(rg.Plane.WorldXY,
                                           -TOLERANCE*10,
                                           TOLERANCE,
                                           rg.CurveOffsetCornerStyle.Sharp)[0]
            _, self._off_contour = temp_crv_off.TryGetPolyline()
        return self._off_contour

    def Duplicate(self):
        """
            Copy all the properties inside the class. In C# for Rhino you need to do this for objects other
            than structs (e.g. Point3D, Vectors, etc). All classes need to be copied (e.g. Curve, Line).
            In (iron)python Rhino things are a bit more complicated. In fact you need to copy all objects (even
            structs, e.g. Point, Vector, Box), these types need to be copied with a new constructor. Class objects
            can be duplicated with the traditional .Duplicate() method. The reason why is that EVERYTHING is an OBJECT
            in Python and they are always passed as a REFERENCE.
            
            :return: (Room) a new object with duplicated properties (geometries) of the room.
        """
        room_copy = Room(contour=self.contour.Duplicate(),
                         obstacles=[o.DuplicateCurve() for o in self.obstacles])
        room_copy._off_contour = rg.Polyline(self.get_offset_contour())
        
        return room_copy


if __name__ == "__main__":
    room = Room(contour=i_contour,
                obstacles=i_obstacles)
    a = room


If we want to resume the structure of the class here’s what it would look like

🦊 While your code grows keep track of the structure of your program with such little schemes. This will help you organizing your pipeline once things start to be complex.


*The extra mile

One last thing about documenting your class. Have you notice the """ symbols inside the functions? These are called doc strings and are comments tailored to describe functions and class’elements. This is the proper way of documenting your code. Here’s and example:

def foo(self, param1, param2)
"""
    Here you place the description of what the function does.
    
    :param param1: the parameter asked by the function
    :param param2: another parameter asked by the function

    :return value: the type of value returned from the function
"""


Now that the Room class is done let’s dig into the next class to code.