give this a shot and see if you can take anything away from it. small note, i was never able to get it to set the line type prior to activating the detail postable command. so it creates a small line to then set the linetype.
something i made a while back for one line diagrams.
# -*- coding: utf-8 -*-
"""Starts Revit's native Detail Line workflow using a saved default line style.
NORMAL RUN:
1. Loads the saved default line style from pick_config.py.
2. Finds the matching Revit Lines subcategory.
3. Looks for an existing detail line in the active view using that line style.
4. If no matching detail line exists, creates a tiny seed detail line using the saved line style.
5. Selects that line.
6. Posts Revit's native Create Similar command.
SHIFT-CLICK CONFIGURATION:
1. Lists all loaded line styles under the Revit Lines category.
2. User selects one default line style.
3. The selected style name is saved to pyRevit config through pick_config.py.
Why Create Similar:
- Revit does not expose a reliable public API for directly setting the active native Detail Line line style.
- Create Similar from a detail line is the most reliable way to launch the native Detail Line command with the desired style.
"""
import clr
import traceback
clr.AddReference("RevitAPI")
clr.AddReference("RevitAPIUI")
from System.Collections.Generic import List
from Autodesk.Revit.DB import (
BuiltInCategory,
CurveElement,
DetailCurve,
ElementId,
FilteredElementCollector,
GraphicsStyleType,
Line,
Transaction,
View,
ViewType,
XYZ
)
from Autodesk.Revit.UI import (
PostableCommand,
RevitCommandId
)
from pyrevit import revit, forms, script, EXEC_PARAMS
import pick_config
doc = revit.doc
uidoc = revit.uidoc
uiapp = uidoc.Application
output = script.get_output()
# ============================================================
# BASIC HELPERS
# ============================================================
def safe_str(value):
try:
if value is None:
return ""
return str(value)
except:
return ""
def get_id_int(element_id):
try:
return element_id.IntegerValue
except:
try:
return int(element_id)
except:
return -1
# ============================================================
# VIEW HELPERS
# ============================================================
def is_supported_detail_line_view(view):
if not view:
return False
try:
if view.IsTemplate:
return False
except:
pass
try:
view_type = view.ViewType
except:
return False
supported_view_types = [
ViewType.FloorPlan,
ViewType.CeilingPlan,
ViewType.EngineeringPlan,
ViewType.AreaPlan,
ViewType.Section,
ViewType.Elevation,
ViewType.Detail,
ViewType.DraftingView,
ViewType.Legend
]
return view_type in supported_view_types
def get_active_view_center_point(view):
"""
Returns a reasonable point in the active view for creating the seed line.
For most views, Origin works fine.
If CropBox exists, use the crop box center.
"""
try:
crop = view.CropBox
if crop:
min_pt = crop.Min
max_pt = crop.Max
x = (min_pt.X + max_pt.X) / 2.0
y = (min_pt.Y + max_pt.Y) / 2.0
z = (min_pt.Z + max_pt.Z) / 2.0
center = XYZ(x, y, z)
try:
trf = crop.Transform
center = trf.OfPoint(center)
except:
pass
return center
except:
pass
try:
return view.Origin
except:
return XYZ(0, 0, 0)
def get_view_right_direction(view):
try:
right = view.RightDirection
if right:
return right
except:
pass
return XYZ(1, 0, 0)
# ============================================================
# LINE STYLE HELPERS
# ============================================================
def get_lines_category():
try:
return doc.Settings.Categories.get_Item(BuiltInCategory.OST_Lines)
except:
return None
def get_line_style_category_by_name(line_style_name):
line_style_name = safe_str(line_style_name).strip()
if not line_style_name:
return None
lines_cat = get_lines_category()
if not lines_cat:
return None
try:
subcats = lines_cat.SubCategories
except:
return None
for subcat in subcats:
try:
if safe_str(subcat.Name).strip() == line_style_name:
return subcat
except:
pass
return None
def get_projection_graphics_style(line_style_category):
if not line_style_category:
return None
try:
return line_style_category.GetGraphicsStyle(GraphicsStyleType.Projection)
except:
return None
def get_curve_line_style_name(curve_element):
try:
style = curve_element.LineStyle
if style:
return safe_str(style.Name).strip()
except:
pass
return ""
# ============================================================
# FIND OR CREATE SEED DETAIL LINE
# ============================================================
def find_existing_detail_line_in_active_view(view, line_style_name):
"""
Find an existing detail line in the active view that already uses the desired style.
This avoids creating a new seed line when possible.
"""
line_style_name = safe_str(line_style_name).strip()
if not line_style_name:
return None
collector = FilteredElementCollector(doc, view.Id) \
.OfCategory(BuiltInCategory.OST_Lines) \
.WhereElementIsNotElementType()
candidates = []
for el in collector:
try:
if not isinstance(el, CurveElement):
continue
except:
continue
try:
if get_curve_line_style_name(el) == line_style_name:
candidates.append(el)
except:
pass
if not candidates:
return None
candidates = sorted(candidates, key=lambda x: get_id_int(x.Id))
return candidates[0]
def create_seed_detail_line(view, line_style_category):
"""
Creates a small detail line in the active view and assigns the desired style.
Note:
This seed line is intentionally left in the view because Revit needs a selected
source element for Create Similar.
"""
gs = get_projection_graphics_style(line_style_category)
if not gs:
return None
center = get_active_view_center_point(view)
right = get_view_right_direction(view)
# Revit will reject curves shorter than Application.ShortCurveTolerance.
# Use something safely above the tolerance.
try:
min_len = doc.Application.ShortCurveTolerance
except:
min_len = 0.01
# Use at least 1 inch, or 10x Revit's short curve tolerance.
one_inch = 1.0 / 12.0
seed_length = max(one_inch, min_len * 10.0)
p1 = center
p2 = center.Add(right.Multiply(seed_length))
line = Line.CreateBound(p1, p2)
new_curve = None
with Transaction(doc, "NXGN - Create Detail Line Style Seed") as t:
t.Start()
new_curve = doc.Create.NewDetailCurve(view, line)
try:
new_curve.LineStyle = gs
except:
pass
t.Commit()
return new_curve
def get_or_create_source_detail_line(view, line_style_name, line_style_category):
existing = find_existing_detail_line_in_active_view(view, line_style_name)
if existing:
return existing, False
created = create_seed_detail_line(view, line_style_category)
return created, True
# ============================================================
# SELECTION / COMMAND HELPERS
# ============================================================
def set_single_selection(element):
ids = List[ElementId]()
try:
ids.Add(element.Id)
except:
pass
uidoc.Selection.SetElementIds(ids)
def post_create_similar_command():
try:
cmd_id = RevitCommandId.LookupPostableCommandId(PostableCommand.CreateSimilar)
except Exception as ex:
forms.alert(
"Could not find Revit's Create Similar command.\n\n{0}".format(ex),
exitscript=True
)
if not cmd_id:
forms.alert(
"Could not find Revit's Create Similar command.",
exitscript=True
)
try:
can_post = uiapp.CanPostCommand(cmd_id)
except:
can_post = True
if not can_post:
forms.alert(
"Revit cannot start Create Similar right now.\n\n"
"Try again from a plan, section, elevation, drafting view, detail view, or legend.",
exitscript=True
)
try:
uiapp.PostCommand(cmd_id)
except Exception as ex:
forms.alert(
"Failed to start Revit's Create Similar command.\n\n{0}".format(ex),
exitscript=True
)
# ============================================================
# NORMAL RUN
# ============================================================
def run_normal():
view = doc.ActiveView
if not is_supported_detail_line_view(view):
forms.alert(
"Detail Lines cannot be started from the current active view.\n\n"
"Active View: {0}\n"
"View Type: {1}".format(view.Name, view.ViewType),
exitscript=True
)
line_style_name = pick_config.get_default_line_style_name_or_configure()
if not line_style_name:
forms.alert(
"No default line style was selected.",
exitscript=True
)
line_style_category = get_line_style_category_by_name(line_style_name)
if not line_style_category:
forms.alert(
"The saved default line style was not found in this project:\n\n{0}\n\n"
"Shift-click the tool to choose a new default.".format(line_style_name),
exitscript=True
)
source_line, was_created = get_or_create_source_detail_line(
view,
line_style_name,
line_style_category
)
if not source_line:
forms.alert(
"Could not find or create a source detail line for this line style:\n\n{0}".format(line_style_name),
exitscript=True
)
set_single_selection(source_line)
post_create_similar_command()
if was_created:
output.close_others()
output.print_md("# NXGN | Detail Line By Style")
output.print_md("")
output.print_md("## Seed Detail Line Created")
output.print_md("")
output.print_md("A tiny source detail line was created because no existing detail line in the active view used the saved style.")
output.print_md("")
output.print_md("| Field | Value |")
output.print_md("|---|---|")
output.print_md("| Saved Line Style | {0} |".format(line_style_name))
output.print_md("| Seed Detail Line | {0} |".format(output.linkify(source_line.Id)))
output.print_md("")
output.print_md("This seed line is used only so Revit can run **Create Similar** with the correct line style.")
# ============================================================
# ENTRY
# ============================================================
if __name__ == "__main__":
try:
if EXEC_PARAMS.config_mode:
pick_config.configure_default()
else:
run_normal()
except Exception as ex:
output.print_md("# NXGN | Detail Line By Style")
output.print_md("")
output.print_md("## Error")
output.print_md("")
output.print_md("```")
output.print_md(str(ex))
output.print_md("```")
traceback.print_exc()
# -*- coding: utf-8 -*-
"""Default Detail Line Style configuration for NXGN | Detail Line By Style.
SHIFT-CLICK:
- Lists all loaded line styles under the Revit "Lines" category.
- User selects one default line style.
- Saved default becomes the line style used when the normal tool is run.
NORMAL RUN:
- Loads the saved default line style name.
- If no default exists, prompts user to configure one.
"""
from pyrevit import revit, DB
from pyrevit import forms
from pyrevit import script
my_config = script.get_config("NXGN_DefaultDetailLineStyle")
KEY_DEFAULT_LINE_STYLE_NAME = "default_detail_line_style_name"
# ============================================================
# BASIC HELPERS
# ============================================================
def safe_str(value):
try:
if value is None:
return ""
return str(value)
except:
return ""
def get_line_style_name_from_option(option):
try:
return option.line_style_name
except:
pass
try:
return option.name
except:
pass
try:
return str(option)
except:
return ""
# ============================================================
# LIST ITEM
# ============================================================
class LineStyleItem(forms.TemplateListItem):
def __init__(self, line_style_name, checked=False):
forms.TemplateListItem.__init__(self, line_style_name, checked=checked)
self.line_style_name = line_style_name
@property
def name(self):
return self.line_style_name
# ============================================================
# CONFIG LOAD / SAVE
# ============================================================
def load_config():
saved = my_config.get_option(KEY_DEFAULT_LINE_STYLE_NAME, "")
return safe_str(saved).strip()
def save_config(line_style_name):
line_style_name = safe_str(line_style_name).strip()
setattr(my_config, KEY_DEFAULT_LINE_STYLE_NAME, line_style_name)
script.save_config()
# ============================================================
# LINE STYLE COLLECTION
# ============================================================
def get_lines_category():
try:
return revit.doc.Settings.Categories.get_Item(DB.BuiltInCategory.OST_Lines)
except:
return None
def collect_line_style_names():
line_style_names = []
lines_cat = get_lines_category()
if not lines_cat:
return line_style_names
try:
subcats = lines_cat.SubCategories
except:
return line_style_names
for subcat in subcats:
try:
name = safe_str(subcat.Name).strip()
if name and name not in line_style_names:
line_style_names.append(name)
except:
pass
return sorted(line_style_names, key=lambda x: x.lower())
def line_style_exists(line_style_name):
line_style_name = safe_str(line_style_name).strip()
if not line_style_name:
return False
return line_style_name in collect_line_style_names()
# ============================================================
# SHARED PICKER
# ============================================================
def pick_line_style(title, button_name, prechecked_line_style_name):
line_style_names = collect_line_style_names()
if not line_style_names:
forms.alert(
"No line styles were found under the Revit Lines category.",
exitscript=True
)
prechecked_line_style_name = safe_str(prechecked_line_style_name).strip()
options = []
for name in line_style_names:
checked = name == prechecked_line_style_name
options.append(LineStyleItem(name, checked=checked))
selected = forms.SelectFromList.show(
options,
title=title,
button_name=button_name,
multiselect=False
)
if selected is None:
script.exit()
selected_name = get_line_style_name_from_option(selected)
selected_name = safe_str(selected_name).strip()
if not selected_name:
script.exit()
return selected_name
# ============================================================
# SHIFT-CLICK CONFIG UI
# ============================================================
def configure_default():
previous_name = load_config()
selected_name = pick_line_style(
title="NXGN | Default Detail Line Style",
button_name="Save Default",
prechecked_line_style_name=previous_name
)
save_config(selected_name)
output = script.get_output()
output.close_others()
output.print_md("# NXGN | Default Detail Line Style")
output.print_md("")
output.print_md("## Default Line Style Saved")
output.print_md("")
output.print_md("| Setting | Value |")
output.print_md("|---|---|")
output.print_md("| Default Detail Line Style | {0} |".format(selected_name))
return selected_name
# ============================================================
# NORMAL-RUN LOAD
# ============================================================
def get_default_line_style_name_or_configure():
saved_name = load_config()
if saved_name and line_style_exists(saved_name):
return saved_name
if saved_name and not line_style_exists(saved_name):
forms.alert(
"The saved default line style was not found in this project:\n\n{0}\n\n"
"Please select a new default.".format(saved_name)
)
return configure_default()
if __name__ == "__main__":
configure_default()