Skip to content

Instantly share code, notes, and snippets.

@CEXT-Dan
Last active February 6, 2026 21:20
Show Gist options
  • Select an option

  • Save CEXT-Dan/d802bf84977ecf16c98a102c40bc1c0d to your computer and use it in GitHub Desktop.

Select an option

Save CEXT-Dan/d802bf84977ecf16c98a102c40bc1c0d to your computer and use it in GitHub Desktop.
Clip polylines to extents
import traceback
from pyrx import Ap, Db, Ed, Ge
from typing import NamedTuple
from collections import defaultdict
import pandas as pd
# https://forums.autodesk.com/t5/visual-lisp-autolisp-and-general/counting-blocks-and-polyline-length-inside-a-block-boundary/m-p/14007712#M169893
class View(NamedTuple):
id: Db.ObjectId
ex: Db.Extents2d
blocks: list[Db.ObjectId]
segs: list[float]
def get_count(self) -> int:
return len(self.blocks)
def get_total_length(self) -> float:
return sum(self.segs)
def is_inside(self, other_ex: Db.Extents2d) -> bool:
return self.ex.contains(other_ex)
def is_point_inside(self, pos: Ge.Point2d) -> bool:
return self.ex.contains(pos)
def pl_clip_arc_seg(self, _arc: Ge.CircArc2d):
pass # ignore
# if self.ex.contains(_arc.center()):
# interval = _arc.getInterval()
# startParam, endParam = interval.getBounds()
# self.segs.append(_arc.length(startParam, endParam))
def pl_clip_line_seg(self, _seg2: Ge.LineSeg2d):
"""
Clips a line segment to the view's extents using the
Liang–Barsky algorithm and adds the clipped length to self.segs.
"""
min_p = self.ex.minPoint()
max_p = self.ex.maxPoint()
p1 = _seg2.startPoint()
p2 = _seg2.endPoint()
x1, y1 = p1.x, p1.y
x2, y2 = p2.x, p2.y
dx = x2 - x1
dy = y2 - y1
# Parametric interval
u1 = 0.0
u2 = 1.0
def clip(p: float, q: float) -> bool:
nonlocal u1, u2
if p == 0.0:
# Line is parallel to boundary
return q >= 0.0
r = q / p
if p < 0.0:
if r > u2:
return False
if r > u1:
u1 = r
else: # p > 0
if r < u1:
return False
if r < u2:
u2 = r
return True
# Left, Right, Bottom, Top
if (
clip(-dx, x1 - min_p.x)
and clip(dx, max_p.x - x1)
and clip(-dy, y1 - min_p.y)
and clip(dy, max_p.y - y1)
):
if u2 >= u1:
cx1 = x1 + u1 * dx
cy1 = y1 + u1 * dy
cx2 = x1 + u2 * dx
cy2 = y1 + u2 * dy
clipped_seg = Ge.LineSeg2d(Ge.Point2d(cx1, cy1), Ge.Point2d(cx2, cy2))
self.segs.append(clipped_seg.length())
def do_kung_fu(ss: Ed.SelectionSet) -> list[View]:
"""
collects objects from drawing, polulates the return value
is a list[View]
"""
view_refs: list[Db.BlockReference] = []
blk_refs: list[Db.BlockReference] = []
plines: list[Db.Polyline] = []
_blk_f = set()
# Categorize objects
ids = ss.objectIds()
for id in ids:
if id.isDerivedFrom(Db.BlockReference.desc()):
ref = Db.BlockReference(id)
name = ref.getBlockName()
if name.casefold() == "VIEW".casefold():
view_refs.append(ref)
elif name.casefold() == "BLK_COUNT".casefold():
blk_refs.append(ref)
elif id.isDerivedFrom(Db.Polyline.desc()):
plines.append(Db.Polyline(id))
# Initialize View objects
views: list[View] = []
for _ref in view_refs:
views.append(View(_ref.objectId(), _ref.getGeomExtents2d(), [], []))
# Associate BLK_COUNT blocks with Views
for _blk in blk_refs:
if _blk.objectId() in _blk_f:
continue
for _view in views:
pos = _blk.position()
if _view.is_point_inside(Ge.Point2d(pos.x, pos.y)):
_view.blocks.append(_blk.objectId())
_blk_f.add(_blk.objectId())
# Extract segments and clip them to views
for pline in plines:
for idx in range(pline.numVerts() - 1):
# Check if segment is an arc (bulge != 0)
if pline.getBulgeAt(idx) != 0:
seg = pline.getArcSeg2dAt(idx)
for _view in views:
_view.pl_clip_arc_seg(seg)
else:
seg = pline.getLineSeg2dAt(idx)
for _view in views:
_view.pl_clip_line_seg(seg)
return views
# Command: THE_CLIPPER
@Ap.Command()
def the_clipper():
"""
the command
"""
try:
ps, ss = Ed.Editor.select()
if ps != Ed.PromptStatus.eOk:
return # Exit cleanly if nothing selected
data = defaultdict(list)
views = do_kung_fu(ss)
# apply to attibutes collect for pandas
for view in views:
ref = Db.BlockReference(view.id)
atts = [
Db.AttributeReference(id, Db.OpenMode.kForWrite)
for id in ref.attributeIds()
]
for att in atts:
if att.tag() == "NUMBER":
data["NUMBER"].append(att.textString())
if att.tag() == "TOTAL_BLOCKS":
att.setTextString(str(view.get_count()))
data["TOTAL_BLOCKS"].append(att.textString())
elif att.tag() == "LENGTH_POLYLINES":
att.setTextString("{:.2f}".format(view.get_total_length()))
data["LENGTH_POLYLINES"].append(att.textString())
df = pd.DataFrame(data)
df_sorted = df.sort_values(by="NUMBER")
# print(df)
df_sorted.to_excel("m:\\output.xlsx", index=False)
except Exception as err:
traceback.print_exc()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment