Takeoff panel
Length Takeoff
Totals the length of lines, arcs, polylines and splines per layer, in the selection or else all of model space, in the drawing's units, with an Excel export.
PYLENGTHTAKEOFFLTOWhat it does#
Measures every line, arc, polyline (lightweight, old-style 2D and 3D) and spline in the selection, adds the lengths up per layer, and shows the totals in the drawing's units. With nothing selected it measures everything in model space. Other objects in the selection (circles, text, blocks) are passed over.
The window reads like this, layers sorted by name:
Model space: 312.48 m in 57 objects on 4 layers.
M-DUCT-SUPPLY 148.20 m (21)
M-DUCT-RETURN 96.75 m (14)
P-PIPE-CW 52.03 m (17)
P-PIPE-HW 15.50 m (5)
The number in brackets is how many objects make up that total. The window lists the first 20 layers; the export and the copy carry all of them.
How to use it#
- Select the objects to measure, or select nothing to measure all of model space. A window or a crossing over one floor, or a Quick Select by layer, both work.
- Click Length Takeoff, or type
PYLENGTHTAKEOFF. - Read the totals. Then pick Export to Excel, Copy or Close.
- Export to Excel asks where to save, suggesting
<drawing> - Length Takeoff.xlsxbeside the drawing. An existing file of that name is replaced, not added to. - Copy puts the same table on the clipboard, tab separated, ready to paste into Excel or an estimate.
The table#
The Excel sheet is called Takeoff. The clipboard copy has the same columns:
| Column | Holds |
|---|---|
| Layer | The layer name. The last row is Total. |
| Objects | How many objects were measured on that layer. |
| Length (unit) | The total as a number in drawing units, rounded to
six decimals, so Excel can add it up. The header carries the unit, such as
Length (mm), or just Length when the drawing's units are
undefined. |
| Length | The same total as text, as the window shows it. |
Units#
Lengths are read in drawing units and labelled from the drawing's insertion units
(INSUNITS). Inches and feet show as feet and inches, such as 152'-3.50"; metric
and the other units as a number and a short label, such as 45.20 m, always to
two decimal places. A drawing whose units are undefined shows bare numbers. Nothing is
converted: if a drawing is drawn in millimetres but INSUNITS says metres, the label is wrong
and the figures are not.
Limitations#
- With nothing selected only model space is measured, even when you are on a layout tab.
- Objects inside blocks and xrefs are not measured: a run drawn as a block counts as nothing. Explode it, or measure in the source drawing.
- Circles and ellipses are not counted.
- Lengths are true lengths along the object, in 3D. A line or 3D polyline that rises measures its sloped length, not its length in plan.
- Grouping is by layer only, not by colour, linetype or size. Keep each system on its own layer to get one figure per system.
- An object AutoCAD cannot measure is left out of the totals, and a toast says how many there were.
Other uses#
- Cable tray, conduit or sprinkler main lengths for a quick quantity before the proper takeoff.
- Perimeter of walls drawn as polylines on their own layer.
- A check that a layer you think is empty really holds nothing measurable, or how many stray objects sit on it.
Source#
The tool's own files, word for word as shipped in Takeoff.panel/Length_Takeoff.pushbutton. Every pyACAD 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# -*- coding: utf-8 -*-
"""Run lengths per layer: duct, pipe and conduit takeoff in drawing units.
Totals the length of lines, arcs, polylines (lightweight, 2D and 3D) and
splines in the selection, or in all of model space when nothing is selected,
grouped by layer. Lengths are shown in the drawing's units (feet and inches for
imperial drawings). The totals can go to Excel or onto the clipboard.
"""
import os
from pyacad import ents, forms, script, sel, toast, units, xl
__title__ = 'Length Takeoff'
TITLE = 'Length Takeoff'
XLSX = 'Excel workbook (*.xlsx)|*.xlsx'
SHOWN = 20 # layers listed in the dialog before "and N more"
EXPORT = 'Export to Excel'
COPY = 'Copy'
CLOSE = 'Close'
def plural(count, word):
return '%d %s%s' % (count, word, '' if count == 1 else 's')
def default_path(drawing, what):
"""'<drawing> - <what>.xlsx' beside the drawing; just the name when unsaved."""
if not drawing or drawing == 'untitled':
return 'Drawing - %s.xlsx' % what
folder, name = os.path.split(drawing)
return os.path.join(folder, '%s - %s.xlsx' % (os.path.splitext(name)[0], what))
def write_workbook(path, headers, rows, sheet):
"""Writes a fresh workbook; True when it worked. The Save dialog already
asked before replacing a file, so an old one is removed rather than merged."""
try:
if os.path.exists(path):
os.remove(path)
xl.write(path, rows, headers=headers, sheet=sheet)
except (IOError, OSError) as error:
toast.error('Could not write the workbook', 'Is it open in Excel? %s' % error)
return False
return True
def unit_label(unit):
"""The short label of a unit name ('mm'), '' for Undefined."""
for name, _, label in units.UNITS.values():
if name.lower() == unit.lower():
return label
return ''
def summary(totals, unit, scope):
total = sum(row['length'] for row in totals)
count = sum(row['count'] for row in totals)
lines = ['%s: %s in %s on %s.' % (scope, units.format_length(total, unit),
plural(count, 'object'), plural(len(totals), 'layer')), '']
for row in totals[:SHOWN]:
lines.append('%s %s (%d)' % (row['layer'], units.format_length(row['length'], unit),
row['count']))
if len(totals) > SHOWN:
lines.append('and %s more: export or copy for the full list' % plural(len(totals) - SHOWN, 'layer'))
return '\n'.join(lines)
def table(totals, unit):
"""(headers, rows) for Excel and the clipboard, with a Total row."""
label = unit_label(unit)
headers = ['Layer', 'Objects', 'Length (%s)' % label if label else 'Length', 'Length']
rows = [[row['layer'], row['count'], round(row['length'], 6),
units.format_length(row['length'], unit)] for row in totals]
total = sum(row['length'] for row in totals)
rows.append(['Total', sum(row['count'] for row in totals), round(total, 6),
units.format_length(total, unit)])
return headers, rows
def tab_text(headers, rows):
return '\n'.join('\t'.join(str(cell) for cell in row) for row in [headers] + rows)
def run():
drawing = script.get_document_path()
if drawing is None:
toast.warning('No drawing is open', 'Open a drawing, then run %s' % TITLE)
return
picked = sel.current()
handles = picked if picked else list(ents.iter())
scope = 'Selection' if picked else 'Model space'
measured = ents.lengths(handles)
totals = ents.length_by_layer(measured)
if not totals:
toast.info('Nothing to measure',
'No lines, arcs, polylines or splines in the %s' % scope.lower())
return
unit = units.current()
headers, rows = table(totals, unit)
choice = forms.ask_options(summary(totals, unit, scope), [EXPORT, COPY, CLOSE], TITLE)
if choice == COPY:
script.clipboard_copy(tab_text(headers, rows))
toast.success('Takeoff copied', 'Paste it into Excel or a document')
elif choice == EXPORT:
path = forms.save_file(XLSX, default_path(drawing, TITLE), 'Export length takeoff')
if path and write_workbook(path, headers, rows, 'Takeoff'):
toast.success('Takeoff exported: %s' % plural(len(totals), 'layer'), path)
unmeasured = sum(1 for item in measured if item['length'] is None)
if unmeasured:
toast.warning('%s could not be measured' % plural(unmeasured, 'object'),
'They are left out of the totals')
if '__commandpath__' in globals():
run()bundle.yaml The bundle contract: the keys pyACAD reads for this tool. View on GitHubtitle: Length Takeoff
tooltip: Totals the length of lines, arcs, polylines and splines per layer, in the selection or else all of model space, in the drawing units. Duct and pipe runs at a glance, with an Excel export.
context: doc
keytip: TL
alias: LTO