Repository: NREL/bifacial_radiance (language: python)

Issue:
Migrate makeModule to new ModuleObj
At the moment bifacial_radiance is composed of the following classes: RadianceObj, GroundObj, MetObj, AnalysisObj and SceneObj.  I'd like to investigate creating a new ModuleObj that encompasses the ever-growing and complicated makeModule function.  The concept is that what is currently a single function with > 20 inputs would be replaced by a ModuleObj with internally-tracked object parameters such as x,y,z extent, axis of rotation, radfile composition and valid scan points.  First a simple module object is created (or loaded from modules.json).  Then it can be made more complicated by tacking extras on, like  .addFrame,   .addTorqueTube,   .addOmegas,  .addCells  etc.     This will have a lot of benefits, including streamlining the codebase, allowing modifications to underlying geometry, using the same geometry multiple times in multiple different positions (like if you have different frame sizes).  It should be easier to introduce new custom geometries and track how that geometry changes, and test how different geometries interact with each other.
ascii encoding error in getEPW_all; allow all azimuth values
southern latitude test cases confirmed (South Africa, 0 azimuth)
cumulative results falting  when sensorsy is not 9 in analysis1axis
When running sensorsy other than 9, cumulative results gets confused.

# Restricting run to one hour for speed, 'MM_DD_HH'
>>>
demo = bifacial_radiance.RadianceObj(simulationname, path = testfolder)  # Create a RadianceObj 'object'
metdata = demo.readWeatherFile(TMYtoread, coerce_year='2020')
demo.setGround() 
sceneDict = {'pitch': 5,'hub_height':1.5, 'nMods':3, 'nRows': 2}  
trackerdict = demo.set1axis(metdata = metdata, cumulativesky = False)
singleindex = '20_01_01_12'      
trackerdict = demo.gendaylit1axis(startdate = singleindex, enddate = singleindex) 
trackerdict = demo.makeScene1axis(moduletype = moduletype, sceneDict = sceneDict) 
trackerdict = demo.makeOct1axis()
result = demo.analysis1axis(sensorsy=3)
  


Discussion / hints:

Fix patch:
diff --git a/bifacial_radiance/__init__.py b/bifacial_radiance/__init__.py
index 60424862..80bf284b 100644
--- a/bifacial_radiance/__init__.py
+++ b/bifacial_radiance/__init__.py
@@ -1,4 +1,4 @@
-from bifacial_radiance.main import AnalysisObj, GroundObj, MetObj, RadianceObj, SceneObj
+from bifacial_radiance.main import AnalysisObj, GroundObj, MetObj, RadianceObj, SceneObj, ModuleObj
 #from bifacial_radiance.readepw import readepw
 from bifacial_radiance import load
 from bifacial_radiance import modelchain
diff --git a/bifacial_radiance/data/module.json b/bifacial_radiance/data/module.json
index 5f332a2b..38408e93 100644
--- a/bifacial_radiance/data/module.json
+++ b/bifacial_radiance/data/module.json
@@ -316,27 +316,43 @@
         "zgap": 0.1
     },
     "test": {
-        "bifi": 0.9,
+        "bifi": 1,
         "cellModule": null,
+        "frameParams": {
+            "frame_material": "Metal_Grey",
+            "frame_thickness": 0.003,
+            "frame_width": 0.05,
+            "frame_z": 0.03,
+            "nSides_frame": 4
+        },
         "modulefile": "objects\\test.rad",
         "modulematerial": "black",
         "numpanels": 1,
-        "offsetfromaxis": 0,
-        "scenex": 1.59,
-        "sceney": 0.95,
-        "scenez": 0.05,
-        "text": "! genbox black test 1.59 0.95 0.02 | xform -t -0.795 -0.475 0 -a 1 -t 0 0.95 0",
+        "offsetfromaxis": 0.18,
+        "omegaParams": {
+            "inverted": false,
+            "mod_overlap": 0.5,
+            "omega_material": "litesoil",
+            "omega_thickness": 0.01,
+            "x_omega1": 0.1,
+            "x_omega3": 0.05,
+            "y_omega": 1.5
+        },
+        "scenex": 2.01,
+        "sceney": 1.0,
+        "scenez": 0.15,
+        "text": "! genbox black test 2 1 0.02 | xform -t -1.0 -0.5 0.18 -a 1 -t 0 1.0 0\r\n! genrev Metal_Grey tube1 t*2.01 0.05 32 | xform -ry 90 -t -1.005 0 0\r\n! genbox Metal_Grey frameside 0.003 1 0.03 | xform -t -1.0 -0.5 0.15 -a 1 -t 0 1.0 0 | xform -rz 0\r\n! genbox Metal_Grey frameleg 0.047 1 0.003 | xform -t -0.997 -0.5 0.15 -a 1 -t 0 1.0 0 | xform -rz 0\r\n! genbox Metal_Grey frameside 0.003 1 0.03 | xform -t 0.997 -0.5 0.15 -a 1 -t 0 1.0 0 | xform -rz 0\r\n! genbox Metal_Grey frameleg 0.047 1 0.003 | xform -t 0.95 -0.5 0.15 -a 1 -t 0 1.0 0 | xform -rz 0\r\n! genbox Metal_Grey frameside 1.994 0.003 0.027 | xform -t -0.997 0.497 0.153 -a 1 -t 0 1.0 0\r\n! genbox Metal_Grey frameleg 1.9 0.05 0.003 | xform -t -0.95 0.45 0.15 -a 1 -t 0 1.0 0\r\n! genbox Metal_Grey frameside 1.994 0.003 0.027 | xform -t -0.997 -0.5 0.153 -a 1 -t 0 1.0 0\r\n! genbox Metal_Grey frameleg 1.9 0.05 0.003 | xform -t -0.95 -0.5 0.15 -a 1 -t 0 1.0 0\r\n! genbox litesoil mod_adj 0.1 1.5 0.01 | xform -t -0.6000000000000001 -0.75 0.13999999999999999\r\n! genbox litesoil verti 0.01 1.5 0.1 | xform -t -0.6000000000000001 -0.75 0.04999999999999999\r\n! genbox litesoil tt_adj 0.05 1.5 0.01 | xform -t -0.6500000000000001 -0.75 0.04999999999999999\r\n! genbox litesoil mod_adj 0.1 1.5 0.01 | xform -t 0.5000000000000001 -0.75 0.13999999999999999\r\n! genbox litesoil verti 0.01 1.5 0.1 | xform -t 0.5900000000000001 -0.75 0.04999999999999999\r\n! genbox litesoil tt_adj 0.05 1.5 0.01 | xform -t 0.6000000000000001 -0.75 0.04999999999999999",
         "torquetube": {
-            "bool": false,
+            "bool": true,
             "diameter": 0.1,
             "material": "Metal_Grey",
             "tubetype": "Round"
         },
-        "x": 1.59,
-        "xgap": 0.0,
-        "y": 0.95,
+        "x": 2,
+        "xgap": 0.01,
+        "y": 1,
         "ygap": 0.0,
         "z": 0.02,
-        "zgap": 0.0
+        "zgap": 0.1
     }
 }
\ No newline at end of file
diff --git a/bifacial_radiance/gui.py b/bifacial_radiance/gui.py
index 7a3cbe24..4411dec7 100644
--- a/bifacial_radiance/gui.py
+++ b/bifacial_radiance/gui.py
@@ -269,7 +269,7 @@ def read_valuesfromGUI():
             torquetubeParamsDict = {}
             moduleParamsDict = {}
             analysisParamsDict = {}
-            cellLevelModuleParamsDict = {}
+            cellModuleDict = {}
             
             if testfolder is not None: simulationParamsDict['testfolder'] = testfolder
             if weatherfile is not None: simulationParamsDict['weatherFile'] = weatherfile 
@@ -324,12 +324,12 @@ def read_valuesfromGUI():
             if modWanted is not None: analysisParamsDict['modWanted'] =  modWanted
             if rowWanted is not None: analysisParamsDict['rowWanted'] =  rowWanted
             
-            if numcellsx is not None: cellLevelModuleParamsDict['numcellsx'] =  numcellsx
-            if numcellsy is not None: cellLevelModuleParamsDict['numcellsy'] =  numcellsy
-            if xcell is not None: cellLevelModuleParamsDict['xcell'] =  xcell 
-            if ycell is not None: cellLevelModuleParamsDict['ycell'] =  ycell
-            if xcellgap is not None: cellLevelModuleParamsDict['xcellgap'] =  xcellgap 
-            if ycellgap is not None: cellLevelModuleParamsDict['ycellgap'] =  ycellgap
+            if numcellsx is not None: cellModuleDict['numcellsx'] =  numcellsx
+            if numcellsy is not None: cellModuleDict['numcellsy'] =  numcellsy
+            if xcell is not None: cellModuleDict['xcell'] =  xcell 
+            if ycell is not None: cellModuleDict['ycell'] =  ycell
+            if xcellgap is not None: cellModuleDict['xcellgap'] =  xcellgap 
+            if ycellgap is not None: cellModuleDict['ycellgap'] =  ycellgap
 
             # Creating None dictionaries for those empty ones
             if timeControlParamsDict == {}:
@@ -347,13 +347,13 @@ def read_valuesfromGUI():
             if analysisParamsDict == {}:
                 analysisParamsDict = None
             
-            if cellLevelModuleParamsDict == {}:
-                cellLevelModuleParamsDict = None
+            if cellModuleDict == {}:
+                cellModuleDict = None
 
             print("Read all values")            
             return simulationParamsDict, sceneParamsDict, timeControlParamsDict, \
                 moduleParamsDict, trackingParamsDict, torquetubeParamsDict, \
-                analysisParamsDict, cellLevelModuleParamsDict, inputvariablefile;
+                analysisParamsDict, cellModuleDict, inputvariablefile;
         
         def save_inputfile(savetitle=None):
     
@@ -361,12 +361,12 @@ def save_inputfile(savetitle=None):
             
             simulationParamsDict, sceneParamsDict, timeControlParamsDict, \
             moduleParamsDict, trackingParamsDict, torquetubeParamsDict, \
-            analysisParamsDict, cellLevelModuleParamsDict, inputvariablefile  = read_valuesfromGUI()
+            analysisParamsDict, cellModuleDict, inputvariablefile  = read_valuesfromGUI()
 
             if savetitle is None:
                 savetitle = inputvariablefile
                 
-            bifacial_radiance.load.savedictionariestoConfigurationIniFile(simulationParamsDict, sceneParamsDict, timeControlParamsDict, moduleParamsDict, trackingParamsDict, torquetubeParamsDict, analysisParamsDict, cellLevelModuleParamsDict, inifilename=savetitle)
+            bifacial_radiance.load.savedictionariestoConfigurationIniFile(simulationParamsDict, sceneParamsDict, timeControlParamsDict, moduleParamsDict, trackingParamsDict, torquetubeParamsDict, analysisParamsDict, cellModuleDict, inifilename=savetitle)
             print("Saved all Values to %s " % savetitle)
             
             
@@ -381,7 +381,7 @@ def runBifacialRadiance():
             
             simulationParamsDict, sceneParamsDict, timeControlParamsDict, \
             moduleParamsDict, trackingParamsDict, torquetubeParamsDict,   \
-            analysisParamsDict, cellLevelModuleParamsDict, inputvariablefile = read_valuesfromGUI()
+            analysisParamsDict, cellModuleDict, inputvariablefile = read_valuesfromGUI()
             
             #get a return out of runModelChain and pass it back out of the GUI.
             self.data, self.analysis = bifacial_radiance.modelchain.runModelChain(simulationParamsDict=simulationParamsDict, 
@@ -391,7 +391,7 @@ def runBifacialRadiance():
                                                        trackingParamsDict=trackingParamsDict, 
                                                        torquetubeParamsDict=torquetubeParamsDict, 
                                                        analysisParamsDict=analysisParamsDict, 
-                                                       cellLevelModuleParamsDict=cellLevelModuleParamsDict
+                                                       cellModuleDict=cellModuleDict
                     )            
         
         def setDefaultValues():
@@ -494,7 +494,7 @@ def read_inputfile():
 
             simulationParamsDict, sceneParamsDict, timeControlParamsDict,\
             moduleParamsDict, trackingParamsDict, torquetubeParamsDict, \
-            analysisParamsDict, cellLevelModuleParamsDict = bifacial_radiance.load.readconfigurationinputfile(inputvariablefile)
+            analysisParamsDict, cellModuleDict = bifacial_radiance.load.readconfigurationinputfile(inputvariablefile)
             
             # TODO: Think if this procedure is correct.
             # readconfigurationfile already validates inputs in the configuration file.
@@ -577,18 +577,18 @@ def read_inputfile():
             try: entry_rowWanted.insert(0,analysisParamsDict['rowWanted'])
             except: pass
         
-            #cellLevelModuleParamsDict
-            try: entry_numcellsx.insert(0,cellLevelModuleParamsDict['numcellsx'])
+            #cellModuleDict
+            try: entry_numcellsx.insert(0,cellModuleDict['numcellsx'])
             except: pass
-            try: entry_numcellsy.insert(0,cellLevelModuleParamsDict['numcellsy'])
+            try: entry_numcellsy.insert(0,cellModuleDict['numcellsy'])
             except: pass
-            try: entry_xcell.insert(0,cellLevelModuleParamsDict['xcell'])
+            try: entry_xcell.insert(0,cellModuleDict['xcell'])
             except: pass
-            try: entry_xcellgap.insert(0,cellLevelModuleParamsDict['xcellgap'])
+            try: entry_xcellgap.insert(0,cellModuleDict['xcellgap'])
             except: pass
-            try: entry_ycell.insert(0,cellLevelModuleParamsDict['ycell'])
+            try: entry_ycell.insert(0,cellModuleDict['ycell'])
             except: pass
-            try: entry_ycellgap.insert(0,cellLevelModuleParamsDict['ycellgap'])
+            try: entry_ycellgap.insert(0,cellModuleDict['ycellgap'])
             except: pass
           
             # EPW boolean
@@ -1274,7 +1274,7 @@ def showModule(noprint=False):
             """
             #simulationParamsDict, sceneParamsDict, timeControlParamsDict, \
             #moduleParamsDict, trackingParamsDict, torquetubeParamsDict, \
-            #analysisParamsDict, cellLevelModuleParamsDict,_ 
+            #analysisParamsDict, cellModuleDict,_ 
             P = read_valuesfromGUI()
             #simDict = P[0]; modDict = P[3]; tubeDict = P[5]
             if P[3] is None:
@@ -1295,17 +1295,14 @@ def showModule(noprint=False):
                       'y':P[3]['y'], 'xgap':P[3]['xgap'], 'ygap':P[3]['ygap'],
                       'zgap':P[3]['zgap'], 'diameter':P[5]['diameter'],
                        'tubetype':P[5]['tubetype'], 'material':P[5]['torqueTubeMaterial'],
-                       'cellLevelModuleParams':cellModuleParams}
+                       'cellModule':cellModuleParams}
 
             # Run makeModule with the above kwarg dictionary
-            demo.makeModule(**kwargs)
+            module = demo.makeModule(**kwargs)
 
-            # need to MakeScene to write the .rad file to the temp folder
-            scene = demo.makeScene(moduletype = moduletype)
-            
             if noprint is False:
                 # show module
-                scene.showModule(moduletype)
+                module.showModule()
             
             return demo
 
@@ -1317,12 +1314,12 @@ def showScene():
             
             # first create the module given the current conditions of the GUI 
             demo = showModule(noprint = True)
-            moduletype = demo.scene.moduletype
+            moduletype = demo.module.name
 
             # read in the GUI values
             #simulationParamsDict, sceneParamsDict, timeControlParamsDict, \
             #moduleParamsDict, trackingParamsDict, torquetubeParamsDict, \
-            #analysisParamsDict, cellLevelModuleParamsDict,_ 
+            #analysisParamsDict, cellModuleDict,_ 
             P = read_valuesfromGUI()
 
             # Case: tracking scenario
@@ -1336,10 +1333,10 @@ def showScene():
                 trackerdict = demo.makeScene1axis(trackerdict, 
                                              sceneDict = P[1], 
                                              cumulativesky=False, 
-                                             moduletype=moduletype)
+                                             module=moduletype)
                 scene = trackerdict[key]['scene']
             else: # fixed tilt scenario
-                scene = demo.makeScene(moduletype=moduletype, sceneDict=P[1])
+                scene = demo.makeScene(module=moduletype, sceneDict=P[1])
             
             scene.showScene()
         
diff --git a/bifacial_radiance/main.py b/bifacial_radiance/main.py
index 8b2c5fce..bea9845f 100644
--- a/bifacial_radiance/main.py
+++ b/bifacial_radiance/main.py
@@ -73,6 +73,9 @@
 def _findme(lst, a): #find string match in a list. script from stackexchange
     return [i for i, x in enumerate(lst) if x == a]
 
+def _missingKeyWarning(dictype, missingkey, newvalue): # prints warnings 
+    print("Warning: {} Dictionary Parameters passed, but {} is missing. ".format(dictype, missingkey))        
+    print("Setting it to default value of {} m to continue\n".format(newvalue))
 
 def _normRGB(r, g, b): #normalize by each color for human vision sensitivity
     return r*0.216+g*0.7152+b*0.0722
@@ -1063,17 +1066,17 @@ def _subhourlydatatoGencumskyformat(gencumskydata):
             csvfile = os.path.join('EPWs', filename)
             print('Saving file {}, # points: {}'.format(csvfile, gencumskydata.__len__()))
             gencumskydata.to_csv(csvfile, index=False, header=False, sep=' ', columns=['GHI','DHI'])
-            self.temp_metdatafile = csvfile
+            self.gencumsky_metfile = csvfile
         
         if gencumdict is not None:
-            self.temp_metdatafile = []
+            self.gencumsky_metfile = []
             for ii in range (0, len(gencumdict)):
                 gencumskydata = gencumdict[ii]
                 newfilename = filename.split('.')[0]+'_year_'+str(ii)+'.csv'
                 csvfile = os.path.join('EPWs', newfilename)
                 print('Saving file {}, # points: {}'.format(csvfile, gencumskydata.__len__()))
                 gencumskydata.to_csv(csvfile, index=False, header=False, sep=' ', columns=['GHI','DHI'])
-                self.temp_metdatafile.append(csvfile)
+                self.gencumsky_metfile.append(csvfile)
 
         return tmydata
 
@@ -1485,7 +1488,7 @@ def gendaylit2manual(self, dni, dhi, sunalt, sunaz):
 
         return skyname
 
-    def genCumSky(self, temp_metdatafile=None, savefile=None):
+    def genCumSky(self, gencumsky_metfile=None, savefile=None):
         """ 
         Generate Skydome using gencumsky. 
         
@@ -1502,7 +1505,7 @@ def genCumSky(self, temp_metdatafile=None, savefile=None):
 
         Parameters
         ------------
-        temp_metdatafile : str
+        gencumsky_metfile : str
             Filename with path to temporary created meteorological file usually created
             in EPWs folder. This csv file has no headers, no index, and two
             space separated columns with values for GHI and DNI for each hour 
@@ -1517,22 +1520,22 @@ def genCumSky(self, temp_metdatafile=None, savefile=None):
         """
         
         # TODO:  error checking and auto-install of gencumulativesky.exe
-        # TODO: add check if readWheatfile has not be done
-        # TODO: check if it fails if gcc module has been loaded? (common hpc fissue)
+        # TODO: add check if readWeatherfile has not be done
+        # TODO: check if it fails if gcc module has been loaded? (common hpc issue)
         
-        import datetime
+        #import datetime
         
-        if temp_metdatafile is None:
-            temp_metdatafile = self.temp_metdatafile
-            if isinstance(temp_metdatafile, str):
-                print("Loaded ", temp_metdatafile)
+        if gencumsky_metfile is None:
+            gencumsky_metfile = self.gencumsky_metfile
+            if isinstance(gencumsky_metfile, str):
+                print("Loaded ", gencumsky_metfile)
                 
-        if isinstance(temp_metdatafile, list):
+        if isinstance(gencumsky_metfile, list):
             print("There are more than 1 year of gencumsky temporal weather file saved."+
-                  "You can pass which file you want with temp_metdatafile input. Since "+
+                  "You can pass which file you want with gencumsky_metfile input. Since "+
                   "No year was selected, defaulting to using the first year of the list")
-            temp_metdatafile = temp_metdatafile[0] 
-            print("Loaded ", temp_metdatafile)
+            gencumsky_metfile = gencumsky_metfile[0] 
+            print("Loaded ", gencumsky_metfile)
 
 
         if savefile is None:
@@ -1546,10 +1549,10 @@ def genCumSky(self, temp_metdatafile=None, savefile=None):
             "-time %s %s -date %s %s %s %s %s" % (startdt.hour, enddt.hour+1,
                                                   startdt.month, startdt.day,
                                                   enddt.month, enddt.day,
-                                                  temp_metdatafile)
+                                                  gencumsky_metfile)
         '''
         cmd = (f"gencumulativesky +s1 -h 0 -a {lat} -o {lon} -m "
-               f"{float(timeZone)*15} -G {temp_metdatafile}" )
+               f"{float(timeZone)*15} -G {gencumsky_metfile}" )
                
         with open(savefile+".cal","w") as f:
             _,err = _popen(cmd, None, f)
@@ -1689,7 +1692,8 @@ def gendaylit1axis(self, metdata=None, trackerdict=None, startdate=None,
         Parameters
         ------------
         metdata
-            Output from readEPW or readTMY.  Needs to have RadianceObj.set1axis() run on it first.
+            MetObj output from readWeatherFile.  Needs to have 
+            RadianceObj.set1axis() run on it first.
         startdate : str 
             DEPRECATED, does not do anything now.
             Recommended to downselect metdata when reading Weather File.
@@ -1719,6 +1723,7 @@ def gendaylit1axis(self, metdata=None, trackerdict=None, startdate=None,
             print("Deprecation Warning: gendyalit1axis no longer downselects"+
                   "Entries by stardate and enddate. Downselect your data"+
                   "when loading with readWeatherFile")
+            return
             
         try:
             metdata.tracker_theta  # this may not exist
@@ -1783,7 +1788,7 @@ def genCumSky1axis(self, trackerdict=None):
             # call gencumulativesky with a new .cal and .rad name
             csvfile = trackerdict[theta]['csvfile']
             savefile = '1axis_%s'%(theta)  #prefix for .cal file and skies\*.rad file
-            skyfile = self.genCumSky(temp_metdatafile=csvfile, savefile=savefile)
+            skyfile = self.genCumSky(gencumsky_metfile=csvfile, savefile=savefile)
             trackerdict[theta]['skyfile'] = skyfile
             print('Created skyfile %s'%(skyfile))
         # delete default skyfile (not strictly necessary)
@@ -1910,152 +1915,27 @@ def makeOct1axis(self, trackerdict=None, singleindex=None, customname=None, hpc=
         return trackerdict
 
     
-    def makeModule(self, name=None, x=None, y=None, z=None, bifi=1, modulefile=None, text=None, customtext='',
-                   torquetube=False, diameter=0.1, tubetype='Round', material='Metal_Grey',
-                   xgap=0.01, ygap=0.0, zgap=0.1, numpanels=1, rewriteModulefile=True,
-                   axisofrotationTorqueTube=False, cellLevelModuleParams=None,  
-                   orientation=None, glass=False, torqueTubeMaterial=None, 
-                   modulematerial = None, omegaParams = None, frameParams = None):
+    def makeModule(self, name=None, x=None, y=None, z=None, bifi=1, modulefile=None, 
+                 text=None, customtext='', torquetube=False,  xgap=0.01, ygap=0.0, 
+                 zgap=0.1, numpanels=1, rewriteModulefile=True, 
+                 axisofrotationTorqueTube=False, cellModule=None,  
+                 glass=False, modulematerial=None, tubeParams=None, 
+                 omegaParams=None, frameParams=None):
+        """
+        pass module generation details into ModuleObj(). See ModuleObj() 
+        docstring for more details
         """
-        Add module details to the .JSON module config file module.json
-        makeModule is in the `RadianceObj` class because this is defined before a `SceneObj` is.
-
-        Module definitions assume that the module .rad file is defined
-        with zero tilt, centered along the x-axis and y-axis for the center
-        of rotation of the module (+X/2, -X/2, +Y/2, -Y/2 on each side).
-        Tip: to define a module that is in 'portrait' mode, y > x. 
-
-        Parameters
-        ------------
-        name : str
-            Input to name the module type
-        x : numeric
-            Width of module along the axis of the torque tube or racking structure. (meters).
-        y : numeric
-            Length of module (meters)
-        bifi : numeric
-            Bifaciality of the panel (not currently used). Between 0 (monofacial) 
-            and 1, default 1.
-        modulefile : str
-            Existing radfile location in \objects.  Otherwise a default value is used
-        text : str
-            Text used in the radfile to generate the module
-        customtext : str
-            Added-text used in the radfile to generate any
-            extra details in the racking/module. Does not overwrite
-            generated module (unlike "text"), but adds to it at the end.
-        rewriteModulefile : bool
-            Default True. Will rewrite module file each time makeModule is run.
-        torquetube : bool
-            This variable defines if there is a torque tube or not.
-        diameter : float
-            Tube diameter in meters. For square,
-            For Square, diameter means the length of one of the
-            square-tube side.  For Hex, diameter is the distance
-            between two vertices (diameter of the circumscribing circle)
-        tubetype : str
-            Options: 'Square', 'Round' (default), 'Hex' or 'Oct'. Tube cross section
-        material : str
-            Options: 'Metal_Grey' or 'black'. Material for the torque tube.
-        numpanels : int
-            Number of modules arrayed in the Y-direction. e.g.
-            1-up or 2-up, etc. (supports any number for carport/Mesa simulations)
-        xgap : float
-            Panel space in X direction. Separation between modules in a row.
-        ygap : float
-            Gap between modules arrayed in the Y-direction if any.
-        zgap : float
-            Distance behind the modules in the z-direction to the edge of the tube (m)
-        cellLevelModuleParams : dict
-            Dictionary with input parameters for creating a cell-level module.
-            See details below for keys needed.
-        axisofrotationTorqueTube : bool
-            Default False. IF true, creates geometry
-            so center of rotation is at the center of the torquetube, with
-            an offsetfromaxis equal to half the torquetube diameter + the zgap.
-            If there is no torquetube (torquetube=False), offsetformaxis
-            will equal the zgap.
-        frameParams : dict
-            Dictionary with input parameters for creating a frame as part of the module.
-            See details below for keys needed.
-        omegaParams : dict
-            Dictionary with input parameters for creating a omega or module support structure.
-            See details below for keys needed.
-        Notes
-        -----
-
-        For creating a cell-level module, the following input parameters should 
-        to be in ``cellLevelModuleParams``:
-        
-        ================   ====================================================
-        Keys : type        Description
-        ================   ====================================================  
-        numcellsx : int    Number of cells in the X-direction within the module
-        numcellsy : int    Number of cells in the Y-direction within the module
-        xcell : float      Width of each cell (X-direction) in the module
-        ycell : float      Length of each cell (Y-direction) in the module
-        xcellgap : float   Spacing between cells in the X-direction
-        ycellgap : float   Spacing between cells in the Y-direction
-        centerJB : float   (optional) Distance betwen both sides of cell arrays 
-                           in a center-JB half-cell module. If 0 or not provided,
-                           module will not have the center JB spacing. 
-                           Only implemented for 'portrait' mode at the moment.
-                           (numcellsy > numcellsx). 
-        ================   ====================================================  
-        
-        For creating a module that includes the frames attached to the module, 
-        the following input parameters should to be in ``frameParams``:
-        
-        =======================  ===============================================
-        Keys : type              Description
-        =======================  =============================================== 
-        frame_material : str     The material the frame structure is made of
-        frame_thickness : float  The profile thickness of the frame 
-        frame_z : float          The Z-direction length of the frame that extends 
-                                 below the module plane
-        frame_width : float      The length of the bottom frame that is bolted 
-                                 with the omega
-        nSides_frame : int       The number of sides of the module that are framed.
-                                 4 (default) or 2
-        =======================  ===============================================
-        
-        
-        For creating a module that includes the racking structure element `omega`, 
-        the following input parameters should be in ``omegaParams``, otherwise 
-        default values will be used:
-        
-        ========================  ===============================================
-        Keys : type               Description
-        ========================  =============================================== 
-        omega_material : str      The material the omega structure is made of
-        x_omega1  : float         The length of the module-adjacent arm of the 
-                                  omega parallel to the x-axis of the module
-        mod_overlap : float       The length of the overlap between omega and 
-                                  module surface on the x-direction
-        y_omega  : float          Length of omega (Y-direction)
-        omega_thickness : float   Omega thickness
-        x_omega3  : float         X-direction length of the torquetube adjacent 
-                                  arm of omega
-        inverted : Bool           Modifies the way the Omega is set on the Torquetbue
-                                  Looks like False: u  vs True: n  (default False)
-        ========================  ===============================================
-        
-        '"""
-
-        # #TODO: refactor this module to streamline it and accept moduleDict input
-
 
-        import json
-        
-        
         if name is None:
             print("usage:  makeModule(name,x,y, bifi = 1, modulefile = '\objects\*.rad', "+
-                  "torquetube=False, diameter = 0.1 (torque tube dia.), "+
-                  "tubetype = 'Round' (or 'square', 'hex'), material = "+
-                  "'Metal_Grey' (or 'black'), zgap = 0.1 (module offset)"+
+                  "torquetube=False,   zgap = 0.1 (module offset)"+
                   "numpanels = 1 (# of panels in portrait), ygap = 0.05 "+
                   "(slope distance between panels when arrayed), "+
                   "rewriteModulefile = True (or False)")
+            print("Optional: tubeParams={} (torque tube details including "
+                  "diameter (torque tube dia. in meters), tubetype='Round' "
+                  "(or 'square', 'hex'), material='Metal_Grey' (or 'black')"
+                  ", axisofrotation=True (does scene rotate around tube)")
             print("Optional: cellLevelModule={} (create cell-level module by "+
                   " passing in dictionary with keys 'numcellsx'6 (#cells in "+
                   "X-dir.), 'numcellsy', 'xcell' (cell size in X-dir. in meters),"+
@@ -2069,891 +1949,273 @@ def makeModule(self, name=None, x=None, y=None, z=None, bifi=1, modulefile=None,
             print("You can also override module_type info by passing 'text'"+
                   "variable, or add on at the end for racking details with "+
                   "'customtext'. See function definition for more details")
-
             return
-
-        
-        #replace whitespace with underlines. what about \n and other weird characters?
-        name2 = str(name).strip().replace(' ', '_')        
-
-        if modulefile is None:
-            modulefile = os.path.join('objects', name2 + '.rad')
-            print("\nModule Name:", name2)
-
-        if rewriteModulefile is True:
-            if os.path.isfile(modulefile):
-                print(f'Pre-existing .rad file {modulefile} '
-                      'will be overwritten')
-                os.remove(modulefile)
-                
-        if orientation is not None:
-            print('\n\n WARNING: Orientation format has been deprecated since '+
-                  'version 0.2.4. If you want to flip your modules, on '+
-                  'makeModule switch the x and y values. X value is the size '+
-                  'of the panel along the row, so for a "landscape" panel x '+
-                  'should be > than y.\n\n')
             
-        if torqueTubeMaterial is not None:
-            material = torqueTubeMaterial
-        #aliases for equations below
-        diam = diameter
-        Ny = numpanels
-        cc=0  # cc is an offest given to the module when cells are used
-              # so that the sensors don't fall in air when numcells is even.
-              # For non cell-level modules default is 0.
-              
-        import math
-
-        # Defaults for rotating system around module
-        offsetfromaxis = 0      # Module Offset
+        self.module = ModuleObj(name=name, x=x, y=y, z=z, bifi=bifi, modulefile=modulefile,
+                   text=text, customtext=customtext, torquetube=torquetube, 
+                   xgap=xgap, ygap=ygap, zgap=zgap, numpanels=numpanels, 
+                   rewriteModulefile=rewriteModulefile, cellModule=cellModule,  
+                   glass=glass, modulematerial=modulematerial, tubeParams=tubeParams,
+                   omegaParams=omegaParams, frameParams=frameParams)
+        return self.module
+    
+    
+    
+    def makeCustomObject(self, name=None, text=None):
+        """
+        Function for development and experimenting with extraneous objects in the scene.
+        This function creates a `name.rad` textfile in the objects folder
+        with whatever text that is passed to it.
+        It is up to the user to pass the correct radiance format.
         
-        # Defaults for rotating system around module
-        tto = zgap + diam/2      # Torquetube Offset
-
-        # Update values for rotating system around torque tube.
-        if axisofrotationTorqueTube == True:
-            tto = 0
-            if torquetube == True:
-                offsetfromaxis = np.round(zgap + diam/2.0,8)
-            else:
-                offsetfromaxis = zgap
-                
-            if frameParams is not None:
-                if 'frame_z' not in frameParams:
-                    frameParams['frame_z'] = 0.03
-                    self._missingKeyWarning('Frame', 'frame_z', frameParams['frame_z'])
-                offsetfromaxis = offsetfromaxis + frameParams['frame_z']
-
-        #TODO: replace these with functions     
-        # Adding the option to replace the module thickess
-        if z is None:
-            z = 0.020
-            
-        if modulematerial is None:
-            modulematerial = 'black'
-            
-        if frameParams is not None:
-            z_inc, frametext, frameParams = self._makeFrames(frameParams = frameParams, 
-                                                             x=x,y=y, ygap=ygap, 
-                                                             numpanels=Ny, 
-                                                             offsetfromaxis=offsetfromaxis)
-        else:
-            frametext = ''
-            z_inc = 0
-            
+        For example, to create a box at coordinates 0,0 (with its bottom surface
+        on the plane z=0):
             
-        if omegaParams is not None:
-            # This also defines scenex for length of the torquetube.
-            scenex, omegatext, omegaParams = self._makeOmega(omegaParams=omegaParams, 
-                                                             x=x,y=y, xgap=xgap, 
-                                                             zgap=zgap, z_inc=z_inc, 
-                                                             offsetfromaxis=offsetfromaxis)
-        else:
-            omegatext = ''
+        .. code-block:
         
+            name = 'box'
+            text='! genbox black PVmodule 0.5 0.5 0.5 | xform -t -0.25 -0.25 0'
 
+        Parameters
+        ----------
+        name : str
+            String input to name the module type
+        text : str
+            Text used in the radfile to generate the module
+        
+        """
 
-        if text is None:
-            
-            if not cellLevelModuleParams:
-                try:
-                    text = '! genbox {} {} {} {} {} '.format(modulematerial, 
-                                                              name2,x, y, z)
-                    text +='| xform -t {} {} {} '.format(-x/2.0,
-                                            (-y*Ny/2.0)-(ygap*(Ny-1)/2.0),
-                                            offsetfromaxis)
-                    text += '-a {} -t 0 {} 0'.format(Ny, y+ygap)
-                    packagingfactor = 100.0
-
-                except NameError as err: # probably because no x or y passed
-                    raise Exception('makeModule variable {}'.format(err.args[0])+
-                                    ' and cellLevelModuleParams is None.  '+
-                                    'One or the other must be specified.')
-            else:
-                c = cellLevelModuleParams
-                x = c['numcellsx']*c['xcell'] + (c['numcellsx']-1)*c['xcellgap']
-                y = c['numcellsy']*c['ycell'] + (c['numcellsy']-1)*c['ycellgap']
-
-                #center cell -
-                if c['numcellsx'] % 2 == 0:
-                    cc = c['xcell']/2.0
-                    print("Module was shifted by {} in X to avoid sensors on air".format(cc))
-
+        customradfile = os.path.join('objects', '%s.rad'%(name)) # update in 0.2.3 to shorten radnames
+        # py2 and 3 compatible: binary write, encode text first
+        with open(customradfile, 'wb') as f:
+            f.write(text.encode('ascii'))
 
-                # For half cell modules with the JB on the center:
-                centerJB = 0
-                if 'centerJB' in c:
-                      centerJB = c['centerJB']
-                    
+        print("\nCustom Object Name", customradfile)
+        self.customradfile = customradfile
+        return customradfile
 
-                text = '! genbox {} cellPVmodule {} {} {} | '.format(modulematerial,
-                                                       c['xcell'], c['ycell'], z)
-                text +='xform -t {} {} {} '.format(-x/2.0 + cc,
-                                 (-y*Ny / 2.0)-(ygap*(Ny-1) / 2.0)-centerJB/2.0,
-                                 offsetfromaxis)
-                
-                
-                text += '-a {} -t {} 0 0 '.format(c['numcellsx'], c['xcell'] + c['xcellgap'])
-                
-                if centerJB != 0:
-                    text += '-a {} -t 0 {} 0 '.format(c['numcellsy']/2, c['ycell'] + c['ycellgap'])
-                    text += '-a {} -t 0 {} 0 '.format(2, y/2.0+centerJB)  
-                else:
-                    text += '-a {} -t 0 {} 0 '.format(c['numcellsy'], c['ycell'] + c['ycellgap'])
-                    
-                text += '-a {} -t 0 {} 0'.format(Ny, y+ygap)
 
-                # OPACITY CALCULATION
-                packagingfactor = np.round((c['xcell']*c['ycell']*c['numcellsx']*c['numcellsy'])/(x*y), 2)
-                print("This is a Cell-Level detailed module with Packaging "+
-                      "Factor of {} %".format(packagingfactor))
+    def printModules(self):
+        # print available module types from ModuleObj
+        modulenames = ModuleObj().readModule()
+        print('Available module names: {}'.format([str(x) for x in modulenames]))
+        return modulenames
+    
+    def makeScene(self, module=None, sceneDict=None, hpc=False, radname=None,
+                  moduletype=None):
+        """
+        Create a SceneObj which contains details of the PV system configuration including
+        tilt, row pitch, height, nMods per row, nRows in the system...
 
-                  
-                       
-            # Defining scenex if it was not defined by the Omegas, 
-            # after the module has been created in case it is a 
-            # cell-level Module, in which the "x" gets calculated internally.
-            # Also sanity check in case omega-to-omega distance is smaller
-            # than module.
-            if 'scenex' not in locals():
-                scenex = x + xgap
-            else:         
-                if scenex<x:
-                    scenex = x+xgap #overwriting scenex to maintain torquetube continuity
+        Parameters
+        ----------
+        module : str or ModuleObj
+            String name of module created with makeModule()
+        sceneDict : dictionary
+            Dictionary with keys: `tilt`, `clearance_height`*, `pitch`,
+            `azimuth`, `nMods`, `nRows`, `hub_height`*, `height`*
+            * height deprecated from sceneDict. For makeScene (fixed systems)
+            if passed it is assumed it reffers to clearance_height.
+            `clearance_height` recommended for fixed_tracking systems.
+            `hub_height` can also be passed as a possibility.
+        hpc : bool
+            Default False. For makeScene, it adds the full path
+            of the objects folder where the module . rad file is saved.
+        radname : str
+            Gives a custom name to the scene file. Useful when parallelizing.
+        moduletype: DEPRECATED. use the `module` kwarg instead.
+        
+        Returns
+        -------
+        SceneObj 
+            'scene' with configuration details
             
-                    print ('Warning: Omega values have been provided, but' +
-                           'the distance between modules with the omega'+
-                           'does not match the x-gap provided.'+
-                           'Setting x-gap to be the space between modules'+
-                           'from the omega.')
-                else:
-                    print ('Warning: Using omega-to-omega distance to define'+
-                           'gap between modules'
-                           +'xgap value not being used')
-
-            # TODO: change torquetube parameters to a dictionary
-            # TODO: Change torquetube routine to its own function for cleanliness.
-            if torquetube is True:
-                if tubetype.lower() == 'square':
-                    if axisofrotationTorqueTube == False:
-                        tto = -z_inc-zgap-diam/2.0
-                    text += '\r\n! genbox {} tube1 {} {} {} '.format(material,
-                                          scenex, diam, diam)
-                    text += '| xform -t {} {} {}'.format(-(scenex)/2.0+cc,
-                                        -diam/2.0, -diam/2.0+tto)
-
-                elif tubetype.lower() == 'round':
-                    if axisofrotationTorqueTube == False:
-                        tto = -z_inc-zgap-diam/2.0
-                    text += '\r\n! genrev {} tube1 t*{} {} '.format(material, scenex, diam/2.0)
-                    text += '32 | xform -ry 90 -t {} {} {}'.format(-(scenex)/2.0+cc, 0, tto)
-
-                elif tubetype.lower() == 'hex':
-                    radius = 0.5*diam
-
-                    if axisofrotationTorqueTube == False:
-                        tto = -z_inc-radius*math.sqrt(3.0)/2.0-zgap
-
-                    text += '\r\n! genbox {} hextube1a {} {} {} | xform -t {} {} {}'.format(
-                            material, scenex, radius, radius*math.sqrt(3),
-                            -(scenex)/2.0+cc, -radius/2.0, -radius*math.sqrt(3.0)/2.0+tto) #ztran -radius*math.sqrt(3.0)-tto
-
-
-                    # Create, translate to center, rotate, translate back to prev. position and translate to overal module position.
-                    text = text+'\r\n! genbox {} hextube1b {} {} {} | xform -t {} {} {} -rx 60 -t 0 0 {}'.format(
-                            material, scenex, radius, radius*math.sqrt(3), -(scenex)/2.0+cc, -radius/2.0, -radius*math.sqrt(3.0)/2.0, tto) #ztran (radius*math.sqrt(3.0)/2.0)-radius*math.sqrt(3.0)-tto)
-                    
-                    text = text+'\r\n! genbox {} hextube1c {} {} {} | xform -t {} {} {} -rx -60 -t 0 0 {}'.format(
-                            material, scenex, radius, radius*math.sqrt(3), -(scenex)/2.0+cc, -radius/2.0, -radius*math.sqrt(3.0)/2.0, tto) #ztran (radius*math.sqrt(3.0)/2.0)-radius*math.sqrt(3.0)-tto)
-
-                elif tubetype.lower()=='oct':
-                    radius = 0.5*diam
-                    s = diam / (1+math.sqrt(2.0))   # 
-
-                    if axisofrotationTorqueTube == False:
-                        tto = -z_inc-radius-zgap
-
-                    text = text+'\r\n! genbox {} octtube1a {} {} {} | xform -t {} {} {}'.format(
-                            material, scenex, s, diam, -(scenex)/2.0, -s/2.0, -radius+tto)
-
-                    # Create, translate to center, rotate, translate back to prev. position and translate to overal module position.
-                    text = text+'\r\n! genbox {} octtube1b {} {} {} | xform -t {} {} {} -rx 45 -t 0 0 {}'.format(
-                            material, scenex, s, diam, -(scenex)/2.0+cc, -s/2.0, -radius, tto)
+        """
+        if moduletype is not None:
+            module = moduletype
+            print("Warning:  input `moduletype` is deprecated. Use kwarg "
+                  "`module` instead")
+        if module is None:
+            try:
+                module = self.module
+                print(f'Using last saved module, name: {module.name}')
+            except AttributeError:
+                print('makeScene(module, sceneDict, nMods, nRows).  '+\
+                          'Available moduletypes: ' )
+                self.printModules() #print available module types
+                return
+        self.scene = SceneObj(module)
 
-                    text = text+'\r\n! genbox {} octtube1c {} {} {} | xform -t {} {} {} -rx 90 -t 0 0 {}'.format(
-                            material, scenex, s, diam, -(scenex)/2.0+cc, -s/2.0, -radius, tto)
+        if sceneDict is None:
+            print('makeScene(moduletype, sceneDict, nMods, nRows).  '+\
+                  'sceneDict inputs: .tilt .clearance_height .pitch .azimuth')
+            return self.scene
 
-                    text = text+'\r\n! genbox {} octtube1d {} {} {} | xform -t {} {} {} -rx 135 -t 0 0 {} '.format(
-                            material, scenex, s, diam, -(scenex)/2.0+cc, -s/2.0, -radius, tto)
+        if 'azimuth' not in sceneDict:
+            sceneDict['azimuth'] = 180
 
+        if 'nRows' not in sceneDict:
+            sceneDict['nRows'] = 7
 
-                else:
-                    raise Exception("Incorrect torque tube type.  "+
-                                    "Available options: 'square' or 'round'."+
-                                    "  Value entered: {}".format(tubetype))
-
-            if glass: 
-                    edge = 0.005                     
-                    text = text+'\r\n! genbox stock_glass {} {} {} {} '.format(name2+'_Glass',x+edge, y+edge, z+edge)
-                    text +='| xform -t 0 {} 0 ' . format(-edge/2.0)
-                    text +='| xform -t {} {} {} '.format(-x/2.0-edge/2.0 + cc,
-                                            (-y*Ny/2.0)-(ygap*(Ny-1)/2.0),
-                                            offsetfromaxis - 0.5*edge + 0.5*z)
-                    text += '-a {} -t 0 {} 0'.format(Ny, y+ygap)
-                
+        if 'nMods' not in sceneDict:
+            sceneDict['nMods'] = 20
 
-        text += frametext
-        text += omegatext    
-        text += customtext  # For adding any other racking details at the module level that the user might want.
-
-
-        moduleDict = {'x':x,
-                      'y':y,
-                      'z':z,
-                      'modulematerial': modulematerial,
-                      'scenex': scenex,
-                      'sceney': np.round(y*Ny + ygap*(Ny-1), 8),
-                      'scenez': np.round(zgap + diam / 2.0, 8),
-                      'numpanels':Ny,
-                      'bifi':bifi,
-                      'text':text,
-                      'modulefile':modulefile,
-                      'offsetfromaxis':offsetfromaxis, #<- this may not be consistent if the module is re-loaded from the JSON later since 'axisofrotationTorqueTube' isn't kept track of..
-                      'xgap':xgap,
-                      'ygap':ygap,
-                      'zgap':zgap,
-                      'cellModule':cellLevelModuleParams,
-                      'torquetube':{'bool':torquetube,
-                                    'diameter':diameter,
-                                    'tubetype':tubetype,
-                                    'material':material
-                              }
-                      }
-                      
-        if omegaParams is not None:
-            moduleDict['omegaParams'] = omegaParams
+        # Fixed tilt routine
+        # Preferred: clearance_height,
+        # If only height is passed, it is assumed to be clearance_height.
         
-        if frameParams is not None:
-            moduleDict['frameParams'] = frameParams
+        sceneDict, use_clearanceheight  = _heightCasesSwitcher(sceneDict, 
+                                                                preferred='clearance_height', 
+                                                                nonpreferred='hub_height')
+        
+        self.nMods = sceneDict['nMods']
+        self.nRows = sceneDict['nRows']
+        self.sceneRAD = self.scene._makeSceneNxR(sceneDict=sceneDict,
+                                                hpc=hpc, radname=radname)
 
-        filedir = os.path.join(DATA_PATH, 'module.json') 
-        with open(filedir) as configfile:
-            data = json.load(configfile)
-
-
-        data.update({name:moduleDict})
-        with open(os.path.join(DATA_PATH, 'module.json') ,'w') as configfile:
-            json.dump(data, configfile, indent=4, sort_keys=True)
-
-        print('Module {} updated in module.json'.format(name))
-
-        self.moduleDict = moduleDict
+        if 'appendRadfile' not in sceneDict:
+            appendRadfile = False
+        else:
+            appendRadfile = sceneDict['appendRadfile']
 
-        return moduleDict
-    
-    def _missingKeyWarning(self, dictype, missingkey, newvalue): # prints warnings 
-        print("Warning: {} Dictionary Parameters passed, but {} is missing".format(dictype, missingkey))        
-        print("Setting it to default value of {} m to continue\n".format(newvalue))
+        if appendRadfile:
+            debug = False
+            try:
+                self.radfiles.append(self.sceneRAD)
+                if debug:
+                    print( "Radfile APPENDED!")
+            except:
+                #TODO: Manage situation where radfile was created with
+                #appendRadfile to False first..
+                self.radfiles=[]
+                self.radfiles.append(self.sceneRAD)
+                if debug:
+                    print( "Radfile APPENDAGE created!")
+        else:
+            self.radfiles = [self.sceneRAD]
+        return self.scene
 
-                                
-        
-    def _makeFrames(self, frameParams, x,y, ygap, numpanels, offsetfromaxis):
+    def appendtoScene(self, radfile=None, customObject=None, text=''):
         """
-        Helper function for creating a module that includes the frames attached to the module, 
+        Appends to the `Scene radfile` in folder `\objects` the text command in Radiance
+        lingo created by the user.
+        Useful when using addCustomObject to the scene.
 
-            
         Parameters
-        ------------
-        frameParams : dict
-            Dictionary with input parameters for creating a frame as part of the module.
-            See details below for keys needed.
-        x : numeric
-            Width of module along the axis of the torque tube or racking structure. (meters).
-        y : numeric
-            Length of module (meters)
-        ygap : float
-            Gap between modules arrayed in the Y-direction if any.
-        numpanels : int
-            Number of modules arrayed in the Y-direction. e.g.
-            1-up or 2-up, etc. (supports any number for carport/Mesa simulations)
-        offsetfromaxis : float
-            Internally defined variable in makeModule that specifies how much
-            the module is offset from the Axis of Rotation due to zgap and or 
-            frame thickness.
+        ----------
+        radfile: str
+            Directory and name of where .rad scene file is stored
+        customObject : str
+            Directory and name of custom object .rad file is stored
+        text : str 
+            Command to be appended to the radfile. Do not leave empty spaces
+            at the end.
 
-            
-        The following input parameters should to be in ``frameParams``, otherwise
-        default values will be used:
+        Returns
+        -------
+        Nothing, the radfile must already be created and assigned when running this.
         
-        ====================    ===============================================
-        Keys : type             Description
-        ================        =============================================== 
-        frame_material : str    The material the frame structure is made of
-        frame_thickness : float The profile thickness of the frame 
-        frame_z : float         The Z-direction length of the frame that extends 
-                                below the module plane
-        frame_width : float     The length of the bottom frame that is bolted 
-                                with the omega
-        nSides_frame : int      The number of sides of the module that are framed.
-                                4 (default) or 2
-        =====================   ===============================================
-
         """
         
-        if 'frame_material' not in frameParams:
-            frameParams['frame_material'] = 'Metal_Grey'
-            self._missingKeyWarning('Frame', 'frame_material', frameParams['frame_material'])
-            
-            
-        if 'frame_thickness' not in frameParams:
-            frameParams['frame_thickness'] = 0.05
-            self._missingKeyWarning('Frame', 'frame_thickness', frameParams['frame_thickness'])
-            
-    
-        if 'frame_z' not in frameParams:
-            frameParams['frame_z'] = 0.3
-            self._missingKeyWarning('Frame', 'frame_thickness', frameParams['frame_thickness'])
-
+        #TODO: Add a custom name and replace radfile name
 
-        if 'nSides_frame' not in frameParams:
-            frameParams['nSides_frame'] = 4
-            self._missingKeyWarning('Frame', 'nSides_frame', frameParams['nSides_frame'])
-        
-        if 'frame_width' not in frameParams:
-            frameParams['frame_width'] = 0.05
-            self._missingKeyWarning('Frame', 'frame_width', frameParams['frame_width'])
-        
-        #Defining internal names
-        frame_material = frameParams['frame_material'] 
-        f_thickness = frameParams['frame_thickness'] 
-        f_height = frameParams['frame_z'] 
-        n_frame = frameParams['nSides_frame']  
-        fl_x = frameParams['frame_width']
-        
-        y_trans_shift = 0 #pertinent to the case of x>y with 2-sided frame
-                
-    
-        
-        # Recalculating width ignoring the thickness of the aluminum
-        # for internal positioining and sizing of hte pieces
-        fl_x = fl_x-f_thickness
-        
-        if x>y and n_frame==2:
-            x_temp,y_temp = y,x
-            rotframe = 90
-            frame_y = x
-            y_trans_shift = x/2-y/2
-        else:
-            x_temp,y_temp = x,y
-            frame_y = y
-            rotframe = 0
-    
-        Ny = numpanels
-        y_half = (y*Ny/2)+(ygap*(Ny-1)/2)
-    
-        # taking care of lengths and translation points
-        # The pieces are same and symmetrical for west and east
-    
-        # naming the frame pieces
-        nameframe1 = 'frameside'
-        nameframe2 = 'frameleg'
-        
-        #frame sides
-        few_x = f_thickness
-        few_y = frame_y
-        few_z = f_height
-    
-        fw_xt = -x_temp/2 # in case of x_temp = y this doesn't reach panel edge
-        fe_xt = x_temp/2-f_thickness 
-        few_yt = -y_half-y_trans_shift
-        few_zt = offsetfromaxis-f_height
-    
-        #frame legs for east-west 
-    
-        flw_xt = -x_temp/2 + f_thickness
-        fle_xt = x_temp/2 - f_thickness-fl_x
-        flew_yt = -y_half-y_trans_shift
-        flew_zt = offsetfromaxis-f_height
-    
-    
-        #pieces for the shorter side (north-south in this case)
-    
-        #filler
-    
-        fns_x = x_temp-2*f_thickness
-        fns_y = f_thickness
-        fns_z = f_height-f_thickness
-    
-        fns_xt = -x_temp/2+f_thickness
-        fn_yt = -y_half+y-f_thickness
-        fs_yt = -y_half
-        fns_zt = offsetfromaxis-f_height+f_thickness
-    
-        # the filler legs
-    
-        filleg_x = x_temp-2*f_thickness-2*fl_x
-        filleg_y = f_thickness + fl_x
-        filleg_z = f_thickness
-    
-        filleg_xt = -x_temp/2+f_thickness+fl_x
-        fillegn_yt = -y_half+y-f_thickness-fl_x
-        fillegs_yt = -y_half
-        filleg_zt = offsetfromaxis-f_height
-    
-    
-        # making frames: west side
-        
+        # py2 and 3 compatible: binary write, encode text first
+        text2 = '\n' + text + ' ' + customObject
         
-        frame_text = '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(frame_material, nameframe1, few_x, few_y, few_z, fw_xt, few_yt, few_zt) 
-        frame_text += ' -a {} -t 0 {} 0 | xform -rz {}'.format(Ny, y_temp+ygap, rotframe)
-    
-        frame_text += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(frame_material, nameframe2, fl_x, frame_y, f_thickness, flw_xt, flew_yt, flew_zt)
-        frame_text += ' -a {} -t 0 {} 0 | xform -rz {}'.format(Ny, y_temp+ygap, rotframe)
-                
-        # making frames: east side
-    
-        frame_text += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(frame_material, nameframe1, few_x, few_y, few_z, fe_xt, few_yt, few_zt) 
-        frame_text += ' -a {} -t 0 {} 0 | xform -rz {}'.format(Ny, y_temp+ygap, rotframe)
-    
-        frame_text += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(frame_material, nameframe2, fl_x, frame_y, f_thickness, fle_xt, flew_yt, flew_zt)
-        frame_text += ' -a {} -t 0 {} 0 | xform -rz {}'.format(Ny, y_temp+ygap, rotframe)
+        debug = False
+        if debug:
+            print (text2)
+
+        with open(radfile, 'a+') as f:
+            f.write(text2)
 
-    
-        if n_frame == 4:
-            #making frames: north side
-    
-            frame_text += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(frame_material, nameframe1, fns_x, fns_y, fns_z, fns_xt, fn_yt, fns_zt) 
-            frame_text += ' -a {} -t 0 {} 0'.format(Ny, y+ygap)
-    
-    
-            frame_text += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(frame_material, nameframe2, filleg_x, filleg_y, filleg_z, filleg_xt, fillegn_yt, filleg_zt)
-            frame_text += ' -a {} -t 0 {} 0'.format(Ny, y+ygap)
-    
-            #making frames: south side
-    
-            frame_text += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(frame_material, nameframe1, fns_x, fns_y, fns_z, fns_xt, fs_yt, fns_zt) 
-            frame_text += ' -a {} -t 0 {} 0'.format(Ny, y+ygap)
-    
-            frame_text += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(frame_material, nameframe2, filleg_x, filleg_y, filleg_z, filleg_xt, fillegs_yt, filleg_zt)
-            frame_text += ' -a {} -t 0 {} 0'.format(Ny, y+ygap)
 
-        z_inc = f_height
 
-        return z_inc, frame_text, frameParams
-    
     
-    def _makeOmega(self, omegaParams, x, y, xgap, zgap, offsetfromaxis, z_inc = 0):
+    def makeScene1axis(self, trackerdict=None, module=None, sceneDict=None,
+                       cumulativesky=None, hpc=False, moduletype=None):
         """
-        Helper function for creating a module that includes the racking 
-        structure element `omega`, 
+        Creates a SceneObj for each tracking angle which contains details of the PV
+        system configuration including row pitch, hub_height, nMods per row, nRows in the system...
 
-            
         Parameters
         ------------
-        omegaParams : dict
-            Dictionary with input parameters for creating a omega or module support structure.
-            See details below for keys needed.
-        x : numeric
-            Width of module along the axis of the torque tube or racking structure. (meters).
-        y : numeric
-            Length of module (meters)
-        xgap : float
-            Panel space in X direction. Separation between modules in a row.
-        zgap : float
-            Distance behind the modules in the z-direction to the edge of the tube (m)
-        offsetfromaxis : float
-            Internally defined variable in makeModule that specifies how much
-            the module is offset from the Axis of Rotation due to zgap and or 
-            frame thickness.
-        z_inc : dict
-            Internally defined variable in makeModule that specifies how much
-            the module is offseted by the Frame.
-        
-        For creating a module that includes the racking structure element `omega`, 
-        the following input parameters should be in ``omegaParams``, otherwise 
-        default values will be used:
-        
-        ====================    ===============================================
-        Keys : type             Description
-        ================        =============================================== 
-        omega_material : str    The material the omega structure is made of
-        x_omega1  : float       The length of the module-adjacent arm of the 
-                                omega parallel to the x-axis of the module
-        mod_overlap : float     The length of the overlap between omega and 
-                                module surface on the x-direction
-        y_omega  : float         Length of omega (Y-direction)
-        omega_thickness  : float Omega thickness
-        x_omega3  : float       X-direction length of the torquetube adjacent 
-                                arm of omega
-        inverted : Bool         Modifies the way the Omega is set on the Torquetbue
-                                Looks like False: u  vs True: n  (default False)
-        =====================   ===============================================
-        """
-        
-        if 'omega_material' not in omegaParams:
-            omegaParams['omega_material'] = 'Metal_Grey'
-            self._missingKeyWarning('Omega', 'omega_material', omegaParams['omega_material'])
-        
-        if 'x_omega1' not in omegaParams:
-            omegaParams['x_omega1'] = xgap*0.5*0.6
-            self._missingKeyWarning('Omega', 'x_omega1', omegaParams['x_omega1'])
-
-        if 'y_omega' not in omegaParams:
-            omegaParams['y_omega'] = y/2
-            self._missingKeyWarning('Omega', 'y_omega', omegaParams['y_omega'])
-        
-        if 'mod_overlap' not in omegaParams:
-            omegaParams['mod_overlap'] = omegaParams['x_omega1']*0.6
-            self._missingKeyWarning('Omega', 'mod_overlap', omegaParams['mod_overlap'])
-                
-        if 'x_omega3' not in omegaParams:
-            omegaParams['x_omega3'] = xgap*0.5*0.3
-            self._missingKeyWarning('Omega', 'x_omega3', omegaParams['x_omega3'])
-                
-        if 'inverted' not in omegaParams:
-            omegaParams['inverted'] = False
-            self._missingKeyWarning('Omega', 'inverted', omegaParams['inverted'])
-        
-        if 'omega_thickness' not in omegaParams:
-            omegaParams['omega_thickness'] = 0.004
-            self._missingKeyWarning('Omega', 'omega_thickness', omegaParams['omega_thickness'])
-                
-        #Defining internal names
-        omega_material = omegaParams['omega_material'] 
-        x_omega1 = omegaParams['x_omega1']
-        y_omega = omegaParams['y_omega']
-        mod_overlap = omegaParams['mod_overlap']
-        x_omega3 = omegaParams['x_omega3'] 
-        inverted = omegaParams['inverted']
-        omega_thickness = omegaParams['omega_thickness']
-        
-        z_omega2 = zgap
-        x_omega2 = omega_thickness 
-        z_omega1 = omega_thickness
-        z_omega3 = omega_thickness
-        
-        #naming the omega pieces
-        name1 = 'mod_adj'
-        name2 = 'verti'
-        name3 = 'tt_adj'
-        
-        
-        # defining the module adjacent member of omega
-        x_translate1 = -x/2 - x_omega1 + mod_overlap
-        y_translate = -y_omega/2 #common for all the pieces
-        z_translate1 = offsetfromaxis-z_omega1
-        
-        #defining the vertical (zgap) member of the omega
-        x_translate2 = x_translate1
-        z_translate2 = offsetfromaxis-z_omega2
-            
-        #defining the torquetube adjacent member of omega
-        x_translate3 = x_translate1-x_omega3
-        z_translate3 =z_translate2
-        
-        if z_inc != 0: 
-            z_translate1 += -z_inc
-            z_translate2 += -z_inc
-            z_translate3 += -z_inc
-        
-        # for this code, only the translations need to be shifted for the inverted omega
-        
-        if inverted == True:
-            # shifting the non-inv omega shape of west as inv omega shape of east
-            x_translate1_inv_east = x/2-mod_overlap
-            x_shift_east = x_translate1_inv_east - x_translate1
-
-            # shifting the non-inv omega shape of west as inv omega shape of east
-            x_translate1_inv_west = -x_translate1_inv_east - x_omega1
-            x_shift_west = -x_translate1_inv_west + (-x_translate1-x_omega1)
-            
-            #customizing the East side of the module for omega_inverted
-
-            omegatext = '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(omega_material, name1, x_omega1, y_omega, z_omega1, x_translate1_inv_east, y_translate, z_translate1) 
-            omegatext += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(omega_material, name2, x_omega2, y_omega, z_omega2, x_translate2 + x_shift_east, y_translate, z_translate2)
-            omegatext += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(omega_material, name3, x_omega3, y_omega, z_omega3, x_translate3 + x_shift_east, y_translate, z_translate3)
-
-            #customizing the West side of the module for omega_inverted
-
-            omegatext += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(omega_material, name1, x_omega1, y_omega, z_omega1, x_translate1_inv_west, y_translate, z_translate1) 
-            omegatext += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(omega_material, name2, x_omega2, y_omega, z_omega2, -x_translate2-x_omega2 -x_shift_west, y_translate, z_translate2)
-            omegatext += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(omega_material, name3, x_omega3, y_omega, z_omega3, -x_translate3-x_omega3 - x_shift_west, y_translate, z_translate3)
-            
-            omega2omega_x = -x_translate1_inv_east*2
-        
-        else:
-            
-            #customizing the West side of the module for omega
-            
-            omegatext = '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(omega_material, name1, x_omega1, y_omega, z_omega1, x_translate1, y_translate, z_translate1) 
-            omegatext += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(omega_material, name2, x_omega2, y_omega, z_omega2, x_translate2, y_translate, z_translate2)
-            omegatext += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(omega_material, name3, x_omega3, y_omega, z_omega3, x_translate3, y_translate, z_translate3)
-                
-            #customizing the East side of the module for omega
-                
-            omegatext += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(omega_material, name1, x_omega1, y_omega, z_omega1, -x_translate1-x_omega1, y_translate, z_translate1) 
-            omegatext += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(omega_material, name2, x_omega2, y_omega, z_omega2, -x_translate2-x_omega2, y_translate, z_translate2)
-            omegatext += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(omega_material, name3, x_omega3, y_omega, z_omega3, -x_translate3-x_omega3, y_translate, z_translate3)
-        
-            omega2omega_x = -x_translate3*2
-        
-        return omega2omega_x,omegatext, omegaParams
-    
-
-    
-    
-    def makeCustomObject(self, name=None, text=None):
-        """
-        Function for development and experimenting with extraneous objects in the scene.
-        This function creates a `name.rad` textfile in the objects folder
-        with whatever text that is passed to it.
-        It is up to the user to pass the correct radiance format.
-        
-        For example, to create a box at coordinates 0,0 (with its bottom surface
-        on the plane z=0):
-            
-        .. code-block:
-        
-            name = 'box'
-            text='! genbox black PVmodule 0.5 0.5 0.5 | xform -t -0.25 -0.25 0'
-
-        Parameters
-        ----------
-        name : str
-            String input to name the module type
-        text : str
-            Text used in the radfile to generate the module
-        
-        """
-
-        customradfile = os.path.join('objects', '%s.rad'%(name)) # update in 0.2.3 to shorten radnames
-        # py2 and 3 compatible: binary write, encode text first
-        with open(customradfile, 'wb') as f:
-            f.write(text.encode('ascii'))
-
-        print("\nCustom Object Name", customradfile)
-        self.customradfile = customradfile
-        return customradfile
-
-
-    def printModules(self):
-        # print available module types by creating a dummy SceneObj
-        temp = SceneObj()
-        modulenames = temp.readModule()
-        print('Available module names: {}'.format([str(x) for x in modulenames]))
-        return modulenames
-    
-    def makeScene(self, moduletype=None, sceneDict=None, hpc=False, radname=None):
-        """
-        Create a SceneObj which contains details of the PV system configuration including
-        tilt, row pitch, height, nMods per row, nRows in the system...
-
-        Parameters
-        ----------
-        moduletype : str
-            String name of module created with makeModule()
-        sceneDict : dictionary
-            Dictionary with keys: `tilt`, `clearance_height`*, `pitch`,
-            `azimuth`, `nMods`, `nRows`, `hub_height`*, `height`*
-            * height deprecated from sceneDict. For makeScene (fixed systems)
-            if passed it is assumed it reffers to clearance_height.
-            `clearance_height` recommended for fixed_tracking systems.
-            `hub_height` can also be passed as a possibility.
-        hpc : bool
+        trackerdict
+            Output from GenCumSky1axis
+        module : str or ModuleObj
+            Name or ModuleObj created with makeModule()
+        sceneDict : 
+            Dictionary with keys:`tilt`, `hub_height`, `pitch`, `azimuth`
+        cumulativesky : bool
+            Defines if sky will be generated with cumulativesky or gendaylit.
+        hpc :  bool
             Default False. For makeScene, it adds the full path
             of the objects folder where the module . rad file is saved.
-        radname : str
-            Gives a custom name to the scene file. Useful when parallelizing.
+        moduletype: DEPRECATED. use the `module` kwarg instead.
 
         Returns
-        -------
-        SceneObj 
-            'scene' with configuration details
-            
+        --------
+        trackerdict 
+            Append the following keys
+                'radfile'
+                    directory where .rad scene file is stored
+                'scene'
+                    SceneObj for each tracker theta
+                'clearance_height'
+                    Calculated ground clearance based on
+                    `hub height`, `tilt` angle and overall collector width `sceney`
+                
         """
         
-        if moduletype is None:
-            print('makeScene(moduletype, sceneDict, nMods, nRows).  '+\
-                  'Available moduletypes: monopanel, simple_panel' )
-            #TODO: read in config file to identify available module types
-            return
-        self.scene = SceneObj(moduletype)
+        import math
 
         if sceneDict is None:
-            print('makeScene(moduletype, sceneDict, nMods, nRows).  '+\
-                  'sceneDict inputs: .tilt .clearance_height .pitch .azimuth')
-            return self.scene
-
-        if 'orientation' in sceneDict:
-            if sceneDict['orientation'] == 'landscape':
-                raise Exception('\n\n ERROR: Orientation format has been '+
-                                'deprecated since version 0.2.4. If you want '+
-                                'to flip your modules, on makeModule switch '+
-                                'the x and y values. X value is the size of '+
-                                'the panel along the row, so for a "landscape"'+
-                                'panel x should be > than y.\n\n')
-        #if sceneDict.has_key('azimuth') is False:
-        if 'azimuth' not in sceneDict:
-            sceneDict['azimuth'] = 180
+            print('usage: makeScene1axis(module, sceneDict, nMods, nRows).'+
+                  'sceneDict inputs: .hub_height .azimuth .nMods .nRows'+
+                  'and .pitch or .gcr')
+            return
 
+        # If no nRows or nMods assigned on deprecated variable or dictionary,
+        # assign default.
         if 'nRows' not in sceneDict:
             sceneDict['nRows'] = 7
-
         if 'nMods' not in sceneDict:
             sceneDict['nMods'] = 20
 
-        # Fixed tilt routine
-        # Preferred: clearance_height,
-        # If only height is passed, it is assumed to be clearance_height.
-        
-        sceneDict, use_clearanceheight  = _heightCasesSwitcher(sceneDict, 
-                                                                preferred='clearance_height', 
-                                                                nonpreferred='hub_height')
-        
-        self.nMods = sceneDict['nMods']
-        self.nRows = sceneDict['nRows']
-        self.sceneRAD = self.scene._makeSceneNxR(moduletype=moduletype,
-                                                sceneDict=sceneDict,
-                                                hpc=hpc, radname=radname)
-
-        if 'appendRadfile' not in sceneDict:
-            appendRadfile = False
-        else:
-            appendRadfile = sceneDict['appendRadfile']
+        if trackerdict is None:
+            try:
+                trackerdict = self.trackerdict
+            except AttributeError:
+                print('No trackerdict value passed or available in self')
 
-        if appendRadfile:
-            debug = False
+        if cumulativesky is None:
             try:
-                self.radfiles.append(self.sceneRAD)
-                if debug:
-                    print( "Radfile APPENDED!")
-            except:
-                #TODO: Manage situation where radfile was created with
-                #appendRadfile to False first..
-                self.radfiles=[]
-                self.radfiles.append(self.sceneRAD)
-                if debug:
-                    print( "Radfile APPENDAGE created!")
-        else:
-            self.radfiles = [self.sceneRAD]
-        return self.scene
+                # see if cumulativesky = False was set earlier,
+                # e.g. in RadianceObj.set1axis
+                cumulativesky = self.cumulativesky
+            except AttributeError:
+                # default cumulativesky = true to maintain backward compatibility.
+                cumulativesky = True
 
-    def appendtoScene(self, radfile=None, customObject=None, text=''):
-        """
-        Appends to the `Scene radfile` in folder `\objects` the text command in Radiance
-        lingo created by the user.
-        Useful when using addCustomObject to the scene.
 
-        Parameters
-        ----------
-        radfile: str
-            Directory and name of where .rad scene file is stored
-        customObject : str
-            Directory and name of custom object .rad file is stored
-        text : str 
-            Command to be appended to the radfile. Do not leave empty spaces
-            at the end.
-
-        Returns
-        -------
-        Nothing, the radfile must already be created and assigned when running this.
-        
-        """
-        
-        #TODO: Add a custom name and replace radfile name
-
-        # py2 and 3 compatible: binary write, encode text first
-        text2 = '\n' + text + ' ' + customObject
-        
-        debug = False
-        if debug:
-            print (text2)
-
-        with open(radfile, 'a+') as f:
-            f.write(text2)
-
-
-
-    
-    def makeScene1axis(self, trackerdict=None, moduletype=None, sceneDict=None,
-                       cumulativesky=None, hpc=False):
-        """
-        Creates a SceneObj for each tracking angle which contains details of the PV
-        system configuration including row pitch, hub_height, nMods per row, nRows in the system...
-
-        Parameters
-        ------------
-        trackerdict
-            Output from GenCumSky1axis
-        moduletype : str
-            Name of module created with makeModule()
-        sceneDict : 
-            Dictionary with keys:`tilt`, `hub_height`, `pitch`, `azimuth`
-        cumulativesky : bool
-            Defines if sky will be generated with cumulativesky or gendaylit.
-        hpc :  bool
-            Default False. For makeScene, it adds the full path
-            of the objects folder where the module . rad file is saved.
-
-        Returns
-        --------
-        trackerdict 
-            Append the following keys
-                'radfile'
-                    directory where .rad scene file is stored
-                'scene'
-                    SceneObj for each tracker theta
-                'clearance_height'
-                    Calculated ground clearance based on
-                    `hub height`, `tilt` angle and overall collector width `sceney`
-                
-        """
-        
-        import math
-
-        if sceneDict is None:
-            print('usage: makeScene1axis(moduletype, sceneDict, nMods, nRows).'+
-                  'sceneDict inputs: .hub_height .azimuth .nMods .nRows'+
-                  'and .pitch or .gcr')
-            return
-
-        # If no nRows or nMods assigned on deprecated variable or dictionary,
-        # assign default.
-        if 'nRows' not in sceneDict:
-            sceneDict['nRows'] = 7
-        if 'nMods' not in sceneDict:
-            sceneDict['nMods'] = 20
-
-        if trackerdict is None:
-            try:
-                trackerdict = self.trackerdict
-            except AttributeError:
-                print('No trackerdict value passed or available in self')
-
-        if cumulativesky is None:
+        if moduletype is not None:
+            module = moduletype
+            print("Warning:  input `moduletype` is deprecated. Use kwarg "
+                  "`module` instead")
+        if module is None:
             try:
-                # see if cumulativesky = False was set earlier,
-                # e.g. in RadianceObj.set1axis
-                cumulativesky = self.cumulativesky
+                module = self.module
+                print(f'Using last saved module, name: {module.name}')
             except AttributeError:
-                # default cumulativesky = true to maintain backward compatibility.
-                cumulativesky = True
-
-        if moduletype is None:
-            print('usage:  makeScene1axis(trackerdict, moduletype, '+
-                  'sceneDict, nMods, nRows). ')
-            self.printModules() #print available module types
-            return
-
+                print('usage:  makeScene1axis(trackerdict, module, '+
+                      'sceneDict, nMods, nRows). ')
+                self.printModules() #print available module types
+                return
 
         if 'orientation' in sceneDict:
-            if sceneDict['orientation'] == 'landscape':
-                raise Exception('\n\n ERROR: Orientation format has been '+
-                                'deprecated since version 0.2.4. If you want'+
-                                ' to flip your modules, on makeModule switch'+
-                                'the x and y values. X value is the size of'+
-                                ' the panel along the row, so for a '+
-                                '"landscape" panel x should be > than y.\n\n')
+            raise Exception('\n\n ERROR: Orientation format has been '
+                'deprecated since version 0.2.4. If you want to flip your '
+                'modules, on makeModule switch the x and y values.\n\n')
        
         # 1axis routine
         # Preferred hub_height
@@ -2961,7 +2223,6 @@ def makeScene1axis(self, trackerdict=None, moduletype=None, sceneDict=None,
                                                         preferred='hub_height', 
                                                         nonpreferred='clearance_height')
 
-        
         if use_clearanceheight:
             simplefix = 0
             hubheight = sceneDict['clearance_height'] # Not really, but this is the fastest 
@@ -2977,7 +2238,7 @@ def makeScene1axis(self, trackerdict=None, moduletype=None, sceneDict=None,
         if cumulativesky is True:        # cumulativesky workflow
             print('\nMaking .rad files for cumulativesky 1-axis workflow')
             for theta in trackerdict:
-                scene = SceneObj(moduletype)
+                scene = SceneObj(module)
                 if trackerdict[theta]['surf_azm'] >= 180:
                     trackerdict[theta]['surf_azm'] = trackerdict[theta]['surf_azm']-180
                     trackerdict[theta]['surf_tilt'] = trackerdict[theta]['surf_tilt']*-1
@@ -3008,8 +2269,7 @@ def makeScene1axis(self, trackerdict=None, moduletype=None, sceneDict=None,
                                   'nRows': sceneDict['nRows'],
                                   'modulez': scene.moduleDict['z']}
 
-                radfile = scene._makeSceneNxR(moduletype=moduletype,
-                                             sceneDict=sceneDict2,
+                radfile = scene._makeSceneNxR(sceneDict=sceneDict2,
                                              radname=radname,
                                              hpc=hpc)
                 trackerdict[theta]['radfile'] = radfile
@@ -3021,7 +2281,7 @@ def makeScene1axis(self, trackerdict=None, moduletype=None, sceneDict=None,
             print('\nMaking ~%s .rad files for gendaylit 1-axis workflow (this takes a minute..)' % (len(trackerdict)))
             count = 0
             for time in trackerdict:
-                scene = SceneObj(moduletype)
+                scene = SceneObj(module)
 
                 if trackerdict[time]['surf_azm'] >= 180:
                     trackerdict[time]['surf_azm'] = trackerdict[time]['surf_azm']-180
@@ -3054,8 +2314,7 @@ def makeScene1axis(self, trackerdict=None, moduletype=None, sceneDict=None,
                                       'nRows': sceneDict['nRows'],
                                       'modulez': scene.moduleDict['z']}
 
-                    radfile = scene._makeSceneNxR(moduletype=moduletype,
-                                                 sceneDict=sceneDict2,
+                    radfile = scene._makeSceneNxR(sceneDict=sceneDict2,
                                                  radname=radname,
                                                  hpc=hpc)
                     trackerdict[time]['radfile'] = radfile
@@ -3068,7 +2327,7 @@ def makeScene1axis(self, trackerdict=None, moduletype=None, sceneDict=None,
         self.nRows = sceneDict['nRows']
         self.hub_height = hubheight
         
-        return trackerdict#self.scene
+        return trackerdict
 
 
     def analysis1axis(self, trackerdict=None, singleindex=None, accuracy='low',
@@ -3531,92 +2790,24 @@ class SceneObj:
     def __init__(self, moduletype=None):
         ''' initialize SceneObj
         '''
-        modulenames = self.readModule()
         # should sceneDict be initialized here? This is set in _makeSceneNxR
         if moduletype is None:
-            #print('Usage: SceneObj(moduletype)\nNo module type selected. 
-            #   Available module types: {}'.format(modulenames))
             return
-        else:
-            if moduletype in modulenames:
-                # read in module details from configuration file.
-                self.moduleDict = self.readModule(name = moduletype)
-            else:
-                print('incorrect panel type selection')
-                return
-
+        elif type(moduletype) == str:
+            self.module = ModuleObj(name=moduletype)
 
 
+        elif type(moduletype) == ModuleObj: # try moduleObj
+            self.module = moduletype
 
-    def readModule(self, name=None):
-        """
-        Read in available modules in module.json.  If a specific module name is
-        passed, return those details into the SceneObj. Otherwise 
-        return available module list.
-
-        Parameters
-        -----------
-        name : str
-            Name of module to be read
-
-        Returns
-        -------
-        moduleDict : dictionary
-            self.scenex : (float)
-                Overall module width including xgap.
-            self.sceney : (float)
-                Overall module(s) height including ygaps along the collector width (CW),
-        list of modulenames if name is not passed in.
-
-        """
-
-        import json
-        filedir = os.path.join(DATA_PATH,'module.json')
-        with open( filedir ) as configfile:
-            data = json.load(configfile)
-
-        modulenames = data.keys()
-        if name is None:
-
-            return modulenames
+        self.moduleDict = self.module.data
+        self.scenex = self.moduleDict['scenex']
+        self.sceney = self.moduleDict['sceney']
+        self.offsetfromaxis = self.moduleDict['offsetfromaxis']
+        self.modulefile = self.moduleDict['modulefile']
 
-        if name in modulenames:
-            moduleDict = data[name]
-            self.moduletype = name
-
-            radfile = moduleDict['modulefile']
-            self.x = moduleDict['x'] # width of module.
-            self.y = moduleDict['y'] # length of module.
-            self.z = moduleDict['z']
-            self.bifi = moduleDict['bifi']  # panel bifaciality. Not used yet
-            if 'scenex' in moduleDict:
-                self.scenex = moduleDict['scenex']
-            else:
-                self.scenex = moduleDict['x']
-            if 'sceney' in moduleDict:
-                self.sceney = moduleDict['sceney']
-            else:
-                self.sceney = moduleDict['y']
-            if 'offsetfromaxis' in moduleDict:
-                self.offsetfromaxis = moduleDict['offsetfromaxis']
-            else:
-                self.offsetfromaxis = 0
-            #
-                    #create new .RAD file
-            if not os.path.isfile(radfile):
-                # py2 and 3 compatible: binary write, encode text first
-                with open(radfile, 'wb') as f:
-                    f.write(moduleDict['text'].encode('ascii'))
-            #if not os.path.isfile(radfile):
-            #    raise Exception('Error: module file not found {}'.format(radfile))mod
-            self.modulefile = radfile
-
-            return moduleDict
-        else:
-            print('Error: module name {} doesnt exist'.format(name))
-            return {}
 
-    def _makeSceneNxR(self, moduletype=None, sceneDict=None, radname=None, hpc=False):
+    def _makeSceneNxR(self, modulename=None, sceneDict=None, radname=None, hpc=False):
         """
         Arrange module defined in :py:class:`bifacial_radiance.SceneObj` into a N x R array.
         Returns a :py:class:`bifacial_radiance.SceneObj` which contains details 
@@ -3630,7 +2821,7 @@ def _makeSceneNxR(self, moduletype=None, sceneDict=None, radname=None, hpc=False
 
         Parameters
         ------------
-        moduletype: str 
+        modulename: str 
             Name of module created with :py:class:`~bifacial_radiance.RadianceObj.makeModule`.
         sceneDict : dictionary 
             Dictionary of scene parameters.
@@ -3663,28 +2854,19 @@ def _makeSceneNxR(self, moduletype=None, sceneDict=None, radname=None, hpc=False
 
         """
 
-        #Cleanup Should this still be here?
-        if moduletype is None:
-            print('makeScene(moduletype, sceneDict, nMods, nRows).'
-                  'Available moduletypes: monopanel, simple_panel' )
-            #TODO: read in config file to identify available module types
-            return
-        #self.scene = SceneObj(moduletype)   #is this needed?
+        if modulename is None:
+            modulename = self.module.name
 
         if sceneDict is None:
-            print('makeScene(moduletype, sceneDict, nMods, nRows).  sceneDict'
+            print('makeScene(modulename, sceneDict, nMods, nRows).  sceneDict'
                   ' inputs: .tilt .azimuth .nMods .nRows' 
                   ' AND .tilt or .gcr ; AND .hub_height or .clearance_height')
 
 
         if 'orientation' in sceneDict:
-            if sceneDict['orientation'] == 'landscape':
-                raise Exception('\n\n ERROR: Orientation format has been '
-                                'deprecated since version 0.2.4. If you want '
-                                'to flip your modules, on makeModule switch '
-                                'the x and y values. X value is the size of '
-                                'the panel along the row, so for a "landscape'
-                                ' panel x should be > than y.\n\n')
+            raise Exception('\n\n ERROR: Orientation format has been '
+                'deprecated since version 0.2.4. If you want to flip your '
+                'modules, on makeModule switch the x and y values.\n\n')
 
         if 'azimuth' not in sceneDict:
             sceneDict['azimuth'] = 180
@@ -3699,7 +2881,7 @@ def _makeSceneNxR(self, moduletype=None, sceneDict=None, radname=None, hpc=False
             sceneDict['originy'] = 0
 
         if radname is None:
-            radname =  str(self.moduletype).strip().replace(' ', '_')
+            radname =  str(self.module.name).strip().replace(' ', '_')
 
         # loading variables
         tilt = sceneDict['tilt']
@@ -3796,31 +2978,7 @@ def _makeSceneNxR(self, moduletype=None, sceneDict=None, radname=None, hpc=False
 #        self.hub_height = hubheight
         return radfile
     
-    def showModule(self, name):
-        """ 
-        Method to call objview on a module called 'name' and render it 
-        (visualize it).
-        
-        Parameters
-        ----------
-        name : str
-            Name of module to be rendered.
-        
-        """
-        
-        moduleDict = self.readModule(name)
-        modulefile = moduleDict['modulefile']
-        
-        cmd = 'objview %s %s' % (os.path.join('materials', 'ground.rad'),
-                                         modulefile)
-        _,err = _popen(cmd,None)
-        if err is not None:
-            print('Error: {}'.format(err))
-            print('possible solution: install radwinexe binary package from '
-                  'http://www.jaloxa.eu/resources/radiance/radwinexe.shtml'
-                  ' into your RADIANCE binaries path')
-            return
-    
+   
     def showScene(self):
         """ 
         Method to call objview on the scene included in self
@@ -3838,6 +2996,960 @@ def showScene(self):
             return
 # end of SceneObj
 
+class ModuleObj:
+    """
+    Module object.  Does the heavy lifting of demo.makeModule()
+    Module details are passed in and stored in module.json.
+    SceneObj reads back
+    
+    Parameters
+    ----------
+    asdf  : 
+
+    Methods
+    -------
+    __init__ : initialize the object, either by reading in from name or passing
+               parameters
+    readModule : read back module data from module.json
+    makeCellLevel :
+    makeOmegas :
+    makeFrames :
+    makeTorqueTube :
+    """
+    
+    def __init__(self, name=None, x=None, y=None, z=None, bifi=1, modulefile=None, 
+                 text=None, customtext='', torquetube=False, xgap=0.01, ygap=0.0, 
+                 zgap=0.1, numpanels=1, rewriteModulefile=True, 
+                 axisofrotationTorqueTube=False, cellModule=None,  
+                 glass=False, modulematerial=None, tubeParams=None,
+                 omegaParams=None, frameParams=None):
+        """
+        Add module details to the .JSON module config file module.json
+        makeModule is in the `RadianceObj` class because this is defined before a `SceneObj` is.
+
+        Module definitions assume that the module .rad file is defined
+        with zero tilt, centered along the x-axis and y-axis for the center
+        of rotation of the module (+X/2, -X/2, +Y/2, -Y/2 on each side).
+        Tip: to define a module that is in 'portrait' mode, y > x. 
+
+        Parameters
+        ------------
+        name : str
+            Input to name the module type
+        x : numeric
+            Width of module along the axis of the torque tube or racking structure. (meters).
+        y : numeric
+            Length of module (meters)
+        bifi : numeric
+            Bifaciality of the panel (not currently used). Between 0 (monofacial) 
+            and 1, default 1.
+        modulefile : str
+            Existing radfile location in \objects.  Otherwise a default value is used
+        text : str
+            Text used in the radfile to generate the module
+        customtext : str
+            Added-text used in the radfile to generate any
+            extra details in the racking/module. Does not overwrite
+            generated module (unlike "text"), but adds to it at the end.
+        rewriteModulefile : bool
+            Default True. Will rewrite module file each time makeModule is run.
+        torquetube : bool
+            This variable defines if there is a torque tube or not.
+        numpanels : int
+            Number of modules arrayed in the Y-direction. e.g.
+            1-up or 2-up, etc. (supports any number for carport/Mesa simulations)
+        xgap : float
+            Panel space in X direction. Separation between modules in a row.
+        ygap : float
+            Gap between modules arrayed in the Y-direction if any.
+        zgap : float
+            Distance behind the modules in the z-direction to the edge of the tube (m)
+        cellModule : dict
+            Dictionary with input parameters for creating a cell-level module.
+            See details below for keys needed.
+        tubeParams : dict
+            Dictionary with input parameters for creating a torque tube as part of the module.
+            See details below for keys needed.  interacts with the `torquetube` 
+            variable so if bool is false but tubeParams is passed, geometry is calculated
+            as if the system is tracked, just with no torque tube object.
+        frameParams : dict
+            Dictionary with input parameters for creating a frame as part of the module.
+            See details below for keys needed.
+        omegaParams : dict
+            Dictionary with input parameters for creating a omega or module support structure.
+            See details below for keys needed.
+        Notes
+        -----
+        For updating torque tube details, the following input parameters should 
+        to be in ``tubeParams``:
+        
+        ================   ====================================================
+        Keys : type        Description
+        ================   ====================================================  
+        diameter : float   Tube diameter in meters. For square, diameter means 
+                           the length of one of the square-tube side.  For Hex, 
+                           diameter is the distance between two vertices 
+                           (diameter of the circumscribing circle). Default 0.1
+        tubetype : str     Options: 'Square', 'Round' (default), 'Hex' or 'Oct'
+                           Tube cross section
+        material : str     Options: 'Metal_Grey' or 'black'. Material for the 
+                           torque tube.
+        axisofrotation     (bool) :  Default True. IF true, creates geometry
+                           so center of rotation is at the center of the 
+                           torquetube, with an offsetfromaxis equal to half the
+                           torquetube diameter + the zgap. If there is no 
+                           torquetube (torquetube=False), offsetformaxis will 
+                           equal the zgap.
+        ================   ====================================================  
+        
+        For creating a cell-level module, the following input parameters should 
+        to be in ``cellModule``:
+        
+        ================   ====================================================
+        Keys : type        Description
+        ================   ====================================================  
+        numcellsx : int    Number of cells in the X-direction within the module
+        numcellsy : int    Number of cells in the Y-direction within the module
+        xcell : float      Width of each cell (X-direction) in the module
+        ycell : float      Length of each cell (Y-direction) in the module
+        xcellgap : float   Spacing between cells in the X-direction
+        ycellgap : float   Spacing between cells in the Y-direction
+        centerJB : float   (optional) Distance betwen both sides of cell arrays 
+                           in a center-JB half-cell module. If 0 or not provided,
+                           module will not have the center JB spacing. 
+                           Only implemented for 'portrait' mode at the moment.
+                           (numcellsy > numcellsx). 
+        ================   ====================================================  
+        
+        For creating a module that includes the frames attached to the module, 
+        the following input parameters should to be in ``frameParams``:
+        
+        =======================  ===============================================
+        Keys : type              Description
+        =======================  =============================================== 
+        frame_material : str     The material the frame structure is made of
+        frame_thickness : float  The profile thickness of the frame 
+        frame_z : float          The Z-direction length of the frame that extends 
+                                 below the module plane
+        frame_width : float      The length of the bottom frame that is bolted 
+                                 with the omega
+        nSides_frame : int       The number of sides of the module that are framed.
+                                 4 (default) or 2
+        =======================  ===============================================
+        
+        
+        For creating a module that includes the racking structure element `omega`, 
+        the following input parameters should be in ``omegaParams``, otherwise 
+        default values will be used:
+        
+        ========================  ===============================================
+        Keys : type               Description
+        ========================  =============================================== 
+        omega_material : str      The material the omega structure is made of
+        x_omega1  : float         The length of the module-adjacent arm of the 
+                                  omega parallel to the x-axis of the module
+        mod_overlap : float       The length of the overlap between omega and 
+                                  module surface on the x-direction
+        y_omega  : float          Length of omega (Y-direction)
+        omega_thickness : float   Omega thickness
+        x_omega3  : float         X-direction length of the torquetube adjacent 
+                                  arm of omega
+        inverted : Bool           Modifies the way the Omega is set on the Torquetbue
+                                  Looks like False: u  vs True: n  (default False)
+        ========================  ===============================================
+        
+        '"""
+
+            
+        """
+        diameter=0.1, tubetype='Round', material='Metal_Grey', axisofrotation=True
+        torquetube = False
+        """
+            
+        #replace whitespace with underlines. what about \n and other weird characters?
+        self.name = str(name).strip().replace(' ', '_') 
+        self.customtext = customtext
+        self.glass = glass
+        
+        # set TT default values
+        tubeParams = self._ttDefaults(torquetube, tubeParams)
+        
+        # are we writing to JSON with passed data or just reading existing?
+        if (x is None) & (y is None) & (cellModule is None):
+            #just read in file
+            self.data = self.readModule(name=name)
+
+        else:
+            # set initial data object
+            self.data = {'x':x,
+                  'y':y,
+                  'z':z,
+                  'modulematerial': modulematerial,
+                  'scenex': 0,
+                  'sceney': 0,
+                  'scenez': 0,
+                  'numpanels':numpanels,
+                  'bifi':bifi,
+                  'text':text,
+                  'modulefile':modulefile,
+                  'offsetfromaxis':0,
+                  'xgap':xgap,
+                  'ygap':ygap,
+                  'zgap':zgap,
+                  'cellModule':cellModule,
+                  'torquetube':{'bool':torquetube,
+                                'diameter':tubeParams['diameter'],
+                                'tubetype':tubeParams['tubetype'],
+                                'material':tubeParams['material']
+                                }
+                  }
+            if omegaParams is not None:
+                self.data['omegaParams'] = omegaParams
+            if frameParams is not None:
+                self.data['frameParams'] = frameParams
+            try:
+                self.axisofrotationTorqueTube = tubeParams['axisofrotation']
+            except AttributeError:
+                self.axisofrotationTorqueTube = False
+                
+            self._zinc = 0
+            self._cc = 0  # cc is an offset given to the module when cells are used
+                  # so that the sensors don't fall in air when numcells is even.
+                  # For non cell-level modules default is 0.
+            
+            if self.data['modulefile'] is None:
+                self.data['modulefile'] = os.path.join('objects',
+                                                       self.name + '.rad')
+                print("\nModule Name:", self.name)
+    
+         
+            
+            if text is None: #text overrides making the module text.
+                self._makeModuleFromDict(**self.data)                
+
+            #write JSON data out and write radfile if it doesn't exist
+            self.saveModule(json=True, rewriteModulefile=rewriteModulefile)
+  
+            
+    def readModule(self, name=None):
+        """
+        Read in available modules in module.json.  If a specific module name is
+        passed, return those details into the SceneObj. Otherwise 
+        return available module list.
+
+        Parameters:  name (str)  Name of module to be read
+
+        Returns:  moduleDict dictionary or list of modulenames if name is not passed in.
+
+        """
+        import json
+        filedir = os.path.join(DATA_PATH,'module.json')
+        with open( filedir ) as configfile:
+            data = json.load(configfile)
+
+        modulenames = data.keys()
+        if name is None:
+            return modulenames
+
+        if name in modulenames:
+            moduleDict = data[name]
+            self.name = name
+
+            if not 'scenex' in moduleDict:
+                moduleDict['scenex'] = moduleDict['x']
+            if not 'sceney' in moduleDict:
+                moduleDict['sceney'] = moduleDict['y']
+            if not 'offsetfromaxis' in moduleDict:
+                moduleDict['offsetfromaxis'] = 0
+
+            self.data = moduleDict
+
+            return moduleDict
+        else:
+            print('Error: module name {} doesnt exist'.format(name))
+            return {}
+
+
+    def saveModule(self, json=True, rewriteModulefile=True):
+        """
+        write out changes to module.json and make radfile if it doesn't
+        exist.  if rewriteModulefile is true, always overwrite Radfile.
+
+        Parameters
+        ----------
+        json : bool, default is True.  Save JSON
+        rewriteModulefile : bool, default is True.
+
+        """
+        import json as jsonmodule
+        
+        if json:
+            filedir = os.path.join(DATA_PATH, 'module.json') 
+            with open(filedir) as configfile:
+                data = jsonmodule.load(configfile)
+    
+            data.update({self.name:self.data})
+            with open(os.path.join(DATA_PATH, 'module.json') ,'w') as configfile:
+                jsonmodule.dump(data, configfile, indent=4, sort_keys=True)
+    
+            print('Module {} updated in module.json'.format(self.name))
+        
+        if rewriteModulefile & os.path.isfile(self.data['modulefile']):
+            print(f"Pre-existing .rad file {self.data['modulefile']} "
+                  "will be overwritten")
+            os.remove(self.data['modulefile'])
+            
+        if not os.path.isfile(self.data['modulefile']):
+            # py2 and 3 compatible: binary write, encode text first
+            with open(self.data['modulefile'], 'wb') as f:
+                f.write(self.data['text'].encode('ascii'))
+            
+    def showModule(self):
+        """ 
+        Method to call objview and render the module object 
+        (visualize it).
+        
+        Parameters: None
+
+        """
+       
+        cmd = 'objview %s %s' % (os.path.join('materials', 'ground.rad'),
+                                         self.data['modulefile'])
+        _,err = _popen(cmd,None)
+        if err is not None:
+            print('Error: {}'.format(err))
+            print('possible solution: install radwinexe binary package from '
+                  'http://www.jaloxa.eu/resources/radiance/radwinexe.shtml'
+                  ' into your RADIANCE binaries path')
+            return 
+
+    def _ttDefaults(self, tt_bool, tubeParams):
+        """ Set torque tube default values in tubeParams.
+        """
+        if not tubeParams :
+            print('Setting default torquetube values')
+            tubeParams = {'diameter':0.1,
+                          'tubetype':'Round',
+                          'material':'Metal_Grey',
+                          'axisofrotation':tt_bool}
+        else:
+            if 'diameter' not in tubeParams:
+                tubeParams['diameter'] = 0.1
+                _missingKeyWarning('tubeParams', 'diameter', tubeParams['diameter'])
+            if 'tubetype' not in tubeParams:
+                tubeParams['tubetype'] = 'Round'
+                _missingKeyWarning('tubeParams', 'tubetype', tubeParams['tubetype'])            
+            if 'material' not in tubeParams:
+                tubeParams['material'] = 'Metal_Grey'
+                _missingKeyWarning('tubeParams', 'material', tubeParams['material'])
+            if 'axisofrotation' not in tubeParams:
+                tubeParams['axisofrotation'] = True
+                _missingKeyWarning('tubeParams', 'axisofrotation', tubeParams['axisofrotation'])
+                
+        return tubeParams
+    
+    def _makeModuleFromDict(self,  x=None, y=None, z=None, xgap=None, ygap=None, 
+                    zgap=None, numpanels=None, modulefile=None,
+                   torquetube={}, cellModule=None,     
+                   modulematerial=None, omegaParams=None, frameParams=None,
+                   **kwargs):
+
+        """
+        starting from self.data, go through and generate the text required to
+        make a module
+        """
+
+        #aliases for equations below
+        if torquetube:
+            diam = torquetube['diameter']
+            tubetype = torquetube['tubetype']
+            material = torquetube['material']
+            torquetube_bool = torquetube['bool']
+        else:
+            diam=0
+            torquetube_bool = False
+            
+        Ny = numpanels
+       
+
+        # Update values for rotating system around torque tube.
+        if self.axisofrotationTorqueTube == True:
+            if torquetube_bool == True:
+                self.data['offsetfromaxis'] = np.round(zgap + diam/2.0,8)
+            else:
+                self.data['offsetfromaxis'] = zgap
+                
+            if frameParams is not None:
+                if 'frame_z' not in frameParams:
+                    frameParams['frame_z'] = 0.03
+                    _missingKeyWarning('Frame', 'frame_z', frameParams['frame_z'])
+                self.data['offsetfromaxis'] = self.data['offsetfromaxis'] + frameParams['frame_z']
+
+
+        # Adding the option to replace the module thickess
+        if z is None:
+            z = 0.020
+            self.data['z'] = 0.020
+            
+        if modulematerial is None:
+            modulematerial = 'black'
+            self.data['modulematerial'] = 'black'
+            
+        if not cellModule:
+            try:
+                text = '! genbox {} {} {} {} {} '.format(modulematerial, 
+                                                          self.name, x, y, z)
+                text +='| xform -t {} {} {} '.format(-x/2.0,
+                                        (-y*Ny/2.0)-(ygap*(Ny-1)/2.0),
+                                        self.data['offsetfromaxis'])
+                text += '-a {} -t 0 {} 0'.format(Ny, y+ygap)
+                packagingfactor = 100.0
+
+            except NameError as err: # probably because no x or y passed
+                raise Exception('makeModule variable {}'.format(err.args[0])+
+                                ' and cellModule is None.  '+
+                                'One or the other must be specified.')
+        else:
+            (text, x, y) = self._cellLevelModule(cellModule, z, Ny, ygap, 
+                                               modulematerial)  
+            
+        self.data['scenex'] = x + xgap
+        self.data['sceney'] = np.round(y*numpanels + ygap*(numpanels-1), 8)
+        self.data['scenez'] = np.round(zgap + diam / 2.0, 8)
+        
+        if frameParams is not None:
+            self._z_inc, frametext, frameParams = self._makeFrames(frameParams = frameParams, 
+                                            x=x,y=y, ygap=ygap,numpanels=Ny, 
+                                            offsetfromaxis=self.data['offsetfromaxis'])
+        else:
+            frametext = ''
+            self._z_inc = 0  # z increment from frame thickness
+        
+          
+            
+        if omegaParams is not None:
+            # This also defines scenex for length of the torquetube.
+            omega2omega_x, omegatext, omegaParams = self._makeOmega(
+                                            omegaParams=omegaParams, 
+                                            x=x,y=y, xgap=xgap, zgap=zgap, 
+                                            z_inc=self._z_inc, offsetfromaxis=self.data['offsetfromaxis'])
+            if omega2omega_x > self.data['scenex']:
+                self.data['scenex'] =  omega2omega_x
+            
+            # TODO: is the above line better than below?
+            """
+            if self.data['scenex']<x:
+                scenex = x+xgap #overwriting scenex to maintain torquetube continuity
+        
+                print ('Warning: Omega values have been provided, but' +
+                       'the distance between modules with the omega'+
+                       'does not match the x-gap provided.'+
+                       'Setting x-gap to be the space between modules'+
+                       'from the omega.')
+            else:
+                print ('Warning: Using omega-to-omega distance to define'+
+                       'gap between modules'
+                       +'xgap value not being used')
+            """
+        else:
+            omegatext = ''
+        
+        # Defining scenex if it was not defined by the Omegas, 
+        # after the module has been created in case it is a 
+        # cell-level Module, in which the "x" gets calculated internally.
+        # Also sanity check in case omega-to-omega distance is smaller
+        # than module.
+
+        if torquetube_bool is True:
+            text += self._makeTorqueTube(tubetype, self.axisofrotationTorqueTube,
+                                         self._z_inc, zgap, diam, material, self.data['scenex'])
+
+        # TODO:  should there be anything updated here like scenez?
+        if self.glass: 
+                edge = 0.005                     
+                text = text+'\r\n! genbox stock_glass {} {} {} {} '.format(self.name+'_Glass',x+edge, y+edge, z+edge)
+                text +='| xform -t 0 {} 0 ' . format(-edge/2.0)
+                text +='| xform -t {} {} {} '.format(-x/2.0-edge/2.0 + self._cc,
+                                        (-y*Ny/2.0)-(ygap*(Ny-1)/2.0),
+                                        self.data['offsetfromaxis'] - 0.5*edge + 0.5*z)
+                text += '-a {} -t 0 {} 0'.format(Ny, y+ygap)
+            
+
+        text += frametext
+        text += omegatext    
+        text += self.customtext  # For adding any other racking details at the module level that the user might want.
+
+        self.data['text'] = text
+        return text
+    #End of makeModuleFromDict()
+    
+
+        
+    def _cellLevelModule(self, cellModuleParams, z, Ny, ygap, 
+                         modulematerial):
+        """  Calculate the .radfile generation text for a cell-level module.
+        """
+        offsetfromaxis = self.data['offsetfromaxis']
+        c = cellModuleParams
+        x = c['numcellsx']*c['xcell'] + (c['numcellsx']-1)*c['xcellgap']
+        y = c['numcellsy']*c['ycell'] + (c['numcellsy']-1)*c['ycellgap']
+
+        #center cell -
+        if c['numcellsx'] % 2 == 0:
+            cc = c['xcell']/2.0
+            print("Module was shifted by {} in X to avoid sensors on air".format(cc))
+
+
+        # For half cell modules with the JB on the center:
+        centerJB = 0
+        if 'centerJB' in c:
+              centerJB = c['centerJB']
+            
+
+        text = '! genbox {} cellPVmodule {} {} {} | '.format(modulematerial,
+                                               c['xcell'], c['ycell'], z)
+        text +='xform -t {} {} {} '.format(-x/2.0 + cc,
+                         (-y*Ny / 2.0)-(ygap*(Ny-1) / 2.0)-centerJB/2.0,
+                         offsetfromaxis)
+        
+        text += '-a {} -t {} 0 0 '.format(c['numcellsx'], c['xcell'] + c['xcellgap'])
+        
+        if centerJB != 0:
+            text += '-a {} -t 0 {} 0 '.format(c['numcellsy']/2, c['ycell'] + c['ycellgap'])
+            text += '-a {} -t 0 {} 0 '.format(2, y/2.0+centerJB)  
+        else:
+            text += '-a {} -t 0 {} 0 '.format(c['numcellsy'], c['ycell'] + c['ycellgap'])
+            
+        text += '-a {} -t 0 {} 0'.format(Ny, y+ygap)
+
+        # OPACITY CALCULATION
+        packagingfactor = np.round((c['xcell']*c['ycell']*c['numcellsx']*c['numcellsy'])/(x*y), 2)
+        print("This is a Cell-Level detailed module with Packaging "+
+              "Factor of {} %".format(packagingfactor)) 
+        
+        self._cc = cc
+        self.data['x'] = x
+        self.data['y'] = y
+        
+        return(text, x, y)                           
+
+
+    def _makeTorqueTube(self, tubetype, axisofrotationTorqueTube, z_inc, zgap, diam, 
+                        material, scenex):
+        """  
+        Return text string for generating the torque tube geometry
+        """
+        import math
+        
+        
+        text = ''
+        tto = 0  # Torquetube Offset. Default = 0 if axisofrotationTT == True
+        cc = self._cc #horizontal offset to center of a cell
+        if tubetype.lower() == 'square':
+            if axisofrotationTorqueTube == False:
+                tto = -z_inc-zgap-diam/2.0
+            text += '\r\n! genbox {} tube1 {} {} {} '.format(material,
+                                  scenex, diam, diam)
+            text += '| xform -t {} {} {}'.format(-(scenex)/2.0+cc,
+                                -diam/2.0, -diam/2.0+tto)
+
+        elif tubetype.lower() == 'round':
+            if axisofrotationTorqueTube == False:
+                tto = -z_inc-zgap-diam/2.0
+            text += '\r\n! genrev {} tube1 t*{} {} '.format(material, scenex, diam/2.0)
+            text += '32 | xform -ry 90 -t {} {} {}'.format(-(scenex)/2.0+cc, 0, tto)
+
+        elif tubetype.lower() == 'hex':
+            radius = 0.5*diam
+
+            if axisofrotationTorqueTube == False:
+                tto = -z_inc-radius*math.sqrt(3.0)/2.0-zgap
+
+            text += '\r\n! genbox {} hextube1a {} {} {} | xform -t {} {} {}'.format(
+                    material, scenex, radius, radius*math.sqrt(3),
+                    -(scenex)/2.0+cc, -radius/2.0, -radius*math.sqrt(3.0)/2.0+tto) #ztran -radius*math.sqrt(3.0)-tto
+
+
+            # Create, translate to center, rotate, translate back to prev. position and translate to overal module position.
+            text = text+'\r\n! genbox {} hextube1b {} {} {} | xform -t {} {} {} -rx 60 -t 0 0 {}'.format(
+                    material, scenex, radius, radius*math.sqrt(3), -(scenex)/2.0+cc, -radius/2.0, -radius*math.sqrt(3.0)/2.0, tto) #ztran (radius*math.sqrt(3.0)/2.0)-radius*math.sqrt(3.0)-tto)
+            
+            text = text+'\r\n! genbox {} hextube1c {} {} {} | xform -t {} {} {} -rx -60 -t 0 0 {}'.format(
+                    material, scenex, radius, radius*math.sqrt(3), -(scenex)/2.0+cc, -radius/2.0, -radius*math.sqrt(3.0)/2.0, tto) #ztran (radius*math.sqrt(3.0)/2.0)-radius*math.sqrt(3.0)-tto)
+
+        elif tubetype.lower()=='oct':
+            radius = 0.5*diam
+            s = diam / (1+math.sqrt(2.0))   # 
+
+            if axisofrotationTorqueTube == False:
+                tto = -z_inc-radius-zgap
+
+            text = text+'\r\n! genbox {} octtube1a {} {} {} | xform -t {} {} {}'.format(
+                    material, scenex, s, diam, -(scenex)/2.0, -s/2.0, -radius+tto)
+
+            # Create, translate to center, rotate, translate back to prev. position and translate to overal module position.
+            text = text+'\r\n! genbox {} octtube1b {} {} {} | xform -t {} {} {} -rx 45 -t 0 0 {}'.format(
+                    material, scenex, s, diam, -(scenex)/2.0+cc, -s/2.0, -radius, tto)
+
+            text = text+'\r\n! genbox {} octtube1c {} {} {} | xform -t {} {} {} -rx 90 -t 0 0 {}'.format(
+                    material, scenex, s, diam, -(scenex)/2.0+cc, -s/2.0, -radius, tto)
+
+            text = text+'\r\n! genbox {} octtube1d {} {} {} | xform -t {} {} {} -rx 135 -t 0 0 {} '.format(
+                    material, scenex, s, diam, -(scenex)/2.0+cc, -s/2.0, -radius, tto)
+
+
+        else:
+            raise Exception("Incorrect torque tube type.  "+
+                            "Available options: 'square' 'oct' 'hex' or 'round'."+
+                            "  Value entered: {}".format(tubetype))    
+        return text
+                                
+        
+    def _makeFrames(self, frameParams, x,y, ygap, numpanels, offsetfromaxis):
+        """
+        Helper function for creating a module that includes the frames attached to the module, 
+
+            
+        Parameters
+        ------------
+        frameParams : dict
+            Dictionary with input parameters for creating a frame as part of the module.
+            See details below for keys needed.
+        x : numeric
+            Width of module along the axis of the torque tube or racking structure. (meters).
+        y : numeric
+            Length of module (meters)
+        ygap : float
+            Gap between modules arrayed in the Y-direction if any.
+        numpanels : int
+            Number of modules arrayed in the Y-direction. e.g.
+            1-up or 2-up, etc. (supports any number for carport/Mesa simulations)
+        offsetfromaxis : float
+            Internally defined variable in makeModule that specifies how much
+            the module is offset from the Axis of Rotation due to zgap and or 
+            frame thickness.
+
+            
+        The following input parameters should to be in ``frameParams``, otherwise
+        default values will be used:
+        
+        ====================    ===============================================
+        Keys : type             Description
+        ================        =============================================== 
+        frame_material : str    The material the frame structure is made of
+        frame_thickness : float The profile thickness of the frame 
+        frame_z : float         The Z-direction length of the frame that extends 
+                                below the module plane
+        frame_width : float     The length of the bottom frame that is bolted 
+                                with the omega
+        nSides_frame : int      The number of sides of the module that are framed.
+                                4 (default) or 2
+        =====================   ===============================================
+
+        """
+        
+        if 'frame_material' not in frameParams:
+            frameParams['frame_material'] = 'Metal_Grey'
+            _missingKeyWarning('Frame', 'frame_material', frameParams['frame_material'])
+            
+            
+        if 'frame_thickness' not in frameParams:
+            frameParams['frame_thickness'] = 0.05
+            _missingKeyWarning('Frame', 'frame_thickness', frameParams['frame_thickness'])
+            
+    
+        if 'frame_z' not in frameParams:
+            frameParams['frame_z'] = 0.3
+            _missingKeyWarning('Frame', 'frame_thickness', frameParams['frame_thickness'])
+
+
+        if 'nSides_frame' not in frameParams:
+            frameParams['nSides_frame'] = 4
+            _missingKeyWarning('Frame', 'nSides_frame', frameParams['nSides_frame'])
+        
+        if 'frame_width' not in frameParams:
+            frameParams['frame_width'] = 0.05
+            _missingKeyWarning('Frame', 'frame_width', frameParams['frame_width'])
+        
+        #Defining internal names
+        frame_material = frameParams['frame_material'] 
+        f_thickness = frameParams['frame_thickness'] 
+        f_height = frameParams['frame_z'] 
+        n_frame = frameParams['nSides_frame']  
+        fl_x = frameParams['frame_width']
+        
+        y_trans_shift = 0 #pertinent to the case of x>y with 2-sided frame
+                
+    
+        
+        # Recalculating width ignoring the thickness of the aluminum
+        # for internal positioining and sizing of hte pieces
+        fl_x = fl_x-f_thickness
+        
+        if x>y and n_frame==2:
+            x_temp,y_temp = y,x
+            rotframe = 90
+            frame_y = x
+            y_trans_shift = x/2-y/2
+        else:
+            x_temp,y_temp = x,y
+            frame_y = y
+            rotframe = 0
+    
+        Ny = numpanels
+        y_half = (y*Ny/2)+(ygap*(Ny-1)/2)
+    
+        # taking care of lengths and translation points
+        # The pieces are same and symmetrical for west and east
+    
+        # naming the frame pieces
+        nameframe1 = 'frameside'
+        nameframe2 = 'frameleg'
+        
+        #frame sides
+        few_x = f_thickness
+        few_y = frame_y
+        few_z = f_height
+    
+        fw_xt = -x_temp/2 # in case of x_temp = y this doesn't reach panel edge
+        fe_xt = x_temp/2-f_thickness 
+        few_yt = -y_half-y_trans_shift
+        few_zt = offsetfromaxis-f_height
+    
+        #frame legs for east-west 
+    
+        flw_xt = -x_temp/2 + f_thickness
+        fle_xt = x_temp/2 - f_thickness-fl_x
+        flew_yt = -y_half-y_trans_shift
+        flew_zt = offsetfromaxis-f_height
+    
+    
+        #pieces for the shorter side (north-south in this case)
+    
+        #filler
+    
+        fns_x = x_temp-2*f_thickness
+        fns_y = f_thickness
+        fns_z = f_height-f_thickness
+    
+        fns_xt = -x_temp/2+f_thickness
+        fn_yt = -y_half+y-f_thickness
+        fs_yt = -y_half
+        fns_zt = offsetfromaxis-f_height+f_thickness
+    
+        # the filler legs
+    
+        filleg_x = x_temp-2*f_thickness-2*fl_x
+        filleg_y = f_thickness + fl_x
+        filleg_z = f_thickness
+    
+        filleg_xt = -x_temp/2+f_thickness+fl_x
+        fillegn_yt = -y_half+y-f_thickness-fl_x
+        fillegs_yt = -y_half
+        filleg_zt = offsetfromaxis-f_height
+    
+    
+        # making frames: west side
+        
+        
+        frame_text = '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(frame_material, nameframe1, few_x, few_y, few_z, fw_xt, few_yt, few_zt) 
+        frame_text += ' -a {} -t 0 {} 0 | xform -rz {}'.format(Ny, y_temp+ygap, rotframe)
+    
+        frame_text += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(frame_material, nameframe2, fl_x, frame_y, f_thickness, flw_xt, flew_yt, flew_zt)
+        frame_text += ' -a {} -t 0 {} 0 | xform -rz {}'.format(Ny, y_temp+ygap, rotframe)
+                
+        # making frames: east side
+    
+        frame_text += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(frame_material, nameframe1, few_x, few_y, few_z, fe_xt, few_yt, few_zt) 
+        frame_text += ' -a {} -t 0 {} 0 | xform -rz {}'.format(Ny, y_temp+ygap, rotframe)
+    
+        frame_text += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(frame_material, nameframe2, fl_x, frame_y, f_thickness, fle_xt, flew_yt, flew_zt)
+        frame_text += ' -a {} -t 0 {} 0 | xform -rz {}'.format(Ny, y_temp+ygap, rotframe)
+
+    
+        if n_frame == 4:
+            #making frames: north side
+    
+            frame_text += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(frame_material, nameframe1, fns_x, fns_y, fns_z, fns_xt, fn_yt, fns_zt) 
+            frame_text += ' -a {} -t 0 {} 0'.format(Ny, y+ygap)
+    
+    
+            frame_text += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(frame_material, nameframe2, filleg_x, filleg_y, filleg_z, filleg_xt, fillegn_yt, filleg_zt)
+            frame_text += ' -a {} -t 0 {} 0'.format(Ny, y+ygap)
+    
+            #making frames: south side
+    
+            frame_text += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(frame_material, nameframe1, fns_x, fns_y, fns_z, fns_xt, fs_yt, fns_zt) 
+            frame_text += ' -a {} -t 0 {} 0'.format(Ny, y+ygap)
+    
+            frame_text += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(frame_material, nameframe2, filleg_x, filleg_y, filleg_z, filleg_xt, fillegs_yt, filleg_zt)
+            frame_text += ' -a {} -t 0 {} 0'.format(Ny, y+ygap)
+
+        z_inc = f_height
+
+        return z_inc, frame_text, frameParams
+    
+    
+    def _makeOmega(self, omegaParams, x, y, xgap, zgap, offsetfromaxis, z_inc = 0, **kwargs):
+        """
+        Helper function for creating a module that includes the racking 
+        structure element `omega`, 
+
+            
+        Parameters
+        ------------
+        omegaParams : dict
+            Dictionary with input parameters for creating a omega or module support structure.
+            See details below for keys needed.
+        x : numeric
+            Width of module along the axis of the torque tube or racking structure. (meters).
+        y : numeric
+            Length of module (meters)
+        xgap : float
+            Panel space in X direction. Separation between modules in a row.
+        zgap : float
+            Distance behind the modules in the z-direction to the edge of the tube (m)
+        offsetfromaxis : float
+            Internally defined variable in makeModule that specifies how much
+            the module is offset from the Axis of Rotation due to zgap and or 
+            frame thickness.
+        z_inc : dict
+            Internally defined variable in makeModule that specifies how much
+            the module is offseted by the Frame.
+        
+        For creating a module that includes the racking structure element `omega`, 
+        the following input parameters should be in ``omegaParams``, otherwise 
+        default values will be used:
+        
+        ====================    ===============================================
+        Keys : type             Description
+        ================        =============================================== 
+        omega_material : str    The material the omega structure is made of
+        x_omega1  : float       The length of the module-adjacent arm of the 
+                                omega parallel to the x-axis of the module
+        mod_overlap : float     The length of the overlap between omega and 
+                                module surface on the x-direction
+        y_omega  : float         Length of omega (Y-direction)
+        omega_thickness  : float Omega thickness
+        x_omega3  : float       X-direction length of the torquetube adjacent 
+                                arm of omega
+        inverted : Bool         Modifies the way the Omega is set on the Torquetbue
+                                Looks like False: u  vs True: n  (default False)
+        =====================   ===============================================
+        """
+        
+        if 'omega_material' not in omegaParams:
+            omegaParams['omega_material'] = 'Metal_Grey'
+            _missingKeyWarning('Omega', 'omega_material', omegaParams['omega_material'])
+        
+        if 'x_omega1' not in omegaParams:
+            omegaParams['x_omega1'] = xgap*0.5*0.6
+            _missingKeyWarning('Omega', 'x_omega1', omegaParams['x_omega1'])
+
+        if 'y_omega' not in omegaParams:
+            omegaParams['y_omega'] = y/2
+            _missingKeyWarning('Omega', 'y_omega', omegaParams['y_omega'])
+        
+        if 'mod_overlap' not in omegaParams:
+            omegaParams['mod_overlap'] = omegaParams['x_omega1']*0.6
+            _missingKeyWarning('Omega', 'mod_overlap', omegaParams['mod_overlap'])
+                
+        if 'x_omega3' not in omegaParams:
+            omegaParams['x_omega3'] = xgap*0.5*0.3
+            _missingKeyWarning('Omega', 'x_omega3', omegaParams['x_omega3'])
+                
+        if 'inverted' not in omegaParams:
+            omegaParams['inverted'] = False
+            _missingKeyWarning('Omega', 'inverted', omegaParams['inverted'])
+        
+        if 'omega_thickness' not in omegaParams:
+            omegaParams['omega_thickness'] = 0.004
+            _missingKeyWarning('Omega', 'omega_thickness', omegaParams['omega_thickness'])
+                
+        #Defining internal names
+        omega_material = omegaParams['omega_material'] 
+        x_omega1 = omegaParams['x_omega1']
+        y_omega = omegaParams['y_omega']
+        mod_overlap = omegaParams['mod_overlap']
+        x_omega3 = omegaParams['x_omega3'] 
+        inverted = omegaParams['inverted']
+        omega_thickness = omegaParams['omega_thickness']
+        
+        z_omega2 = zgap
+        x_omega2 = omega_thickness 
+        z_omega1 = omega_thickness
+        z_omega3 = omega_thickness
+        
+        #naming the omega pieces
+        name1 = 'mod_adj'
+        name2 = 'verti'
+        name3 = 'tt_adj'
+        
+        
+        # defining the module adjacent member of omega
+        x_translate1 = -x/2 - x_omega1 + mod_overlap
+        y_translate = -y_omega/2 #common for all the pieces
+        z_translate1 = offsetfromaxis-z_omega1
+        
+        #defining the vertical (zgap) member of the omega
+        x_translate2 = x_translate1
+        z_translate2 = offsetfromaxis-z_omega2
+            
+        #defining the torquetube adjacent member of omega
+        x_translate3 = x_translate1-x_omega3
+        z_translate3 =z_translate2
+        
+        if z_inc != 0: 
+            z_translate1 += -z_inc
+            z_translate2 += -z_inc
+            z_translate3 += -z_inc
+        
+        # for this code, only the translations need to be shifted for the inverted omega
+        
+        if inverted == True:
+            # shifting the non-inv omega shape of west as inv omega shape of east
+            x_translate1_inv_east = x/2-mod_overlap
+            x_shift_east = x_translate1_inv_east - x_translate1
+
+            # shifting the non-inv omega shape of west as inv omega shape of east
+            x_translate1_inv_west = -x_translate1_inv_east - x_omega1
+            x_shift_west = -x_translate1_inv_west + (-x_translate1-x_omega1)
+            
+            #customizing the East side of the module for omega_inverted
+
+            omegatext = '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(omega_material, name1, x_omega1, y_omega, z_omega1, x_translate1_inv_east, y_translate, z_translate1) 
+            omegatext += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(omega_material, name2, x_omega2, y_omega, z_omega2, x_translate2 + x_shift_east, y_translate, z_translate2)
+            omegatext += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(omega_material, name3, x_omega3, y_omega, z_omega3, x_translate3 + x_shift_east, y_translate, z_translate3)
+
+            #customizing the West side of the module for omega_inverted
+
+            omegatext += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(omega_material, name1, x_omega1, y_omega, z_omega1, x_translate1_inv_west, y_translate, z_translate1) 
+            omegatext += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(omega_material, name2, x_omega2, y_omega, z_omega2, -x_translate2-x_omega2 -x_shift_west, y_translate, z_translate2)
+            omegatext += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(omega_material, name3, x_omega3, y_omega, z_omega3, -x_translate3-x_omega3 - x_shift_west, y_translate, z_translate3)
+            
+            omega2omega_x = -x_translate1_inv_east*2
+        
+        else:
+            
+            #customizing the West side of the module for omega
+            
+            omegatext = '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(omega_material, name1, x_omega1, y_omega, z_omega1, x_translate1, y_translate, z_translate1) 
+            omegatext += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(omega_material, name2, x_omega2, y_omega, z_omega2, x_translate2, y_translate, z_translate2)
+            omegatext += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(omega_material, name3, x_omega3, y_omega, z_omega3, x_translate3, y_translate, z_translate3)
+                
+            #customizing the East side of the module for omega
+                
+            omegatext += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(omega_material, name1, x_omega1, y_omega, z_omega1, -x_translate1-x_omega1, y_translate, z_translate1) 
+            omegatext += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(omega_material, name2, x_omega2, y_omega, z_omega2, -x_translate2-x_omega2, y_translate, z_translate2)
+            omegatext += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(omega_material, name3, x_omega3, y_omega, z_omega3, -x_translate3-x_omega3, y_translate, z_translate3)
+        
+            omega2omega_x = -x_translate3*2
+        return omega2omega_x,omegatext, omegaParams
+    
+    
+    
+# end of ModuleObj
+        
 class MetObj:
     """
     Meteorological data from EPW file.
diff --git a/bifacial_radiance/modelchain.py b/bifacial_radiance/modelchain.py
index fe9c809c..3246c5bb 100644
--- a/bifacial_radiance/modelchain.py
+++ b/bifacial_radiance/modelchain.py
@@ -27,7 +27,7 @@ def _append_dicts(x, y):
 
 def runModelChain(simulationParamsDict, sceneParamsDict, timeControlParamsDict=None, 
                   moduleParamsDict=None, trackingParamsDict=None, torquetubeParamsDict=None, 
-                  analysisParamsDict=None, cellLevelModuleParamsDict=None):
+                  analysisParamsDict=None, cellModuleDict=None):
     """
     This calls config.py values, which are arranged into dictionaries,
     and runs all the respective processes based on the variables in the config.py.
@@ -35,7 +35,7 @@ def runModelChain(simulationParamsDict, sceneParamsDict, timeControlParamsDict=N
     To import the variables from a .ini file, use::
         
         (simulationParamsDict, sceneParamsDict, timeControlParamsDict, moduleParamsDict, 
-         trackingParamsDict,torquetubeParamsDict,analysisParamsDict,cellLevelModuleParamsDict) = 
+         trackingParamsDict,torquetubeParamsDict,analysisParamsDict,cellModuleDict) = 
         bifacial_radiance.load.readconfigurationinputfile(inifile)
     
     """
@@ -56,12 +56,12 @@ def runModelChain(simulationParamsDict, sceneParamsDict, timeControlParamsDict=N
     inifilename = os.path.join(
         simulationParamsDict['testfolder'],  'simulation.ini')
     bifacial_radiance.load.savedictionariestoConfigurationIniFile(simulationParamsDict, sceneParamsDict, timeControlParamsDict,
-                                                                  moduleParamsDict, trackingParamsDict, torquetubeParamsDict, analysisParamsDict, cellLevelModuleParamsDict, inifilename)
+                                                                  moduleParamsDict, trackingParamsDict, torquetubeParamsDict, analysisParamsDict, cellModuleDict, inifilename)
    
     # re-load configuration file to make sure all booleans are converted
     (simulationParamsDict, sceneParamsDict, timeControlParamsDict, 
      moduleParamsDict, trackingParamsDict,torquetubeParamsDict,
-     analysisParamsDict,cellLevelModuleParamsDict) = \
+     analysisParamsDict,cellModuleDict) = \
         bifacial_radiance.load.readconfigurationinputfile(inifilename)
     
     # Load weatherfile
@@ -92,46 +92,47 @@ def runModelChain(simulationParamsDict, sceneParamsDict, timeControlParamsDict=N
     A = demo.printModules()
     
     #cellLeveLParams are none by default.
-    cellLevelModuleParams = None 
+    cellModule = None 
     try:
         if simulationParamsDict['cellLevelModule']:
-            cellLevelModuleParams = cellLevelModuleParamsDict
+            cellModule = cellModuleDict
     except: pass
     
-    if torquetubeParamsDict:
+
+    if not torquetubeParamsDict:
         #kwargs = {**torquetubeParamsDict, **moduleParamsDict} #Py3 Only
-        kwargs = _append_dicts(torquetubeParamsDict, moduleParamsDict)
-    else:
-        kwargs = moduleParamsDict
-        
+        torquetubeParamsDict = {}
+    torquetubeParamsDict['axisofrotation'] = simulationParamsDict[
+                                         'axisofrotationTorqueTube']
+
+    kwargs = moduleParamsDict
     if simulationParamsDict['moduletype'] in A:
         if simulationParamsDict['rewriteModule'] is True:
-            moduleDict = demo.makeModule(name=simulationParamsDict['moduletype'],
+            module = demo.makeModule(name=simulationParamsDict['moduletype'],
                                          torquetube=simulationParamsDict['torqueTube'],
-                                         axisofrotationTorqueTube=simulationParamsDict[
-                                             'axisofrotationTorqueTube'],
-                                         cellLevelModuleParams=cellLevelModuleParams,
+                                         tubeParams=torquetubeParamsDict,
+                                         cellModule=cellModule,
                                          **kwargs)
 
         print("\nUsing Pre-determined Module Type: %s " %
               simulationParamsDict['moduletype'])
     else:
-        moduleDict = demo.makeModule(name=simulationParamsDict['moduletype'],
+        module = demo.makeModule(name=simulationParamsDict['moduletype'],
                                      torquetube=simulationParamsDict['torqueTube'],
-                                     axisofrotationTorqueTube=simulationParamsDict['axisofrotationTorqueTube'],
-                                     cellLevelModuleParams=cellLevelModuleParams,
+                                     tubeParams=torquetubeParamsDict,
+                                     cellModule=cellModule,
                                      **kwargs)
 
     
     if 'gcr' not in sceneParamsDict:  # didn't get gcr passed - need to calculate it
-        sceneParamsDict['gcr'] = moduleDict['sceney'] / \
+        sceneParamsDict['gcr'] = module.data['sceney'] / \
             sceneParamsDict['pitch']
 
     if simulationParamsDict['tracking'] == False and simulationParamsDict['cumulativeSky'] == True:
     # Fixed gencumsky condition
-        scene = demo.makeScene(
-        moduletype=simulationParamsDict['moduletype'], sceneDict=sceneParamsDict)
-        demo.genCumSky(demo.temp_metdatafile)  
+        scene = demo.makeScene(module=simulationParamsDict['moduletype'], 
+                               sceneDict=sceneParamsDict)
+        demo.genCumSky(demo.gencumsky_metfile)  
         octfile = demo.makeOct(demo.getfilelist())
         analysis = bifacial_radiance.AnalysisObj(octfile, demo.name)
         frontscan, backscan = analysis.moduleAnalysis(scene, analysisParamsDict['modWanted'],
@@ -165,7 +166,7 @@ def runModelChain(simulationParamsDict, sceneParamsDict, timeControlParamsDict=N
             trackerdict = demo.gendaylit1axis()                
 
         trackerdict = demo.makeScene1axis(trackerdict=trackerdict,
-                                          moduletype=simulationParamsDict['moduletype'],
+                                          module=simulationParamsDict['moduletype'],
                                           sceneDict=sceneParamsDict,
                                           cumulativesky=simulationParamsDict['cumulativeSky'])
 
diff --git a/docs/sphinx/source/whatsnew/v0.3.5.rst b/docs/sphinx/source/whatsnew/v0.3.5.rst
index ba9247a0..bbf0af9c 100644
--- a/docs/sphinx/source/whatsnew/v0.3.5.rst
+++ b/docs/sphinx/source/whatsnew/v0.3.5.rst
@@ -2,7 +2,8 @@
 
 v0.3.5 (XX / XX / 2021)
 ------------------------
-Release of new version including new spectral property class, module analysis when using cells, and other improvements for more seamless use with dask and paralellization.
+Release of new version including new spectral property class, ModuleObj class, module analysis when using cells, 
+and other improvements for more seamless use with dask and paralellization.
 
 
 Enhancements
@@ -17,31 +18,43 @@ Enhancements
 * :py:class:`~bifacial_radiance.RadianceObj.makeModule` fuction has additional input to specify the ``modulematerial``.
 * :py:class:`~bifacial_radiance.RadianceObj.makeModule` fuction can create framed modules using the argument ``frameParams``.  Dictionary inputs include `frame_material`, `frame_thickness`, `frame_z`, `frame_z` and `nsides_frame`.
 * :py:class:`~bifacial_radiance.RadianceObj.makeModule` fuction generates Omegas (frame offset elements) using the argument ``omegaParams``. Dictionary inputs include `omega_material`, `x_omega1`, `mod_overlap`, `y_omega`, `omega_thickness`, `x_omega3` and `inverted`.
-* :py:class:`~bifacial_radiance.RadianceObj.makeModule` input argument ``cellLevelModuleParams`` has a new field option `centerJB` to reflect the distance betwen both sides of cell arrays in a center-JB half-cell module. 
+* :py:class:`~bifacial_radiance.RadianceObj.makeModule` input argument ``cellModule`` has a new field option `centerJB` to reflect the distance betwen both sides of cell arrays in a center-JB half-cell module. 
 * :py:class:`~bifacial_radiance.RadianceObj.makeScene` has optional input parameter ``radname`` to give a custom name to the scene radfile.
 * New :py:class:`~bifacial_radiance.AnalysisObj.analyzeRow` function to analyze every module in the row.
 * :py:class:`~bifacial_radiance.RadianceObj.readWeatherFile` allows 'source=solargis' input option to read commercial weather files obtained from SolarGIS.com
+* For all weather files, night-time data (GHI <= 0) is removed when the MetObj is created.  This has always been the case for tracked simulations, but is also now true for fixed-tilt simulations as well.
 
 
 
 API Changes
 ~~~~~~~~~~~~
-* For all weather files, night-time data (GHI <= 0) is removed when the MetObj is created.  This has always been the case for tracked simulations, but is also now true for fixed-tilt simulations as well.
+* New :py:class:`~bifacial_radiance.ModuleObj` is used to generate module details. You can continue to use :py:class:`~bifacial_radiance.RadianceObj.makeModule`, or pass the same parameters directly to `ModuleObj` (:issue:`328`)(:pull:`338`)
+* :py:class:`~bifacial_radiance.RadianceObj.makeModule` now returns a ModuleObj instead of a dict. (:pull:`338`)
+* The dict previously returned by :py:class:`~bifacial_radiance.RadianceObj.makeModule` is stored in :py:class:`~bifacial_radiance.ModuleObj.data` (:pull:`338`)
+* :py:class:`~bifacial_radiance.SceneObj.showModule` is moved out of SceneObj and is now in :py:class:`~bifacial_radiance.ModuleObj.showModule` (:pull:`338`)
+* :py:class:`~bifacial_radiance.RadianceObj.makeModule` input argument ``cellLevelModuleParams`` is now named ``cellModule`` (:pull:`338`)
+* :py:class:`~bifacial_radiance.RadianceObj.makeModule` input dict ``tubeParams`` is now used for torquetube inputs. Dict keys include `diameter`, `material`, `tubetype` and `axisofrotation`. (:pull:`338`)
 * Due to now allowing multiple years and subhourly files, timestamped files (octfiles, skyfiles, objects, results files) now are of YYYY-MM-DD_HHMM format.
 * :py:class:`~bifacial_radiance.RadianceObj.readWeatherFile` has new arguments `source`, and `coerce_year`, but `daydate` option is removed. (:issue:`311`)
 * :py:class:`~bifacial_radiance.RadianceObj.readWeatherFile` arguments `starttime` and `endtime` accept datetime and pd.TimeIndex inputs, as well as strings in either 'MM_DD_HH' or 'MM_DD' or 'YYYY-MM-DD_HHMM' format.
 * Removed the standalone `RadianceObj.readEPW` function.  use :py:class:`~bifacial_radiance.RadianceObj.readWeatherFile` instead.
-* :py:class:`~bifacial_radiance.RadianceObj.genCumSky` new kwargument `temp_metdatafile` pointing to the auto-generated met data csv.
+* :py:class:`~bifacial_radiance.RadianceObj.genCumSky` new kwargument `gencumsky_metfile` pointing to the auto-generated met data csv file.
 * :py:class:`~bifacial_radiance.RadianceObj.set1axis` has new argument `fixed_tilt_azimuth`.  
 * :py:class:`~bifacial_radiance.RadianceObj.makeOct1axis` input argument ``singleindex`` must now take the format of 'YY_MM_DD_HH_MM'.  
 * :py:class:`~bifacial_radiance.AnalysisObj.moduleAnalysis` input arguments added: ``sensorsy_back``, ``sensorsy_front``, ``sensorsx_back``, ``sensorsx_front``, ''modscanfront'', ''modscanback'', ''relative''. 
 * :py:class:`~bifacial_radiance.AnalysisObj.analysis` function has boolean input ``RGB`` to enable spectral simulations
 
+
 Deprecations
 ~~~~~~~~~~~~~~
 * :py:class:`~bifacial_radiance.RadianceObj.gendaylit1axis` parameters ``startdate`` and ``enddate`` are now deprecated.  Downselect weather values when reading in weather file with :py:class:`~bifacial_radiance.RadianceObj.readWeatherFile`. 
 * :py:class:`~bifacial_radiance.RadianceObj.genCumSky` arguments `startdt` and `enddt` are now removed.  Limit time of simulations using :py:class:`~bifacial_radiance.RadianceObj.readWeatherFile` argument `starttime` and `endtime` instead.
 * :py:class:`~bifacial_radiance.RadianceObj.genCumSky` kwargument `epwfile` has been removed. EPW files are no longer passed directly to genCumSky.
+* :py:class:`~bifacial_radiance.SceneObj.makeScene` and :py:class:`~bifacial_radiance.SceneObj.makeScene1axis` kwarg `moduletype` is now `module` (:pull:`338`)
+* :py:class:`~bifacial_radiance.SceneObj.readModule` and :py:class:`~bifacial_radiance.SceneObj.showModule` are removed. (:pull:`338`)
+* :py:class:`~bifacial_radiance.RadianceObj.makeModule` inputs variables `diameter`, `material`, `torqueTubeMaterial`, `tubetype` and `axisofrotationTorqueTube` are deprecated.  use the ``tubeParams`` dict input instead. (:pull:`338`)
+* :py:class:`~bifacial_radiance.RadianceObj.makeModule` input kwargument ``cellLevelModuleParams`` is deprecated.  use ``cellModule`` instead (:pull:`338`)
+
 
 Bug fixes
 ~~~~~~~~~
@@ -49,6 +62,7 @@ Bug fixes
 * runtimeWarnings are ignored in set1axis. Fixes pvlib tracker #260
 * Fixed bug on input file load that would rewrite ''zgap'' parameter as ''ygap''. (:issue:'283')
 * Image folder was not correctly loading on install (:issue:'275')(:issue:'282')
+* Weather data with zero length after filtering returns a more informative error (:issue:'332')
 
 
 
diff --git a/docs/tutorials/1 - Introductory Example - Fixed Tilt simple setup.ipynb b/docs/tutorials/1 - Introductory Example - Fixed Tilt simple setup.ipynb
index 542217c3..8e7fb583 100644
--- a/docs/tutorials/1 - Introductory Example - Fixed Tilt simple setup.ipynb
+++ b/docs/tutorials/1 - Introductory Example - Fixed Tilt simple setup.ipynb
@@ -165,7 +165,9 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "Input albedo 0-1, albedo by wavelength [R, G, B], or ground material names:['black', 'litesoil', 'concrete', 'white_EPDM', 'beigeroof', 'beigeroof_lite', 'beigeroof_heavy', 'asphalt', 'greyroof', 'Metal_Aluminum_Anodized', 'Color_I11', 'M_0010_Snow', 'Metal_Grey']\n"
+      "\n",
+      "Input albedo 0-1, or string from ground.printGroundMaterials().\n",
+      "Alternatively, run setGround after readWeatherData()and setGround will read metdata.albedo if availalbe\n"
      ]
     }
    ],
@@ -185,7 +187,16 @@
    "cell_type": "code",
    "execution_count": 5,
    "metadata": {},
-   "outputs": [],
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Loading albedo, 1 value(s), 0.620 avg\n",
+      "1 nonzero albedo values.\n"
+     ]
+    }
+   ],
    "source": [
     "albedo = 0.62\n",
     "demo.setGround(albedo)"
@@ -216,7 +227,7 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "Getting weather file: USA_VA_Richmond.Intl.AP.724010_TMY.epw\n",
+      "Getting weather file: USA_VA_Richmond.724010_TMY2.epw\n",
       " ... OK!\n"
      ]
     }
@@ -244,7 +255,10 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "Saving file EPWs\\epw_temp.csv, # points: 8760\n"
+      "8760 line in WeatherFile. Assuming this is a standard hourly  WeatherFile for the year for purposes of saving Gencumulativesky temporal weather files in EPW folder.\n",
+      "Coercing year to 2001\n",
+      "Saving file EPWs\\metdata_temp.csv, # points: 8760\n",
+      "Calculating Sun position for Metdata that is right-labeled  with a delta of -30 mins. i.e. 12 is 11:30 sunpos\n"
      ]
     }
    ],
@@ -279,7 +293,11 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "message: There were 4255 sun up hours in this climate file\r\n",
+      "Loaded  EPWs\\metdata_temp.csv\n",
+      "message: Error!  Solar altitude is -7 < -6 degrees and Idh = 13 > 10 W/m^2 on day 76 !Ibn is 0.  Attempting to continue!\n",
+      "Error!  Solar altitude is -7 < -6 degrees and Idh = 11 > 10 W/m^2 on day 78 !Ibn is 0.  Attempting to continue!\n",
+      "Error!  Solar altitude is -6 < -6 degrees and Idh = 14 > 10 W/m^2 on day 81 !Ibn is 0.  Attempting to continue!\n",
+      "There were 4537 sun up hours in this climate file\n",
       "Total Ibh/Lbh: 0.000000\n"
      ]
     }
@@ -334,44 +352,19 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
+      "Setting default torquetube values\n",
       "\n",
       "Module Name: Prism_Solar_Bi60_landscape\n",
-      "REWRITING pre-existing module file. \n",
-      "Module Prism Solar Bi60 landscape successfully created\n"
+      "Module Prism_Solar_Bi60_landscape updated in module.json\n",
+      "Pre-existing .rad file objects\\Prism_Solar_Bi60_landscape.rad will be overwritten\n",
+      "{'x': 1.695, 'y': 0.984, 'z': 0.02, 'modulematerial': 'black', 'scenex': 1.705, 'sceney': 0.984, 'scenez': 0.15, 'numpanels': 1, 'bifi': 1, 'text': '! genbox black Prism_Solar_Bi60_landscape 1.695 0.984 0.02 | xform -t -0.8475 -0.492 0 -a 1 -t 0 0.984 0', 'modulefile': 'objects\\\\Prism_Solar_Bi60_landscape.rad', 'offsetfromaxis': 0, 'xgap': 0.01, 'ygap': 0.0, 'zgap': 0.1, 'cellModule': None, 'torquetube': {'bool': False, 'diameter': 0.1, 'tubetype': 'Round', 'material': 'Metal_Grey'}}\n"
      ]
-    },
-    {
-     "data": {
-      "text/plain": [
-       "{'x': 1.695,\n",
-       " 'y': 0.984,\n",
-       " 'z': 0.02,\n",
-       " 'scenex': 1.705,\n",
-       " 'sceney': 0.984,\n",
-       " 'scenez': 0.15,\n",
-       " 'numpanels': 1,\n",
-       " 'bifi': 1,\n",
-       " 'text': '! genbox black Prism_Solar_Bi60_landscape 1.695 0.984 0.02 | xform -t -0.8475 -0.492 0 -a 1 -t 0 0.984 0',\n",
-       " 'modulefile': 'objects\\\\Prism_Solar_Bi60_landscape.rad',\n",
-       " 'offsetfromaxis': 0,\n",
-       " 'xgap': 0.01,\n",
-       " 'ygap': 0.0,\n",
-       " 'zgap': 0.1,\n",
-       " 'cellModule': None,\n",
-       " 'torquetube': {'bool': False,\n",
-       "  'diameter': 0.1,\n",
-       "  'tubetype': 'Round',\n",
-       "  'material': 'Metal_Grey'}}"
-      ]
-     },
-     "execution_count": 9,
-     "metadata": {},
-     "output_type": "execute_result"
     }
    ],
    "source": [
     "module_type = 'Prism Solar Bi60 landscape' \n",
-    "demo.makeModule(name=module_type,x=1.695, y=0.984)\n"
+    "module = demo.makeModule(name=module_type,x=1.695, y=0.984)\n",
+    "print(module.data)"
    ]
   },
   {
@@ -390,7 +383,8 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "Available module names: ['Prism Solar Bi60', '2upTracker', 'test', 'Prism Solar Bi60 landscape', 'cellModule', 'Custom Tracker Module', 'PrismSolar', 'Taskar', 'Task13', 'square', 'round', 'hex', 'oct', 'Stock_Panel']\n"
+      "Setting default torquetube values\n",
+      "Available module names: ['2upTracker', 'Bi60', 'Custom Tracker Module', 'Prism Solar Bi60', 'Prism Solar Bi60 landscape', 'PrismSolar', 'Prism_Solar_Bi60_landscape', 'Sharp_NU-U235F2', 'cellModule', 'hex', 'oct', 'round', 'sensor', 'square', 'test']\n"
      ]
     }
    ],
@@ -429,7 +423,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "To make the scene we have to create a Scene Object through the method makeScene. This method will create a .rad file in the objects folder, with the parameters specified in sceneDict and the module created above."
+    "To make the scene we have to create a Scene Object through the method makeScene. This method will create a .rad file in the objects folder, with the parameters specified in sceneDict and the module created above.  You can alternatively pass a string with the name of the `moduletype`."
    ]
   },
   {
@@ -438,7 +432,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "scene = demo.makeScene(module_type,sceneDict)"
+    "scene = demo.makeScene(module,sceneDict)"
    ]
   },
   {
@@ -492,7 +486,7 @@
       "text/plain": [
        "['materials\\\\ground.rad',\n",
        " 'skies\\\\cumulative.rad',\n",
-       " 'objects\\\\Prism_Solar_Bi60_landscape_0.2_3_10_20x7_origin0,0.rad']"
+       " 'objects\\\\Prism_Solar_Bi60_landscape_C_0.20000_rtr_3.00000_tilt_10.00000_20modsx7rows_origin0,0.rad']"
       ]
      },
      "execution_count": 14,
@@ -635,9 +629,9 @@
        "      <td>0.216102</td>\n",
        "      <td>a9.3.a0.Prism_Solar_Bi60_landscape.6457</td>\n",
        "      <td>a9.3.a0.Prism_Solar_Bi60_landscape.2310</td>\n",
-       "      <td>1518193.0</td>\n",
-       "      <td>369505.0</td>\n",
-       "      <td>0.243385</td>\n",
+       "      <td>1623786.0</td>\n",
+       "      <td>441060.2</td>\n",
+       "      <td>0.271625</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>1</th>\n",
@@ -647,9 +641,9 @@
        "      <td>0.233189</td>\n",
        "      <td>a9.3.a0.Prism_Solar_Bi60_landscape.6457</td>\n",
        "      <td>a9.3.a0.Prism_Solar_Bi60_landscape.2310</td>\n",
-       "      <td>1518383.0</td>\n",
-       "      <td>307981.9</td>\n",
-       "      <td>0.202835</td>\n",
+       "      <td>1623813.0</td>\n",
+       "      <td>338871.1</td>\n",
+       "      <td>0.208689</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>2</th>\n",
@@ -659,9 +653,9 @@
        "      <td>0.250276</td>\n",
        "      <td>a9.3.a0.Prism_Solar_Bi60_landscape.6457</td>\n",
        "      <td>a9.3.a0.Prism_Solar_Bi60_landscape.2310</td>\n",
-       "      <td>1518554.0</td>\n",
-       "      <td>239488.1</td>\n",
-       "      <td>0.157708</td>\n",
+       "      <td>1623818.0</td>\n",
+       "      <td>264072.5</td>\n",
+       "      <td>0.162624</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>3</th>\n",
@@ -671,9 +665,9 @@
        "      <td>0.267363</td>\n",
        "      <td>a9.3.a0.Prism_Solar_Bi60_landscape.6457</td>\n",
        "      <td>a9.3.a0.Prism_Solar_Bi60_landscape.2310</td>\n",
-       "      <td>1518725.0</td>\n",
-       "      <td>199386.5</td>\n",
-       "      <td>0.131285</td>\n",
+       "      <td>1623822.0</td>\n",
+       "      <td>221105.9</td>\n",
+       "      <td>0.136164</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>4</th>\n",
@@ -683,9 +677,9 @@
        "      <td>0.284450</td>\n",
        "      <td>a9.3.a0.Prism_Solar_Bi60_landscape.6457</td>\n",
        "      <td>a9.3.a0.Prism_Solar_Bi60_landscape.2310</td>\n",
-       "      <td>1518896.0</td>\n",
-       "      <td>184967.1</td>\n",
-       "      <td>0.121777</td>\n",
+       "      <td>1623827.0</td>\n",
+       "      <td>205510.9</td>\n",
+       "      <td>0.126560</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>5</th>\n",
@@ -695,9 +689,9 @@
        "      <td>0.301537</td>\n",
        "      <td>a9.3.a0.Prism_Solar_Bi60_landscape.6457</td>\n",
        "      <td>a9.3.a0.Prism_Solar_Bi60_landscape.2310</td>\n",
-       "      <td>1519066.0</td>\n",
-       "      <td>191999.1</td>\n",
-       "      <td>0.126393</td>\n",
+       "      <td>1623832.0</td>\n",
+       "      <td>210197.5</td>\n",
+       "      <td>0.129445</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>6</th>\n",
@@ -707,9 +701,9 @@
        "      <td>0.318624</td>\n",
        "      <td>a9.3.a0.Prism_Solar_Bi60_landscape.6457</td>\n",
        "      <td>a9.3.a0.Prism_Solar_Bi60_landscape.2310</td>\n",
-       "      <td>1519237.0</td>\n",
-       "      <td>215212.1</td>\n",
-       "      <td>0.141658</td>\n",
+       "      <td>1623837.0</td>\n",
+       "      <td>232091.1</td>\n",
+       "      <td>0.142928</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>7</th>\n",
@@ -719,9 +713,9 @@
        "      <td>0.335711</td>\n",
        "      <td>a9.3.a0.Prism_Solar_Bi60_landscape.6457</td>\n",
        "      <td>a9.3.a0.Prism_Solar_Bi60_landscape.2310</td>\n",
-       "      <td>1515019.0</td>\n",
-       "      <td>258336.9</td>\n",
-       "      <td>0.170517</td>\n",
+       "      <td>1623841.0</td>\n",
+       "      <td>276560.5</td>\n",
+       "      <td>0.170313</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>8</th>\n",
@@ -731,9 +725,9 @@
        "      <td>0.352798</td>\n",
        "      <td>a9.3.a0.Prism_Solar_Bi60_landscape.6457</td>\n",
        "      <td>a9.3.a0.Prism_Solar_Bi60_landscape.2310</td>\n",
-       "      <td>1514893.0</td>\n",
-       "      <td>311205.9</td>\n",
-       "      <td>0.205431</td>\n",
+       "      <td>1638132.0</td>\n",
+       "      <td>334609.2</td>\n",
+       "      <td>0.204263</td>\n",
        "    </tr>\n",
        "  </tbody>\n",
        "</table>\n",
@@ -763,26 +757,26 @@
        "8  a9.3.a0.Prism_Solar_Bi60_landscape.6457   \n",
        "\n",
        "                                   rearMat   Wm2Front   Wm2Back  \\\n",
-       "0  a9.3.a0.Prism_Solar_Bi60_landscape.2310  1518193.0  369505.0   \n",
-       "1  a9.3.a0.Prism_Solar_Bi60_landscape.2310  1518383.0  307981.9   \n",
-       "2  a9.3.a0.Prism_Solar_Bi60_landscape.2310  1518554.0  239488.1   \n",
-       "3  a9.3.a0.Prism_Solar_Bi60_landscape.2310  1518725.0  199386.5   \n",
-       "4  a9.3.a0.Prism_Solar_Bi60_landscape.2310  1518896.0  184967.1   \n",
-       "5  a9.3.a0.Prism_Solar_Bi60_landscape.2310  1519066.0  191999.1   \n",
-       "6  a9.3.a0.Prism_Solar_Bi60_landscape.2310  1519237.0  215212.1   \n",
-       "7  a9.3.a0.Prism_Solar_Bi60_landscape.2310  1515019.0  258336.9   \n",
-       "8  a9.3.a0.Prism_Solar_Bi60_landscape.2310  1514893.0  311205.9   \n",
+       "0  a9.3.a0.Prism_Solar_Bi60_landscape.2310  1623786.0  441060.2   \n",
+       "1  a9.3.a0.Prism_Solar_Bi60_landscape.2310  1623813.0  338871.1   \n",
+       "2  a9.3.a0.Prism_Solar_Bi60_landscape.2310  1623818.0  264072.5   \n",
+       "3  a9.3.a0.Prism_Solar_Bi60_landscape.2310  1623822.0  221105.9   \n",
+       "4  a9.3.a0.Prism_Solar_Bi60_landscape.2310  1623827.0  205510.9   \n",
+       "5  a9.3.a0.Prism_Solar_Bi60_landscape.2310  1623832.0  210197.5   \n",
+       "6  a9.3.a0.Prism_Solar_Bi60_landscape.2310  1623837.0  232091.1   \n",
+       "7  a9.3.a0.Prism_Solar_Bi60_landscape.2310  1623841.0  276560.5   \n",
+       "8  a9.3.a0.Prism_Solar_Bi60_landscape.2310  1638132.0  334609.2   \n",
        "\n",
        "   Back/FrontRatio  \n",
-       "0         0.243385  \n",
-       "1         0.202835  \n",
-       "2         0.157708  \n",
-       "3         0.131285  \n",
-       "4         0.121777  \n",
-       "5         0.126393  \n",
-       "6         0.141658  \n",
-       "7         0.170517  \n",
-       "8         0.205431  "
+       "0         0.271625  \n",
+       "1         0.208689  \n",
+       "2         0.162624  \n",
+       "3         0.136164  \n",
+       "4         0.126560  \n",
+       "5         0.129445  \n",
+       "6         0.142928  \n",
+       "7         0.170313  \n",
+       "8         0.204263  "
       ]
      },
      "execution_count": 18,
@@ -814,7 +808,7 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "Annual bifacial ratio: 0.15 \n"
+      "Annual bifacial ratio: 0.16 \n"
      ]
     }
    ],
@@ -907,7 +901,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.7.3"
+   "version": "3.8.8"
   }
  },
  "nbformat": 4,
diff --git a/docs/tutorials/1 - Introductory Example - Fixed Tilt simple setup.py b/docs/tutorials/1 - Introductory Example - Fixed Tilt simple setup.py
index 31bc3363..745b74bc 100644
--- a/docs/tutorials/1 - Introductory Example - Fixed Tilt simple setup.py
+++ b/docs/tutorials/1 - Introductory Example - Fixed Tilt simple setup.py
@@ -165,7 +165,8 @@
 
 
 module_type = 'Prism Solar Bi60 landscape' 
-demo.makeModule(name=module_type,x=1.695, y=0.984)
+module = demo.makeModule(name=module_type,x=1.695, y=0.984)
+print(module.data)
 
 
 # In case you want to use a pre-defined module or a module you've created previously, they are stored in a JSON format in data/module.json, and the options available can be called with printModules:
@@ -191,12 +192,12 @@
 sceneDict = {'tilt':10,'pitch':3,'clearance_height':0.2,'azimuth':180, 'nMods': 20, 'nRows': 7} 
 
 
-# To make the scene we have to create a Scene Object through the method makeScene. This method will create a .rad file in the objects folder, with the parameters specified in sceneDict and the module created above.
+# To make the scene we have to create a Scene Object through the method makeScene. This method will create a .rad file in the objects folder, with the parameters specified in sceneDict and the module created above.  You can alternatively pass a string with the name of the `moduletype`.
 
 # In[12]:
 
 
-scene = demo.makeScene(module_type,sceneDict)
+scene = demo.makeScene(module,sceneDict)
 
 
 # <a id='step8'></a>
diff --git a/docs/tutorials/2 - Introductory Example - Single Axis Tracking with cumulative Sky.ipynb b/docs/tutorials/2 - Introductory Example - Single Axis Tracking with cumulative Sky.ipynb
index 920a85ff..d00cecb2 100644
--- a/docs/tutorials/2 - Introductory Example - Single Axis Tracking with cumulative Sky.ipynb
+++ b/docs/tutorials/2 - Introductory Example - Single Axis Tracking with cumulative Sky.ipynb
@@ -363,7 +363,7 @@
     }
    ],
    "source": [
-    "trackerdict = demo.genCumSky1axis(trackerdict = trackerdict)"
+    "trackerdict = demo.genCumSky1axis()"
    ]
   },
   {
@@ -442,19 +442,16 @@
     "y = 1.7    # meters\n",
     "moduletype = 'Custom Tracker Module'\n",
     "torquetube = True\n",
-    "tubetype = 'round'\n",
-    "diameter = 0.1 # diameter of the torque tube\n",
     "numpanels = 2\n",
-    "axisofrotationTorqueTube = True\n",
     "zgap = 0.05\n",
     "ygap = 0.10\n",
     "xgap = 0.02\n",
-    "material = 'Metal_Grey'\n",
     "\n",
-    "demo.makeModule(name = moduletype, x = x, y = y, \n",
-    "                torquetube = torquetube, tubetype = tubetype, material = material,\n",
-    "                diameter = diameter, xgap = xgap, ygap = ygap, zgap = zgap, \n",
-    "                numpanels = numpanels, axisofrotationTorqueTube = axisofrotationTorqueTube)"
+    "tubeDict = {'diameter':0.1, 'material':'Metal_Grey', 'tubetype':'round', 'axisofrotation':True}\n",
+    "module = demo.makeModule(name=moduletype, x=x, y=y, \n",
+    "                torquetube=torquetube, xgap=xgap, ygap=ygap, zgap=zgap, \n",
+    "                numpanels=numpanels, tubeParams=tubeDict)\n",
+    "print(module.data)"
    ]
   },
   {
@@ -508,7 +505,7 @@
     }
    ],
    "source": [
-    "trackerdict = demo.makeScene1axis(trackerdict = trackerdict, moduletype = moduletype, sceneDict = sceneDict) "
+    "trackerdict = demo.makeScene1axis(trackerdict = trackerdict, module = module, sceneDict = sceneDict) "
    ]
   },
   {
@@ -2619,7 +2616,7 @@
     "demo.set1axis(limit_angle = limit_angle, backtrack = backtrack, gcr = gcr, cumulativesky = cumulativesky)\n",
     "demo.genCumSky1axis()\n",
     "sceneDict = {'gcr': gcr,'height':hub_height, 'nMods': nMods, 'nRows': nRows}  # orientation deprecated on v.0.2.4.\n",
-    "demo.makeScene1axis(moduletype=moduletype,sceneDict = sceneDict)\n",
+    "demo.makeScene1axis(module=moduletype,sceneDict = sceneDict)\n",
     "demo.makeOct1axis()\n",
     "demo.analysis1axis(modWanted = modWanted, rowWanted = rowWanted)\n"
    ]
@@ -2642,7 +2639,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.7.3"
+   "version": "3.8.8"
   }
  },
  "nbformat": 4,
diff --git a/docs/tutorials/2 - Introductory Example - Single Axis Tracking with cumulative Sky.py b/docs/tutorials/2 - Introductory Example - Single Axis Tracking with cumulative Sky.py
index 6a49c451..51a34da0 100644
--- a/docs/tutorials/2 - Introductory Example - Single Axis Tracking with cumulative Sky.py
+++ b/docs/tutorials/2 - Introductory Example - Single Axis Tracking with cumulative Sky.py
@@ -164,7 +164,7 @@
 # In[9]:
 
 
-trackerdict = demo.genCumSky1axis(trackerdict = trackerdict)
+trackerdict = demo.genCumSky1axis()
 
 
 # This is how one of the cumulative sky .cal files associated with each .rad file generated look like: 
@@ -188,19 +188,16 @@
 y = 1.7    # meters
 moduletype = 'Custom Tracker Module'
 torquetube = True
-tubetype = 'round'
-diameter = 0.1 # diameter of the torque tube
 numpanels = 2
-axisofrotationTorqueTube = True
 zgap = 0.05
 ygap = 0.10
 xgap = 0.02
-material = 'Metal_Grey'
 
-demo.makeModule(name = moduletype, x = x, y = y, 
-                torquetube = torquetube, tubetype = tubetype, material = material,
-                diameter = diameter, xgap = xgap, ygap = ygap, zgap = zgap, 
-                numpanels = numpanels, axisofrotationTorqueTube = axisofrotationTorqueTube)
+tubeDict = {'diameter':0.1, 'material':'Metal_Grey', 'tubetype':'round', 'axisofrotation':True}
+module = demo.makeModule(name=moduletype, x=x, y=y, 
+                torquetube=torquetube, xgap=xgap, ygap=ygap, zgap=zgap, 
+                numpanels=numpanels, tubeParams=tubeDict)
+print(module.data)
 
 
 # <a id='step8'></a>
@@ -221,7 +218,7 @@
 # In[12]:
 
 
-trackerdict = demo.makeScene1axis(trackerdict = trackerdict, moduletype = moduletype, sceneDict = sceneDict) 
+trackerdict = demo.makeScene1axis(trackerdict = trackerdict, module = module, sceneDict = sceneDict) 
 
 
 # <a id='step9'></a>
@@ -346,7 +343,7 @@
 demo.set1axis(limit_angle = limit_angle, backtrack = backtrack, gcr = gcr, cumulativesky = cumulativesky)
 demo.genCumSky1axis()
 sceneDict = {'gcr': gcr,'height':hub_height, 'nMods': nMods, 'nRows': nRows}  # orientation deprecated on v.0.2.4.
-demo.makeScene1axis(moduletype=moduletype,sceneDict = sceneDict)
+demo.makeScene1axis(module=moduletype,sceneDict = sceneDict)
 demo.makeOct1axis()
 demo.analysis1axis(modWanted = modWanted, rowWanted = rowWanted)
 


Test patch:
diff --git a/tests/test_bifacial_radiance.py b/tests/test_bifacial_radiance.py
index 34b978d6..eb4eb8f9 100644
--- a/tests/test_bifacial_radiance.py
+++ b/tests/test_bifacial_radiance.py
@@ -66,8 +66,8 @@ def test_RadianceObj_fixed_tilt_end_to_end():
     demo.gendaylit(timeindex=timeindex, metdata=metdata)  # Noon, June 17th
     # create a scene using panels in landscape at 10 deg tilt, 1.5m pitch. 0.2 m ground clearance
     sceneDict = {'tilt':10,'pitch':1.5,'clearance_height':0.2, 'nMods':10, 'nRows':3}  
-    demo.makeModule(name='test',y=0.95,x=1.59, xgap=0)
-    scene = demo.makeScene('test',sceneDict) #makeScene creates a .rad file with 20 modules per row, 7 rows.
+    module = demo.makeModule(name='test',y=0.95,x=1.59, xgap=0)
+    scene = demo.makeScene(module, sceneDict) #makeScene creates a .rad file with 20 modules per row, 7 rows.
     octfile = demo.makeOct(demo.getfilelist())  # makeOct combines all of the ground, sky and object files into a .oct file.
     analysis = bifacial_radiance.AnalysisObj(octfile, demo.name)  # return an analysis object including the scan dimensions for back irradiance
     (frontscan,backscan) = analysis.moduleAnalysis(scene)
@@ -177,29 +177,29 @@ def test_1axis_gencumSky():
     demo = bifacial_radiance.RadianceObj(name)  # Create a RadianceObj 'object'
     demo.setGround(albedo) # input albedo number or material name like 'concrete'.  To see options, run this without any input.
     demo.readWeatherFile(weatherFile=MET_FILENAME, starttime='01_01_01', endtime = '01_01_23', coerce_year=2001) # read in the EPW weather data from above
-    moduleDict=demo.makeModule(name='test',x=0.984,y=1.95, numpanels = 2, ygap = 0.1)
-    pitch= np.round(moduleDict['sceney'] / gcr,3)
+    module=demo.makeModule(name='test',x=0.984,y=1.95, numpanels = 2, ygap = 0.1)
+    pitch= np.round(module.data['sceney'] / gcr,3)
     trackerdict = demo.set1axis(cumulativesky = True, gcr=gcr)
     demo.genCumSky1axis()
     assert trackerdict[-45.0]['skyfile'][0:5] == 'skies' #  # Having trouble with the \ or //    'skies\\1axis_-45.0.rad'
     sceneDict = {'gcr': gcr,'hub_height':hub_height, 'clearance_height':hub_height, 'nMods':10, 'nRows':3}  
-    trackerdict = demo.makeScene1axis(sceneDict=sceneDict, moduletype = 'test')
+    trackerdict = demo.makeScene1axis(sceneDict=sceneDict, module = 'test')
     # Removing all of this other tests for hub_height and height since it's ben identified that
     # a new module to handle hub_height and height in sceneDict needs to be implemented
     # instead of checking inside of makeScene, makeSceneNxR, and makeScene1axis
     assert trackerdict[-5.0]['radfile'][0:7] == 'objects' # 'objects\\1axis-5.0_1.825_11.42_5.0_10x3_origin0,0.rad'
 
     sceneDict = {'pitch': pitch,'clearance_height':hub_height, 'nMods':10, 'nRows':3}  # testing height filter too
-    trackerdict = demo.makeScene1axis(sceneDict=sceneDict, moduletype = 'test')
+    trackerdict = demo.makeScene1axis(sceneDict=sceneDict, module = 'test')
 #    assert trackerdict[-5.0]['radfile'] == 'objects\\1axis-5.0_1.825_11.42_5.0_10x3_origin0,0.rad'
     sceneDict = {'pitch': pitch,'height':hub_height, 'nMods':10, 'nRows':3}  # testing height filter too
-    trackerdict = demo.makeScene1axis(sceneDict=sceneDict, moduletype = 'test')
+    trackerdict = demo.makeScene1axis(sceneDict=sceneDict, module = 'test')
 #    assert trackerdict[-5.0]['radfile'] == 'objects\\1axis-5.0_1.825_11.42_5.0_10x3_origin0,0.rad'
     sceneDict = {'pitch': pitch,'height':hub_height, 'clearance_height':hub_height, 'nMods':10, 'nRows':3}  # testing height filter too
-    trackerdict = demo.makeScene1axis(sceneDict=sceneDict, moduletype = 'test')
+    trackerdict = demo.makeScene1axis(sceneDict=sceneDict, module = 'test')
 #    assert trackerdict[-5.0]['radfile'] == 'objects\\1axis-5.0_1.825_11.42_5.0_10x3_origin0,0.rad'
     sceneDict = {'pitch': pitch,'height':hub_height, 'hub_height':hub_height, 'nMods':10, 'nRows':3}  # testing height filter too
-    trackerdict = demo.makeScene1axis(sceneDict=sceneDict, moduletype = 'test')
+    trackerdict = demo.makeScene1axis(sceneDict=sceneDict, module = 'test')
     demo.exportTrackerDict(trackerdict, savefile = 'results\exportedTrackerDict')
     assert trackerdict[-5.0]['radfile'][0:7] == 'objects' 
     #assert trackerdict[-5.0]['radfile'] == 'objects\\1axis-5.0_1.825_11.42_5.0_10x3_origin0,0.rad'
@@ -225,7 +225,7 @@ def test_SceneObj_makeSceneNxR_lowtilt():
     #scene = bifacial_radiance.SceneObj(moduletype = name)
     #scene._makeSceneNxR(tilt=10,height=0.2,pitch=1.5)
     sceneDict={'tilt':10, 'clearance_height':0.2, 'pitch':1.5}
-    scene = demo.makeScene(moduletype='test', sceneDict=sceneDict)
+    scene = demo.makeScene(module='test', sceneDict=sceneDict)
     analysis = bifacial_radiance.AnalysisObj()
     (frontscan,backscan) = analysis.moduleAnalysis(scene)
     
@@ -253,7 +253,7 @@ def test_SceneObj_makeSceneNxR_hightilt():
     #scene = bifacial_radiance.SceneObj(moduletype = name)
     #scene._makeSceneNxR(tilt=65,height=0.2,pitch=1.5,azimuth=89)
     sceneDict={'tilt':65, 'clearance_height':0.2, 'pitch':1.5, 'azimuth':89}
-    scene = demo.makeScene(moduletype='test', sceneDict=sceneDict)
+    scene = demo.makeScene(module='test', sceneDict=sceneDict)
     analysis = bifacial_radiance.AnalysisObj()
     (frontscan,backscan) = analysis.moduleAnalysis(scene)
     
@@ -305,25 +305,25 @@ def test_CellLevelModule():
                    'xcellgap':0.02, 'ycellgap':0.02}
     #moduleDict = demo.makeModule(name=name, cellLevelModule=True, xcell=0.156, rewriteModulefile=True, ycell=0.156,  
     #                             numcellsx=6, numcellsy=10, xcellgap=0.02, ycellgap=0.02)
-    moduleDict = demo.makeModule(name='test', rewriteModulefile=True, cellLevelModuleParams = cellParams)
-    assert moduleDict['x'] == 1.036
-    assert moduleDict['y'] == 1.74
-    assert moduleDict['scenex'] == 1.046
-    assert moduleDict['sceney'] == 1.74
-    assert moduleDict['text'] == '! genbox black cellPVmodule 0.156 0.156 0.02 | xform -t -0.44 -0.87 0 -a 6 -t 0.176 0 0 -a 10 -t 0 0.176 0 -a 1 -t 0 1.74 0'
+    module = demo.makeModule(name='test', rewriteModulefile=True, cellModule=cellParams)
+    assert module.data['x'] == 1.036
+    assert module.data['y'] == 1.74
+    assert module.data['scenex'] == 1.046
+    assert module.data['sceney'] == 1.74
+    assert module.data['text'] == '! genbox black cellPVmodule 0.156 0.156 0.02 | xform -t -0.44 -0.87 0 -a 6 -t 0.176 0 0 -a 10 -t 0 0.176 0 -a 1 -t 0 1.74 0'
     
 def test_TorqueTubes_Module():
     name = "_test_TorqueTubes"
     demo = bifacial_radiance.RadianceObj(name)  # Create a RadianceObj 'object'
-    moduleDict = demo.makeModule(name='square', y=0.95,x=1.59, rewriteModulefile=True, torquetube=True, tubetype='square')
-    assert moduleDict['x'] == 1.59
-    assert moduleDict['text'] == '! genbox black square 1.59 0.95 0.02 | xform -t -0.795 -0.475 0 -a 1 -t 0 0.95 0\r\n! genbox Metal_Grey tube1 1.6 0.1 0.1 | xform -t -0.8 -0.05 -0.2'
-    moduleDict = demo.makeModule(name='round', y=0.95,x=1.59, rewriteModulefile=True, torquetube=True, tubetype='round')
-    assert moduleDict['text'][0:30] == '! genbox black round 1.59 0.95'
-    moduleDict = demo.makeModule(name='hex', y=0.95,x=1.59, rewriteModulefile=True, torquetube=True, tubetype='hex')
-    assert moduleDict['text'][0:30] == '! genbox black hex 1.59 0.95 0'
-    moduleDict = demo.makeModule(name='oct', y=0.95,x=1.59, rewriteModulefile=True, torquetube=True, tubetype='oct')
-    assert moduleDict['text'][0:30] == '! genbox black oct 1.59 0.95 0'
+    module = demo.makeModule(name='square', y=0.95,x=1.59, rewriteModulefile=True, torquetube=True, tubeParams={'tubetype':'square', 'axisofrotation':False})
+    assert module.data['x'] == 1.59
+    assert module.data['text'] == '! genbox black square 1.59 0.95 0.02 | xform -t -0.795 -0.475 0 -a 1 -t 0 0.95 0\r\n! genbox Metal_Grey tube1 1.6 0.1 0.1 | xform -t -0.8 -0.05 -0.2'
+    module = demo.makeModule(name='round', y=0.95,x=1.59, rewriteModulefile=True, torquetube=True, tubeParams={'tubetype':'round', 'axisofrotation':False})
+    assert module.data['text'][0:30] == '! genbox black round 1.59 0.95'
+    module = demo.makeModule(name='hex', y=0.95,x=1.59, rewriteModulefile=True, torquetube=True, tubeParams={'tubetype':'hex', 'axisofrotation':False})
+    assert module.data['text'][0:30] == '! genbox black hex 1.59 0.95 0'
+    module = demo.makeModule(name='oct', y=0.95,x=1.59, rewriteModulefile=True, torquetube=True, tubeParams={'tubetype':'oct', 'axisofrotation':False})
+    assert module.data['text'][0:30] == '! genbox black oct 1.59 0.95 0'
 
 def test_gendaylit2manual():
     name = "_test_gendaylit2manual"
@@ -435,9 +435,9 @@ def test_moduleFrameandOmegas():
             diam = 0.0
             
         demo.makeModule(name='test',x=2, y=1, torquetube = torquetube, 
-                        diameter = diam, zgap = zgap, 
-                        frameParams=frameParams, omegaParams=omegaParams,
-                        axisofrotationTorqueTube=axisofrotationTorqueTube)
+                        tubeParams={'diameter':diam,'axisofrotation':axisofrotationTorqueTube},
+                        zgap = zgap, frameParams=frameParams, omegaParams=omegaParams
+                        )
         
         scene = demo.makeScene('test',sceneDict)
         octfile = demo.makeOct()
@@ -612,8 +612,8 @@ def test_readWeatherFile_subhourly():
     demo = bifacial_radiance.RadianceObj(name)
     metdata = demo.readWeatherFile(weatherFile=MET_FILENAME4,
                                     source='solargis', tz_convert_val= 2 )
-    assert len(demo.temp_metdatafile) == 2
-    gencumsky_file2 = pd.read_csv(demo.temp_metdatafile[1], delimiter=' ', 
+    assert len(demo.gencumsky_metfile) == 2
+    gencumsky_file2 = pd.read_csv(demo.gencumsky_metfile[1], delimiter=' ', 
                                     header=None)
     assert gencumsky_file2.__len__() == 8760
     assert gencumsky_file2.iloc[12,0] == pytest.approx(284.0, abs=0.1)