-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgbxmlMerge_streamlit.py
259 lines (209 loc) · 7.95 KB
/
gbxmlMerge_streamlit.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
## -*- coding: utf-8 -*-
"""
Spyder Editor
Ripcord Engineering - JPS
Merges two gbXML files exported from Revit. First gbXML based on Revit masses does not include openings.
Second gbXML based on Revit spaces includes openings. Openings within variable 'dist' parameter are
take from gbXML based on Revit spaces and merged with gbXML based on Revit masses.
"""
# xgbxml on PyPi: https://pypi.org/project/xgbxml/
# Topologic on PyPi: https://pypi.org/project/topologicpy/
from lxml import etree
from xgbxml import get_parser
from copy import copy
import streamlit as st
import streamlit.components.v1 as components
from topologicpy import topologic
#print(dir(topologic)) # troubleshooting of topologic module path(s)
# tab1 front matter
title_body = '[gbxmlMerge](https://github.com/jpstaub/Revit-Dynamo-MEP/blob/master/AutomatedBuildingEnergyModel/Ripcord_Revit-gbXML_Workflow_22-09-15.pdf)'
sub_body = 'by [Ripcord Engineering](https://www.ripcordengineering.com)'
def uploader_fpa():
print("Uploaded gbxml without openings.")
def uploader_fpb():
print("Uploaded gbxml with openings.")
# define: file variables with streamlit
# fpa = st.file_uploader("gbxml without openings", type = 'xml', on_change = uploader_cb())
fpa_label = 'gbxml without openings'
fpa = st.sidebar.file_uploader(fpa_label, type = 'xml', on_change = uploader_fpa())
if fpa is None:
st.stop()
# fpb = st.file_uploader("gbxml with openings", type = 'xml', on_change = uploader_cb())
fpb_label = 'gbxml with openings'
fpb = st.sidebar.file_uploader(fpb_label, type = 'xml', on_change = uploader_fpb())
if fpb is None:
st.stop()
fpo = 'merged.xml'
# set: distance tolerance of opening from surface in gbXML length units (typically the thickness of the roof or wall)
# dist = 1.1
dist_statement = 'tolerance of opening from surface in gbXML length units'
dist_help = 'typically greater than the thickness of the roof'
dist_warning = 'Please input an opening tolerance value greater than 0.'
dist = st.sidebar.number_input(dist_statement, min_value=0.0, max_value=2.0, value=0.0, help=dist_help)
if dist==0:
st.warning(dist_warning)
st.stop()
# use: xgbxml to generate a lxml parser / read: gbXML version from input file
tree_parser=etree.parse(fpa)
gbxml=tree_parser.getroot()
parser=get_parser(version=gbxml.attrib['version'])
# parser=get_parser(version='0.37')
# open: the file using the lxml parser
tree_A = etree.parse(fpa,parser)
gbxml_A = tree_A.getroot()
# open: the file using the lxml parser
tree_B = etree.parse(fpb,parser)
gbxml_B = tree_B.getroot()
# make: a copy of gbxml_A which is named gbxml_C
gbxml_C = copy(gbxml_A)
etree_C = etree.ElementTree(gbxml_C)
# define: topologicpy faceByVertices (Wassim Jabi) - 27MAY
def faceByVertices(vertices):
vertices
edges = []
for i in range(len(vertices)-1):
v1 = vertices[i]
v2 = vertices[i+1]
try:
e = topologic.Edge.ByStartVertexEndVertex(v1, v2)
if e:
edges.append(e)
except:
continue
v1 = vertices[-1]
v2 = vertices[0]
# print("V1:", v1.X(), v1.Y(), v1.Z())
# print("V2:", v2.X(), v2.Y(), v2.Z())
try:
e = topologic.Edge.ByStartVertexEndVertex(v1, v2)
if e:
edges.append(e)
except:
print("Edge creation failed!")
pass
# print("I managed to create",len(edges),"edges")
if len(edges) >= 3:
c = topologic.Cluster.ByTopologies(edges, False)
w = c.SelfMerge()
if w.Type() == topologic.Wire.Type() and w.IsClosed():
f = topologic.Face.ByExternalBoundary(w)
else:
raise Exception("Error: Could not get a valid wire")
else:
raise Exception("Error: could not get a valid number of edges")
return f
# format: Space Name
for space in gbxml_C.Campus.Building.Spaces:
try:
name = space.Name.text.replace('-', ':')
number = name.split(':')[1].strip()
new_name = number + ' Space'
space.Name.text = new_name
except:
pass
# insert: gbxml_B WindowTypes into gbxml_C
for window_type in gbxml_B.WindowTypes:
gbxml_C.insert(1, window_type)
# get: gbxml_B openings (ops)
# make: gbxml_B opening centroids (ocs)
ops = []
ocs = []
for op in gbxml_B.Campus.Surfaces.Openings:
ops.append(op)
o = []
for c in op.PlanarGeometry.get_coordinates():
o.append(topologic.Vertex.ByCoordinates(c[0],c[1],c[2]))
ocs.append(topologic.Topology.Centroid(faceByVertices(o)))
# get: gbxml_C exterior surfaces (exsu)
# make: gbxml_C surface faces (sfs)
exsu = []
sfs = []
for su in gbxml_C.Campus.Surfaces:
if su.get_attribute('surfaceType') in ['ExteriorWall', 'Roof']:
exsu.append(su)
s = []
for c in su.PlanarGeometry.get_coordinates():
s.append(topologic.Vertex.ByCoordinates(c[0],c[1],c[2]))
sfs.append(faceByVertices(s))
# test: gbxml_B opening centroid IsInside(face,point,tolerance) of gbxml_C surface face (vin)
vin = []
for oc in ocs:
r = []
for sf in sfs:
r.append(topologic.FaceUtility.IsInside(sf,oc,dist))
vin.append(r)
# # qa: count number of true responses per opening vertex
# count = []
# for v in vin:
# count.append(v.count(True))
# get: indices of gbxml_C surfaces with gbxml_B opening centroid isinside (sfoc)
sfoc = []
for v in vin:
if True in v:
sfoc.append(v.index(True))
else:
# sfoc.append(False) #False is equivalent to 0 index in 'sf' check below
sfoc.append('False')
# insert: gbxml_B opening into gbxml_C surface object if opening within variable 'dist' parameter
i = 0
j = 0
infos = []
errors = []
for sf in sfoc:
# if sf==False: #False is equivalent to 0 index
if sf=='False':
infos.append('Opening without matching surface: ' + ops[i].Name.text + '.')
j+=1
i+=1
else:
try:
exsu[sf].insert(3, exsu[sf].copy_opening(ops[i],tolerance=dist)) # copy_opening is xgbxml method
infos.append('Copied opening: ' + ops[i].Name.text)
i+=1
except ValueError:
opening_error = ('Caught ValueError. Check opening: ' + ops[i].Name.text + '.')
# st.error(opening_error)
errors.append(opening_error)
i+=1
except Exception:
opening_error = ('Caught Exception. Check opening: ' + ops[i].Name.text + '.')
# st.error(opening_error)
errors.append(opening_error)
i+=1
# write status to an expander container
with st.expander('Status'):
st.info('Copied ' + str(i-j) + ' of ' + str(len(ops)) + ' openings.')
for i in infos:
if 'Copied' in i:
st.write(i)
else:
st.error(i)
# write errors to an expander container
with st.expander('Exceptions'):
for e in errors:
st.error(e)
# render: the gbXML etree
render_body = 'Please select the gbXML to view.'
render_options = ('gbXML without openings', 'gbXML with openings', 'merged gbXML')
render = st.radio(render_body, render_options, index=2)
if render == 'gbXML without openings':
ax = gbxml_A.Campus.render()
ax.figure.set_size_inches(8, 8)
ax.set_title('gbXML without openings')
elif render == 'gbXML with openings':
ax = gbxml_B.Campus.render()
ax.figure.set_size_inches(8, 8)
ax.set_title('gbXML with openings')
else:
ax = gbxml_C.Campus.render()
ax.figure.set_size_inches(8, 8)
ax.set_title('merged gbXML')
st.set_option('deprecation.showPyplotGlobalUse', False) #hides deprecation warning on webpage
st.pyplot()
# download: the gbXML_C etree to a local file
with st.sidebar:
download_label = 'Download Merged gbXML'
st.download_button(download_label, etree.tostring(etree_C, pretty_print=True), file_name = fpo)
# embed: ladybug tools gbxml viewer
iframe_address = 'https://www.ladybug.tools/spider/gbxml-viewer/r14/gv-cor-core/gv-cor.html'
components.iframe(iframe_address, width=900, height=600)