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:
- perimeter (the countour of the room)
- obstacles (doors’ openings, windows’ openings, inner walls, etc..)
Hence our GHpython component entries will look something like this:
Where,
- the perimeter is fed like a single referenced object named
i_contour
of typePolyline
- the obstacles is fed like a list referenced objects named
i_obstacles
of typeCurve
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
def __init__()
: is called the constructor, it is responsible for construct the object with the given properties.self.<name>
: is used to assign “public” properties to the class.self._<name>
: is used to assign “private” properties to the class. In reality in python everything is accessible from outside the class. This is more of atype hint
.if __name__ == "__main__":
it’s just a convention to clean up our code and indicate where the code is actually running. This is not part of the function (you can see it from the indent).
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 get_offset_contour(self)
: it allows to build an offset of the room needed later for the nester.def Duplicate(self)
: it allows to 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 theRhinoCommons
naming convention for the functionDuplicate()
.
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.