Last active
February 6, 2026 21:20
-
-
Save CEXT-Dan/d802bf84977ecf16c98a102c40bc1c0d to your computer and use it in GitHub Desktop.
Clip polylines to extents
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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