RRadout_01.py
RRadout_01.py
—
Python Source,
18 KB (19150 bytes)
File contents
### Revit to Radiance scene file export module ### 2016 Georg Mischler ### ### Version 0.1 - proof of concept import sys import nt as os import clr import System clr.AddReferenceByName("RevitAPI.dll"); clr.AddReferenceByName("RevitAPIUI.dll"); from Autodesk.Revit import * from Autodesk.Revit.UI import * from Autodesk.Revit.UI.Macros import * from Autodesk.Revit.DB import * from Autodesk.Revit.DB.Architecture import TopographySurface from Autodesk.Revit.UI.Selection import * from System.Collections.Generic import * from System.Collections import * from System import * from math import * class ThisApplication (ApplicationEntryPoint): ### CONFIGURATION ################################################################# # # Metric or imperial? # True -> Export unit meters # False -> Export unit inches metric = True # # # Output file normal Geometry fname = os.environ['USERPROFILE'] + r'\Desktop\revout.rad' # # Output file Topography Geometry topo_fname = os.environ['USERPROFILE'] + r'\Desktop\revout_topo.rad' # # ### END CONFIGURATION ############################################################# #region Revit Macros generated code def FinishInitialization(self): ApplicationEntryPoint.FinishInitialization(self) self.InternalStartup() def OnShutdown(self): self.InternalShutdown() ApplicationEntryPoint.OnShutdown(self) def InternalStartup(self): self.Startup() def InternalShutdown(self): self.Shutdown() #endregion def Startup(self): pass def Shutdown(self): pass def ExportTopoToRadiance(self): if (self.ActiveUIDocument != None): self.__ExportTopoToRadianceImplementation(self.Application, self.ActiveUIDocument.Document) else: self.__ExportTopoToRadianceImplementation(self.Application, None) def __ExportTopoToRadianceImplementation(self, app, doc): context = _MyTopoExportContext(doc, self.metric, self.topo_fname) exporter = CustomExporter(doc, context) curview = doc.ActiveView exporter.Export(curview) def ExportToRadiance(self): if (self.ActiveUIDocument != None): self.__ExportToRadianceImplementation(self.Application, self.ActiveUIDocument.Document) else: self.__ExportToRadianceImplementation(self.Application, None) def __ExportToRadianceImplementation(self, app, doc): context = _MyExportContext(doc, self.metric, self.fname) exporter = CustomExporter(doc, context) curview = doc.ActiveView exporter.Export(curview) # Transaction mode def GetTransactionMode(self): return Attributes.TransactionMode.Manual # Addin Id def GetAddInId(self): return '98E9898F-B62F-415A-9AA3-1F705B44EAC0' class _RadianceBaseExportContext(IExportContext): def _make_polygon(self, name, pts): sl = ['{name} polygon {name}_{n:06d}'.format(name=name, n=self.cur_n)] self.cur_n += 1 sl.append('0 0 {num}'.format(num=len(pts)*3)) for pt in pts: sl.append(' {pt[0]:.8f} {pt[1]:.8f} {pt[2]:.8f}'.format(pt=pt)) sl.append('') return '\n'.join(sl) def _make_ring(self, name, cen, norm, r0, r1): s = '''{name} ring {name}_{n:06d} 0 0 8 {cen[0]:.8f} {cen[1]:.8f} {cen[2]:.8f} {norm[0]:.8f} {norm[1]:.8f} {norm[2]:.8f} {r0:.8f} {r1:.8f} '''.format(name=name, n=self.cur_n, cen=cen, norm=norm, r0=r0, r1=r1) self.cur_n += 1 return s def _make_cylinder(self, name, start, end, rad): s = '''{name} cylinder {name}_{n:06d} 0 0 7 {start[0]:.8f} {start[1]:.8f} {start[2]:.8f} {end[0]:.8f} {end[1]:.8f} {end[2]:.8f} {rad:.8f} '''.format(name=name, n=self.cur_n, start=start, end=end, rad=rad) self.cur_n += 1 return s def _norm_name(self, s): return s.replace(' ', '_') class _MyTopoExportContext(_RadianceBaseExportContext): def __init__(self, doc, metric, fname): self.cur_n = 0 self.cur_mat = '' self.doc = doc self.fname = fname self.cancelled = False if metric: self.xforms = [Transform.Identity.ScaleBasis(0.3048)] else: self.xforms = [Transform.Identity] def Finish(self): self.f.write('\n# End of Revit Topography Mesh Export\n') self.f.close() def Start(self): self.f = open(self.fname, 'w') self.f.write('# Radiance Scene File\n') self.f.write('# Revit Topography Mesh Export\n') self.f.write('# Original File: %s\n\n' % self.doc.PathName) return True def IsCanceled(self): if self.cancelled: self.f.write('\n# Export cancelled\n\n') return self.cancelled def OnDaylightPortal(self, node): pass def OnElementBegin(self, id): el = self.doc.GetElement(id) if isinstance(el, TopographySurface): matpar = el.Parameter['Material'] if matpar and matpar.HasValue: matid = matpar.AsElementId() matel = self.doc.GetElement(matid) if matel: self.cur_mat = matel.Name elif el.Category and el.Category.Material: # assume by category self.cur_mat = el.Category.Material.Name return RenderNodeAction.Proceed return RenderNodeAction.Skip def OnElementEnd(self, node): self.cur_mat = '' def OnFaceBegin(self, node): return RenderNodeAction.Skip def OnFaceEnd(self, node): pass def OnInstanceBegin(self, node): return RenderNodeAction.Skip def OnInstanceEnd(self, node): pass def OnLight(self, node): pass def OnLinkBegin(self, node): return RenderNodeAction.Skip def OnLinkEnd(self, node): pass def OnMaterial(self, node): pass def OnPolymesh(self, node): xf = self.xforms[-1] if self.cur_mat: name = self._norm_name('mesh_topography+++' + self.cur_mat) else: name = 'mesh_topography' fs = node.GetFacets() points = node.GetPoints() self.f.write('\n# Polymesh Topography with %d facets\n' % node.NumberOfFacets) for facet in fs: # XXX xform! pl = (xf.OfPoint(points[facet.V1]), xf.OfPoint(points[facet.V2]), xf.OfPoint(points[facet.V3])) self.f.write(self._make_polygon(name, pl)) def OnRPC(self, node): pass def OnViewBegin(self, node): return RenderNodeAction.Proceed def OnViewEnd(self, node): pass class _MyExportContext(_RadianceBaseExportContext): def __init__(self, doc, metric, fname): self.fname = fname if metric: self.xforms = [Transform.Identity.ScaleBasis(0.3048)] else: self.xforms = [Transform.Identity] self.cylhalfs = [] self.RND = 10 self.cancelled = False self.doc = doc self.cur_level = 'No-Level' self.cur_family = [''] self.cur_type = '' self.cur_n = 0 def Finish(self): self.f.write('\n# End of Revit Geometry Export\n') self.f.close() def Start(self): self.f.write('# Radiance Scene File\n') self.f.write('# Revit Geometry Export\n') self.f.write('# Original File: %s\n\n' % self.doc.PathName) self.f = open(self.fname, 'w') return True def IsCanceled(self): if self.cancelled: self.f.write('\n# Export cancelled\n\n') return self.cancelled def OnDaylightPortal(self, node): pass def _get_element_level(self, el): level = self.doc.GetElement(el.LevelId) if level: return level.Name if hasattr(el, 'Host') and isinstance(el.Host, Level): return el.Host.Name # let's just hope this key is not locale dependent. ref_lev = el.Parameter['Reference Level'] if ref_lev and ref_lev.HasValue: return self.doc.GetElement(ref_lev.AsElementId()).Name def OnElementBegin(self, id): try: el = self.doc.GetElement(id) self.cylhalfs.append({}) level = self._get_element_level(el) if level: self.cur_level = level else: self.cur_level = 'Level-XXX' cur_family = '' if hasattr(el, 'Symbol'): sym = el.Symbol if sym and sym.Family: cur_family = sym.Family.Name self.cur_family.append(cur_family) self.cur_type = el.Name self.f.write('\n# %s // %s // %s\n' % (self.cur_level, self.cur_family, el.Name)) return RenderNodeAction.Proceed except: self._show_exc() def OnElementEnd(self, id): el = self.doc.GetElement(id) chl = self.cylhalfs.pop() # write the partial cylinders that were not matched for hck, (ch, matid) in chl.items(): self._writeCylinderSegments(ch, matid) self.cur_family.pop() self.cur_level = 'No-Level' def _make_name(self, matid): mat = self.doc.GetElement(matid) if self.cur_family[-1]: l = [self.cur_level, self.cur_family[-1]] else: l = [self.cur_level, self.cur_type] if mat: l.append(mat.Name) s = '+++'.join(l) return self._norm_name(s) def _writeMesh(self, mesh, matid): mesh = mesh.Transformed[self.xforms[-1]] name = self._make_name(matid) sl = [] for i in range(mesh.NumTriangles): t = mesh.Triangle[i] s = self._make_polygon(name, (t.Vertex[0], t.Vertex[1], t.Vertex[2])) sl.append(s) self.f.write('\n### Mesh Face Triangles\n') self.f.write(''.join(sl)) self.f.write('\n') def _almost_equal(self, x, y): return round(x, self.RND) == round(y, self.RND) def _loop_to_segments(self, loop): segs = [] for edge in loop: c = edge.AsCurve().CreateTransformed(self.xforms[-1]) cpts = c.Tessellate() segs.append(list(cpts)) for i in range(len(segs)-1): # some edges may be backwards pl0 = segs[i] pl1 = segs[i+1] if pl0[-1].IsAlmostEqualTo( pl1[0]): continue if pl0[-1].IsAlmostEqualTo(pl1[-1]): pl1.reverse() continue if pl0[0].IsAlmostEqualTo(pl1[0]): pl0.reverse() continue if pl0[0].IsAlmostEqualTo(pl1[-1]): pl0.reverse() pl1.reverse() continue return segs def _write_polygon(self, f, matid): name = self._make_name(matid) edges = f.EdgeLoops[0] loop = edges.ForwardIterator() segs = self._loop_to_segments(loop) pts = [] for ptl in segs: pts.extend(ptl[:-1]) if f.EdgeLoops.Size > 1: # holes last = pts[-1] for i in range(1, f.EdgeLoops.Size): loop = f.EdgeLoops[i] hpts = [] hsegs = self._loop_to_segments(loop) for hptl in hsegs: hpts.extend(hptl[:-1]) hpts.append(hpts[0]) hpts.reverse() hpts.append(last) pts.extend(hpts) self.f.write(self._make_polygon(name, pts)) def _write_circle(self, f, matid): name = self._make_name(matid) xf = self.xforms[-1] curve = f.EdgeLoops[0][0].AsCurve().CreateTransformed(self.xforms[-1]) irad = 0.0 if f.EdgeLoops.Size == 2: curve2 = f.EdgeLoops[1][0].AsCurve().CreateTransformed(self.xforms[-1]) irad = curve2.Radius s = self._make_ring(name, curve.Center, curve.Normal, irad, curve.Radius) self.f.write(s) def _is_circle(self, f): loops = f.EdgeLoops if loops.Size not in [1,2]: return False loop = loops[0] if loop.Size != 2: return False c0 = loop[0].AsCurve() c1 = loop[1].AsCurve() if not isinstance(c0, Arc) or not isinstance(c1, Arc): return False if not self._almost_equal(c0.Radius, c1.Radius): return False if loops.Size == 2: # inner loop for ring loop2 = loops[1] if loop2.Size != 2: return False c02 = loop2[0].AsCurve() c12 = loop2[1].AsCurve() if not isinstance(c02, Arc) or not isinstance(c12, Arc): return False if not self._almost_equal(c02.Radius, c12.Radius): return False if not c0.Center.IsAlmostEqualTo(c02.Center): return False return True def _is_HalfCylinder(self,f): loop = f.EdgeLoops[0] if loop.Size != 4: self.f.write('\n# XXX Cylinder with weird number of edges #%d\n' % loop.Size) return 'weird' curves = [] lines = [] for i in range(loop.Size): c = loop[i].AsCurve() if isinstance(c, Arc): curves.append(c) elif isinstance(c, Line): lines.append(c) else: self.f.write('\n# XXX Cylinder with weird edge type #%d: "%s"\n' % (i, str(c.GetType()))) return 'weird' if not len(curves) == len(lines) == 2: return False c = curves[0] if (round(c.GetEndParameter(1) - c.GetEndParameter(0), self.RND) * 2 == round(c.Period, self.RND)): return True return False def _writeCylinder(self, cyl, matid): name = self._make_name(matid) arcs = self._get_cyl_arcs(cyl) xf = self.xforms[-1] s = self._make_cylinder(name=self._make_name(matid), start=xf.OfPoint(arcs[0].Center), end=xf.OfPoint(arcs[1].Center), rad=xf.OfVector(cyl.Radius[0]).GetLength()) self.f.write(s) def _get_cyl_arcs(self, cyl): loop = cyl.EdgeLoops[0] c0 = loop[0].AsCurve() if isinstance(c0, Arc): c1 = loop[2].AsCurve() else: c0 = loop[1].AsCurve() assert(isinstance(c0, Arc)) c1 = loop[3].AsCurve() return c0, c1 def _get_cyl_segments(self, cyl): loop = cyl.EdgeLoops[0] segs = self._loop_to_segments(loop) c0 = loop[0].AsCurve() if isinstance(c0, Arc): s0 = segs[0] s1 = segs[2] else: s0 = segs[1] s1 = segs[3] return s0, s1 def _writeCylinderSegments(self, cyl, matid): name = self._make_name(matid) segs = self._get_cyl_segments(cyl) pts0 = segs[0] pts1 = segs[1] sl = [] llen = pts0.Count if pts0.Count != pts1.Count: llen = min((pts0.Count, pts1.Count)) self.f.write('# XXX Segment count differs: %d %d\n' % (pts0.Count, pts1.Count)) for i in range(llen-1): s = self._make_polygon(name, (pts0[i], pts0[i+1], pts1[llen-i-2], pts1[llen-i-1]) ) sl.append(s) self.cur_n += 1 self.f.write('\n### Cylinder Segments\n') self.f.write(''.join(sl)) self.f.write('\n') def _get_hcsig(self, cyl): # unique signature for the location and size of a cylinder # we use this to reunite half shells # XXX Should we transform them to world first ?!? # XXX Since this works on a per-family instance basis, # XXX that may not actually be necessary. arcs = self._get_cyl_arcs(cyl) orig = arcs[0].Center end = arcs[1].Center rad = cyl.Radius[0].GetLength() return (round(rad, self.RND), round(orig[0], self.RND), round(orig[1], self.RND), round(orig[2], self.RND), round(end[0], self.RND), round(end[1], self.RND), round(end[2], self.RND)) def OnFaceBegin(self, node): try: f = node.GetFace() ref = f.Reference matid = f.MaterialElementId if isinstance(f, CylindricalFace): hcres = self._is_HalfCylinder(f) if hcres is True: hck = self._get_hcsig(f) oh = self.cylhalfs[-1].get(hck) if oh: del self.cylhalfs[-1][hck] self._writeCylinder(f, matid) else: self.cylhalfs[-1][hck] = (f, matid) elif hcres == 'weird': # unexpected properties for a cylyndrical face self._writeMesh(f.Triangulate(), matid) else: self._writeCylinderSegments(f, matid) elif isinstance(f, PlanarFace): if self._is_circle(f): self._write_circle(f, matid) else: self._write_polygon(f, matid) else: self._writeMesh(f.Triangulate(), matid) return RenderNodeAction.Skip except: self._show_exc() def _show_exc(self): # Otherwise we'll only see the traceback to where C# called Python ei = sys.exc_info() sl = [str(ei[1])] tb = ei[2] while tb: s = '%s Line: %d' % (tb.tb_lasti, tb.tb_lineno) sl.append(s) tb = tb.tb_next ss = '\n'.join(sl) TaskDialog.Show('Traceback', ss) def OnFaceEnd(self, node): pass def OnInstanceBegin(self, node): self.xforms.append(self.xforms[-1].Multiply(node.GetTransform())) return RenderNodeAction.Proceed def OnInstanceEnd(self, node): self.xforms.pop() def OnLight(self, node): pass def OnLinkBegin(self, node): TaskDialog.Show('OnLinkBegin', str(node)) return RenderNodeAction.Proceed def OnLinkEnd(self, node): pass def OnMaterial(self, node): pass def OnPolymesh(self, node): pass # seperate function above def OnRPC(self, node): pass def OnViewBegin(self, node): return RenderNodeAction.Proceed def OnViewEnd(self, node): pass
by
Andy McNeil
—
last modified
Feb 29, 2016 12:18 PM