Viewpoints panel

Section Fit

Fit the six section planes to the selection, turned to the objects rather than the world.

On the ribbonStacked button, Viewpoints panel
NeedsA selection
Shift+ClickThe padding each plane stands off the objects, and the snap angle

What it does#

Navisworks' own Fit Section to Selection gives a box square to the world, so a diagonal run of pipe gets four corners of empty space. This gives planes parallel to the run, with a little padding so faces do not graze the geometry.

How to use it#

  1. Select the objects.
  2. Click Section Fit. The toast reports the box it made.

Limitations#

  • Inside the snap angle, 1.5 degrees by default, the box stays square to the world, because a small tilt costs every plane's alignment and buys nothing.
  • A selection with no geometry, such as a folder node, has nothing to fit.

Other uses#

  • Section to one room's worth of services before a walkthrough, then nudge the faces with Section Nudge.

Good to know#

The padding starts at 150 mm and is shown and typed in the document's units, like any pyNavis length: 6" in a feet-and-inches document, or a value with its unit such as 150mm anywhere. Both settings are shared with Section Plan in section_planes.json.

Source#

The tool's own files, word for word as shipped in Viewpoints.panel/Section.stack/Fit.pushbutton. Every pyNavis tool is an ordinary bundle: copy these into your own extension and change what you like.

script.py Runs on a click. View on GitHub
"""Fits the six section planes to the current selection.

Navisworks' own Fit Section to Selection gives a world-aligned box; this turns
the box to the objects, so a diagonal run gets planes parallel to the run
instead of four corners of empty space. Shift+Click sets the padding.
"""

from pynavis import section, selection, settings, toast

values = settings.load(section.TOOL, section.DEFAULTS)
result = section.fit_to_selection(selection.get_items(), values)
toast.show(result.level, result.message, result.detail)
config.py Runs on Shift+Click. View on GitHub
"""Defaults for Section Fit (the Shift+Click settings).

Padding is how far each of the six planes stands off the objects, so faces do
not graze the geometry. It is shown and typed in the document's units, the
way any pyNavis length is (pynavis.lengths: 6", 1' 6 1/2", 150mm), and kept
in millimetres. Snap angle is how close to square counts as square: below it
the box stops turning, because a two-degree tilt costs you every plane's
alignment and buys nothing.
"""
import clr

clr.AddReference('PresentationFramework')
clr.AddReference('PresentationCore')
clr.AddReference('WindowsBase')
clr.AddReference('PyNavis.Runtime')

from System.Windows import (FontWeights, HorizontalAlignment, ResizeMode,
                            SizeToContent, TextWrapping, Thickness, Visibility,
                            Window, WindowStartupLocation)
from System.Windows.Controls import Orientation, StackPanel, TextBox
from System.Windows.Media import FontFamily
from PyNavis.Runtime.Forms import DesignSystem, FluentChrome

from pynavis import lengths, section, settings, toast

T = DesignSystem.Tokens.Current
values = settings.load(section.TOOL, section.DEFAULTS)

units = 'Millimeters'                         # no document open: the stored unit
try:
    from pynavis import app
    units = str(app.get_doc().Units)
except Exception:
    pass

window = Window()
window.Title = 'Section Defaults'
window.Width = 380
window.SizeToContent = SizeToContent.Height
window.ResizeMode = ResizeMode.NoResize
window.WindowStartupLocation = WindowStartupLocation.CenterScreen
window.FontFamily = FontFamily('Segoe UI Variable Text, Segoe UI')
window.Background = DesignSystem.Brush(T.Paper)
window.ShowInTaskbar = False
FluentChrome.Apply(window)

body = StackPanel()
body.Margin = Thickness(20, 16, 20, 16)

title = DesignSystem.Text('Section fit defaults', 16, T.Ink, FontWeights.SemiBold)
body.Children.Add(title)
caption = DesignSystem.Text('Used every time Fit or Plan runs.', 12, T.Muted)
caption.Margin = Thickness(0, 4, 0, 14)
caption.TextWrapping = TextWrapping.Wrap
body.Children.Add(caption)


def section_label(label):
    text = DesignSystem.Text(label, 12, T.Muted, FontWeights.Medium)
    text.Margin = Thickness(0, 10, 0, 6)
    body.Children.Add(text)


section_label('Padding')
padding_box = TextBox()
padding_box.Text = lengths.format_input(
    lengths.convert(float(values['padding_mm']), 'Millimeters', units), units)
padding_box.Width = 110
padding_field = DesignSystem.InputField(T, padding_box, lengths.suffix(units) or None)
padding_field.HorizontalAlignment = HorizontalAlignment.Left
body.Children.Add(padding_field)
padding_caption = DesignSystem.Text(
    'How far each plane stands off the objects.', 12, T.Muted)
padding_caption.Margin = Thickness(0, 4, 0, 0)
body.Children.Add(padding_caption)
padding_error = DesignSystem.Text('Enter a length, %s.' % lengths.hint(units), 12, T.Error)
padding_error.TextWrapping = TextWrapping.Wrap
padding_error.Margin = Thickness(0, 4, 0, 0)
padding_error.Visibility = Visibility.Collapsed
body.Children.Add(padding_error)

section_label('Snap angle')
snap_box = TextBox()
snap_box.Text = '%g' % float(values['snap_degrees'])
snap_box.Width = 80
snap_field = DesignSystem.InputField(T, snap_box, 'deg')
snap_field.HorizontalAlignment = HorizontalAlignment.Left
body.Children.Add(snap_field)
snap_caption = DesignSystem.Text(
    'Within this of square, the box stays square to the world.', 12, T.Muted)
snap_caption.Margin = Thickness(0, 4, 0, 0)
snap_caption.TextWrapping = TextWrapping.Wrap
body.Children.Add(snap_caption)
snap_error = DesignSystem.Text('Enter between 0 and 45.', 12, T.Error)
snap_error.Margin = Thickness(0, 4, 0, 0)
snap_error.Visibility = Visibility.Collapsed
body.Children.Add(snap_error)


def number(box):
    try:
        return float(box.Text)
    except ValueError:
        return None


def padding_mm():
    """The typed padding in millimetres, or None when it does not read."""
    typed = lengths.parse(padding_box.Text, units)
    return None if typed is None else lengths.convert(typed, units, 'Millimeters')


def validate(*args):
    """Live, so the error appears as you type and clears the same way."""
    padding = padding_mm()
    snap = number(snap_box)
    padding_bad = padding is None or padding < 0
    snap_bad = snap is None or snap < 0 or snap > 45
    padding_error.Visibility = (Visibility.Visible if padding_bad
                                else Visibility.Collapsed)
    snap_error.Visibility = Visibility.Visible if snap_bad else Visibility.Collapsed
    padding_field.BorderBrush = DesignSystem.Brush(
        T.Error if padding_bad else T.LineStrong)
    snap_field.BorderBrush = DesignSystem.Brush(
        T.Error if snap_bad else T.LineStrong)
    save_btn.IsEnabled = not (padding_bad or snap_bad)
    return not (padding_bad or snap_bad)


def save():
    if not validate():
        return
    settings.save(section.TOOL, {'padding_mm': padding_mm(),
                                 'snap_degrees': number(snap_box)})
    toast.success('Section defaults saved.')
    window.Close()


footer = StackPanel()
footer.Orientation = Orientation.Horizontal
footer.HorizontalAlignment = HorizontalAlignment.Right
footer.Margin = Thickness(0, 18, 0, 0)
cancel_btn = DesignSystem.Secondary(T, 'Cancel', lambda: window.Close())
cancel_btn.Margin = Thickness(0, 0, 8, 0)
footer.Children.Add(cancel_btn)
save_btn = DesignSystem.Primary(T, 'Save', save)
footer.Children.Add(save_btn)
body.Children.Add(footer)

padding_box.TextChanged += validate
snap_box.TextChanged += validate
validate()

window.Content = body
window.ShowDialog()
bundle.yaml The bundle contract: the keys pyNavis reads for this tool. View on GitHub
title: Section Fit
tooltip: Fit the six section planes to the selection, square to the objects