Skip to content

Commit e1cba79

Browse files
committed
- add slope
- add toast to alert user - formatting of xyz to make it more readable
1 parent 6815e41 commit e1cba79

File tree

2 files changed

+76
-40
lines changed

2 files changed

+76
-40
lines changed

extensions/pyRevitTools.extension/pyRevit.tab/Modify.panel/3D.pulldown/Measure.pushbutton/measure3d.xaml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@
1818

1919
<Separator Margin="0,5"/>
2020

21-
<TextBlock x:Name="dx_text" Text="ΔX: -" Margin="0,5,0,2" TextAlignment="Right" FontWeight="SemiBold"/>
22-
<TextBlock x:Name="dy_text" Text="ΔY: -" Margin="0,2" TextAlignment="Right" FontWeight="SemiBold"/>
23-
<TextBlock x:Name="dz_text" Text="ΔZ: -" Margin="0,2" TextAlignment="Right" FontWeight="SemiBold"/>
24-
<TextBlock x:Name="diagonal_text" Text="Diagonal: -" Margin="0,2" TextAlignment="Right" FontWeight="Bold" FontSize="14"/>
21+
<TextBlock x:Name="dx_text" Margin="0,5,0,2" TextAlignment="Right" FontFamily="Consolas" FontWeight="SemiBold"/>
22+
<TextBlock x:Name="dy_text" Margin="0,2" TextAlignment="Right" FontFamily="Consolas" FontWeight="SemiBold"/>
23+
<TextBlock x:Name="dz_text" Margin="0,2" TextAlignment="Right" FontFamily="Consolas" FontWeight="SemiBold"/>
24+
<TextBlock x:Name="diagonal_text" Margin="0,2" TextAlignment="Right" FontWeight="Bold" FontSize="14"/>
25+
<TextBlock x:Name="slope_text" Margin="0,2" TextAlignment="Right" FontFamily="Consolas" FontWeight="SemiBold"/>
2526
<TextBlock x:Name="project_unit_text" Text="" Visibility="Collapsed" Foreground="Red" FontWeight="Bold" Margin="0,5,0,2"/>
2627
</StackPanel>
2728
</GroupBox>

extensions/pyRevitTools.extension/pyRevit.tab/Modify.panel/3D.pulldown/Measure.pushbutton/script.py

Lines changed: 71 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# -*- coding: utf-8 -*-
22
from collections import deque
3-
from pyrevit import HOST_APP, revit, forms, script, traceback
3+
from math import pi, atan, sqrt
4+
from pyrevit import HOST_APP, revit, forms, script
45
from pyrevit import UI, DB
56
from pyrevit.framework import System
67
from Autodesk.Revit.Exceptions import InvalidOperationException
@@ -9,13 +10,22 @@
910

1011
doc = HOST_APP.doc
1112
uidoc = revit.uidoc
13+
# Length
1214
length_format_options = doc.GetUnits().GetFormatOptions(DB.SpecTypeId.Length)
1315
length_unit = length_format_options.GetUnitTypeId()
1416
length_unit_label = DB.LabelUtils.GetLabelForUnit(length_unit)
1517
length_unit_symbol = length_format_options.GetSymbolTypeId()
1618
length_unit_symbol_label = None
1719
if not length_unit_symbol.Empty():
1820
length_unit_symbol_label = DB.LabelUtils.GetLabelForSymbol(length_unit_symbol)
21+
# Slope
22+
slope_format_options = doc.GetUnits().GetFormatOptions(DB.SpecTypeId.Slope)
23+
slope_unit = slope_format_options.GetUnitTypeId()
24+
slope_unit_label = DB.LabelUtils.GetLabelForUnit(slope_unit)
25+
slope_unit_symbol = slope_format_options.GetSymbolTypeId()
26+
slope_unit_symbol_label = None
27+
if not slope_unit_symbol.Empty():
28+
lslope_unit_symbol_label = DB.LabelUtils.GetLabelForSymbol(slope_unit_symbol)
1929

2030
# Global variables
2131
measure_window = None
@@ -34,29 +44,46 @@
3444

3545
WINDOW_POSITION = "measure_window_pos"
3646

47+
3748
def calculate_distances(point1, point2):
38-
"""Calculate dx, dy, dz and diagonal distance between two points.
49+
"""Calculate dx, dy, dz, diagonal distance, and slope angle between two points.
3950
4051
Args:
4152
point1 (DB.XYZ): First point
4253
point2 (DB.XYZ): Second point
4354
4455
Returns:
45-
tuple: (dx, dy, dz, diagonal) all in internal units (feet)
56+
tuple: (dx, dy, dz, diagonal, slope) all in internal units (feet, rad)
4657
"""
4758
dx = abs(point2.X - point1.X)
4859
dy = abs(point2.Y - point1.Y)
4960
dz = abs(point2.Z - point1.Z)
5061
diagonal = point1.DistanceTo(point2)
5162

52-
return dx, dy, dz, diagonal
63+
horizontal = sqrt(dx ** 2 + dy ** 2)
64+
65+
if horizontal == 0:
66+
slope = pi / 2.0 # 90 degrees (vertical)
67+
else:
68+
slope = atan(dz / horizontal)
5369

70+
return dx, dy, dz, diagonal, slope
5471

55-
def format_distance(value_in_feet):
72+
73+
def format_distance(value_internal):
5674
return DB.UnitFormatUtils.Format(
5775
doc.GetUnits(),
5876
DB.SpecTypeId.Length,
59-
value_in_feet,
77+
value_internal,
78+
False,
79+
)
80+
81+
82+
def format_slope(value_internal):
83+
return DB.UnitFormatUtils.Format(
84+
doc.GetUnits(),
85+
DB.SpecTypeId.Slope,
86+
value_internal,
6087
False,
6188
)
6289

@@ -148,7 +175,7 @@ def validate_3d_view():
148175
forms.alert(
149176
"Please activate a 3D view before using the 3D Measure tool.",
150177
title="3D View Required",
151-
exitscript=True
178+
exitscript=True,
152179
)
153180
return False
154181
return True
@@ -159,7 +186,7 @@ def perform_measurement():
159186
# Add 3D view validation
160187
if not validate_3d_view():
161188
return
162-
189+
163190
try:
164191
with forms.WarningBar(title="Pick first point"):
165192
point1 = revit.pick_elementpoint(world=True)
@@ -180,26 +207,32 @@ def perform_measurement():
180207
dc3d_server.meshes = existing_meshes + new_meshes
181208
uidoc.RefreshActiveView()
182209

183-
dx, dy, dz, diagonal = calculate_distances(point1, point2)
210+
dx, dy, dz, diagonal, slope = calculate_distances(point1, point2)
184211

185212
measure_window.point1_text.Text = "Point 1: {}".format(format_point(point1))
186213
measure_window.point2_text.Text = "Point 2: {}".format(format_point(point2))
187-
measure_window.dx_text.Text = "ΔX: {}".format(format_distance(dx))
188-
measure_window.dy_text.Text = "ΔY: {}".format(format_distance(dy))
189-
measure_window.dz_text.Text = "ΔZ: {}".format(format_distance(dz))
214+
measure_window.dx_text.Text = "ΔX: {:>15}".format(format_distance(dx))
215+
measure_window.dy_text.Text = "ΔY: {:>15}".format(format_distance(dy))
216+
measure_window.dz_text.Text = "ΔZ: {:>15}".format(format_distance(dz))
190217
measure_window.diagonal_text.Text = "Diagonal: {}".format(
191218
format_distance(diagonal)
192219
)
220+
measure_window.slope_text.Text = "Slope: {}".format(format_slope(slope))
193221

194222
# Add to history
195-
history_entry = "Measurement {}:\n P1: {}\n P2: {}\n ΔX: {}\n ΔY: {}\n ΔZ: {}\n Diagonal: {}\n".format(
196-
len(measurement_history) + 1,
197-
format_point(point1),
198-
format_point(point2),
199-
format_distance(dx),
200-
format_distance(dy),
201-
format_distance(dz),
202-
format_distance(diagonal),
223+
history_entry = (
224+
"Measurement {}:\n P1: {}\n P2: {}\n "
225+
"ΔX: {:>15}\n ΔY: {:>15}\n ΔZ: {:>15}\n "
226+
"Diagonal: {}\n Slope: {}".format(
227+
len(measurement_history) + 1,
228+
format_point(point1),
229+
format_point(point2),
230+
format_distance(dx),
231+
format_distance(dy),
232+
format_distance(dz),
233+
format_distance(diagonal),
234+
format_slope(slope),
235+
)
203236
)
204237
measurement_history.append(history_entry)
205238

@@ -214,13 +247,15 @@ def perform_measurement():
214247
logger.error("InvalidOperationException during measurement: {}".format(ex))
215248
forms.alert(
216249
"Measurement cancelled due to invalid operation. Please try again.",
217-
title="Measurement Error"
250+
title="Measurement Error",
218251
)
219252
except Exception as ex:
220-
logger.error("Error during measurement: {}\n{}".format(ex, traceback.format_exc()))
253+
logger.exception(
254+
"Error during measurement: {}".format(ex)
255+
)
221256
forms.alert(
222257
"An unexpected error occurred during measurement. Check the log for details.",
223-
title="Measurement Error"
258+
title="Measurement Error",
224259
)
225260

226261

@@ -253,6 +288,7 @@ def __init__(self, xaml_file_name):
253288
self.dy_text.Text = "ΔY: -"
254289
self.dz_text.Text = "ΔZ: -"
255290
self.diagonal_text.Text = "Diagonal: -"
291+
self.slope_text.Text = "Slope: -"
256292
self.history_text.Text = "No measurements yet"
257293

258294
if not length_unit_symbol_label:
@@ -268,41 +304,40 @@ def __init__(self, xaml_file_name):
268304
try:
269305
pos = script.load_data(WINDOW_POSITION, this_project=False)
270306
all_bounds = [s.WorkingArea for s in System.Windows.Forms.Screen.AllScreens]
271-
x, y = pos['Left'], pos['Top']
307+
x, y = pos["Left"], pos["Top"]
272308
visible = any(
273-
(b.Left <= x <= b.Right and b.Top <= y <= b.Bottom)
274-
for b in all_bounds
309+
(b.Left <= x <= b.Right and b.Top <= y <= b.Bottom) for b in all_bounds
275310
)
276311
if not visible:
277312
raise Exception
278313
self.WindowStartupLocation = System.Windows.WindowStartupLocation.Manual
279-
self.Left = pos.get('Left', 200)
280-
self.Top = pos.get('Top', 150)
314+
self.Left = pos.get("Left", 200)
315+
self.Top = pos.get("Top", 150)
281316
except Exception:
282-
self.WindowStartupLocation = System.Windows.WindowStartupLocation.CenterScreen
317+
self.WindowStartupLocation = (
318+
System.Windows.WindowStartupLocation.CenterScreen
319+
)
283320

284321
self.Show()
285-
322+
286323
# Automatically start the first measurement
287324
measure_handler_event.Raise()
288325

289326
def window_closed(self, sender, args):
290327
"""Handle window close event - copy history to clipboard, cleanup DC3D server and visual aids."""
291328
global dc3d_server
292-
new_pos = {'Left': self.Left, 'Top': self.Top}
329+
new_pos = {"Left": self.Left, "Top": self.Top}
293330
script.store_data(WINDOW_POSITION, new_pos, this_project=False)
294-
331+
295332
# Copy measurement history to clipboard before cleanup
296333
try:
297334
if measurement_history:
298335
history_text = "\n".join(measurement_history)
299336
script.clipboard_copy(history_text)
300-
logger.info("Measurement history copied to clipboard")
301-
else:
302-
logger.info("No measurements to copy to clipboard")
337+
forms.toast("Measurements copied to Clipboard!", title="Measure 3D")
303338
except Exception as ex:
304339
logger.error("Error copying to clipboard: {}".format(ex))
305-
340+
306341
try:
307342
# Delete all visual aids
308343
if dc3d_server:

0 commit comments

Comments
 (0)