ymg3 Posted September 1 Posted September 1 Been playing quite a bit with Gilles Chanteau's kdtree lisp app published at theSwamp kd-tree (AutoLISP). So decided to turn it into a dll to use from autolisp. I am attaching the C# code below and a bat file to compile it for Autocad 2017. The dll will create the following function for autolisp: KDtreeLisp.dll Reference & Command SyntaxIn AutoLISP documentation standards, optional parameters are enclosed in square brackets [optional]. (KD-BUILD pointList [dimensions] [customHandle]) Builds a KD-Tree spatial index from a list of points. Arguments: pointList: LIST — List of 2D or 3D point lists ((x y z) ...). dimensions (optional): INT — 2 for 2D $(X,Y)$ or 3 for 3D $(X,Y,Z)$. Default is 2. customHandle (optional): STR — Custom string handle identifier. Returns: STR handle (e.g., "<KD-TREE-1>" or "<KD-TREE-SURVEY>"), or nil on failure. (KD-NEAREST handle targetPoint [count] [maxRadius]) Finds the nearest n points to a target point. Arguments: handle: STR — KD-Tree handle string. targetPoint: LIST — Target point (x y z). count (optional): INT — Number of nearest points to retrieve. Default is 1. maxRadius (optional): REAL or INT — Maximum search radius limit. Points farther than this distance are ignored. Returns:If count = 1 (or omitted): A single point (x y z). If count > 1: A list of points ((x1 y1 z1) (x2 y2 z2) ...). (KD-NEAREST-INFO handle targetPoint [count] [maxRadius]) Finds nearest points with detailed metadata. Arguments: handle: STR — KD-Tree handle string. targetPoint: LIST — Target point (x y z). count (optional): INT — Number of points to retrieve. Default is 1. maxRadius (optional): REAL or INT — Maximum search radius limit. Returns: A list of detailed entries (((x y z) index distance) ...). (KD-RANGE handle centerPoint radius) Finds all points inside a fixed search radius without a count limit. Arguments: handle: STR — KD-Tree handle string. centerPoint: LIST — Center search coordinate (x y z). radius: REAL or INT — Search radius. Returns: A list of all enclosed points ((x1 y1 z1) (x2 y2 z2) ...) sorted by distance, or nil if none are found. (KD-FREE [handle]) Frees tree memory. Arguments: handle (optional): STR — Tree handle to free. If omitted, clears all active trees. Returns: INT — Number of trees removed. Once you've compiled the C#, kdtreelisp.dll will be created and ready to use in Autocad. using System; using System.Collections.Generic; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.Geometry; using Autodesk.AutoCAD.Runtime; namespace KdTreeLisp { // ========================================== // 1. DATA STRUCTURES & TREE ENGINE // ========================================== public class KdItem { public Point3d Point { get; set; } public int Id { get; set; } public KdItem(Point3d pt, int id) { Point = pt; Id = id; } } public class KdNode { public Point3d Point { get; set; } public int Id { get; set; } public KdNode Left { get; set; } public KdNode Right { get; set; } public KdNode(Point3d point, int id) { Point = point; Id = id; } } public class NeighborResult : IComparable<NeighborResult> { public KdNode Node { get; set; } public double DistanceSq { get; set; } public NeighborResult(KdNode node, double distSq) { Node = node; DistanceSq = distSq; } public int CompareTo(NeighborResult other) { return other.DistanceSq.CompareTo(this.DistanceSq); } } public class KdTree { public KdNode Root { get; private set; } public int ActiveDimensions { get; private set; } public void Build(List<KdItem> items, int activeDimensions) { ActiveDimensions = Math.Max(1, Math.Min(3, activeDimensions)); Root = BuildRecursive(items, 0); } private KdNode BuildRecursive(List<KdItem> items, int depth) { if (items == null || items.Count == 0) return null; int axis = depth % ActiveDimensions; items.Sort(delegate(KdItem a, KdItem b) { return GetAxisValue(a.Point, axis).CompareTo(GetAxisValue(b.Point, axis)); }); int medianIndex = items.Count / 2; KdNode node = new KdNode(items[medianIndex].Point, items[medianIndex].Id); node.Left = BuildRecursive(items.GetRange(0, medianIndex), depth + 1); node.Right = BuildRecursive(items.GetRange(medianIndex + 1, items.Count - (medianIndex + 1)), depth + 1); return node; } public double CalculateDistanceSq(Point3d p1, Point3d p2) { double dx = p1.X - p2.X; double dy = p1.Y - p2.Y; if (ActiveDimensions == 2) { return dx * dx + dy * dy; } double dz = p1.Z - p2.Z; return dx * dx + dy * dy + dz * dz; } // K-Nearest Neighbor Search with Optional Maximum Radius Limit public List<NeighborResult> FindNearest(Point3d target, int count, double maxRadius = double.MaxValue) { List<NeighborResult> heap = new List<NeighborResult>(); if (Root == null || count <= 0) return heap; double maxDistSq = (maxRadius == double.MaxValue) ? double.MaxValue : maxRadius * maxRadius; SearchNearest(Root, target, count, maxDistSq, heap, 0); // Sort ascending by distance before returning heap.Sort(delegate(NeighborResult a, NeighborResult b) { return a.DistanceSq.CompareTo(b.DistanceSq); }); return heap; } private void SearchNearest(KdNode current, Point3d target, int count, double maxDistSq, List<NeighborResult> heap, int depth) { if (current == null) return; double distSq = CalculateDistanceSq(current.Point, target); // Accept point only if inside maximum distance threshold if (distSq <= maxDistSq) { if (heap.Count < count) { heap.Add(new NeighborResult(current, distSq)); heap.Sort(); } else if (distSq < heap[0].DistanceSq) { heap[0] = new NeighborResult(current, distSq); heap.Sort(); } } int axis = depth % ActiveDimensions; double diff = GetAxisValue(target, axis) - GetAxisValue(current.Point, axis); KdNode primary = diff < 0 ? current.Left : current.Right; KdNode secondary = diff < 0 ? current.Right : current.Left; SearchNearest(primary, target, count, maxDistSq, heap, depth + 1); // Pruning condition: check if secondary subtree could contain points closer than current worst candidate double currentSearchRadiusSq = (heap.Count < count) ? maxDistSq : Math.Min(heap[0].DistanceSq, maxDistSq); if (diff * diff < currentSearchRadiusSq) { SearchNearest(secondary, target, count, maxDistSq, heap, depth + 1); } } // Range Search: Retrieves ALL points within radius public List<NeighborResult> RangeSearch(Point3d center, double radius) { List<NeighborResult> results = new List<NeighborResult>(); if (Root == null || radius < 0) return results; double radiusSq = radius * radius; SearchRangeRecursive(Root, center, radiusSq, results, 0); results.Sort(delegate(NeighborResult a, NeighborResult b) { return a.DistanceSq.CompareTo(b.DistanceSq); }); return results; } private void SearchRangeRecursive(KdNode current, Point3d center, double radiusSq, List<NeighborResult> results, int depth) { if (current == null) return; double distSq = CalculateDistanceSq(current.Point, center); if (distSq <= radiusSq) { results.Add(new NeighborResult(current, distSq)); } int axis = depth % ActiveDimensions; double diff = GetAxisValue(center, axis) - GetAxisValue(current.Point, axis); if (diff < 0) { SearchRangeRecursive(current.Left, center, radiusSq, results, depth + 1); if (diff * diff <= radiusSq) { SearchRangeRecursive(current.Right, center, radiusSq, results, depth + 1); } } else { SearchRangeRecursive(current.Right, center, radiusSq, results, depth + 1); if (diff * diff <= radiusSq) { SearchRangeRecursive(current.Left, center, radiusSq, results, depth + 1); } } } private double GetAxisValue(Point3d pt, int axis) { switch (axis) { case 0: return pt.X; case 1: return pt.Y; default: return pt.Z; } } } // ========================================== // 2. AUTOLISP INTERFACE BRIDGE // ========================================== public class LispBridge { private static readonly Dictionary<string, KdTree> _trees = new Dictionary<string, KdTree>(); private static int _treeCounter = 1; // Signature: (KD-BUILD pointList [dimensions] [customHandle]) [LispFunction("KD-BUILD")] public static TypedValue BuildTree(ResultBuffer args) { if (args == null) return new TypedValue((int)LispDataType.Nil); TypedValue[] arr = args.AsArray(); List<KdItem> items = new List<KdItem>(); int idCounter = 0; int requestedDimensions = 2; string customHandle = null; foreach (TypedValue tv in arr) { if (tv.TypeCode == (int)LispDataType.Point3d) { items.Add(new KdItem((Point3d)tv.Value, idCounter++)); } else if (tv.TypeCode == (int)LispDataType.Point2d) { Point2d pt2 = (Point2d)tv.Value; items.Add(new KdItem(new Point3d(pt2.X, pt2.Y, 0.0), idCounter++)); } else if (tv.TypeCode == (int)LispDataType.Int16 || tv.TypeCode == (int)LispDataType.Int32) { requestedDimensions = Convert.ToInt32(tv.Value); } else if (tv.TypeCode == (int)LispDataType.Text) { customHandle = Convert.ToString(tv.Value); } } if (items.Count == 0) return new TypedValue((int)LispDataType.Nil); KdTree tree = new KdTree(); tree.Build(items, requestedDimensions); string handle = string.IsNullOrEmpty(customHandle) ? string.Format("<KD-TREE-{0}>", _treeCounter++) : string.Format("<KD-TREE-{0}>", customHandle.ToUpper()); _trees[handle] = tree; return new TypedValue((int)LispDataType.Text, handle); } // Signature: (KD-NEAREST handle targetPoint [count] [maxRadius]) [LispFunction("KD-NEAREST")] public static ResultBuffer FindNearest(ResultBuffer args) { if (args == null) return null; List<TypedValue> values = ExtractValues(args); if (values.Count < 2) return null; if (values[0].TypeCode != (int)LispDataType.Text) return null; string handle = Convert.ToString(values[0].Value); if (!_trees.ContainsKey(handle)) return null; KdTree tree = _trees[handle]; if (tree.Root == null) return null; Point3d target; if (values[1].TypeCode == (int)LispDataType.Point3d) { target = (Point3d)values[1].Value; } else if (values[1].TypeCode == (int)LispDataType.Point2d) { Point2d p2 = (Point2d)values[1].Value; target = new Point3d(p2.X, p2.Y, 0.0); } else { return null; } int count = 1; if (values.Count > 2 && (values[2].TypeCode == (int)LispDataType.Int16 || values[2].TypeCode == (int)LispDataType.Int32)) { count = Convert.ToInt32(values[2].Value); } double maxRadius = double.MaxValue; if (values.Count > 3) { TypedValue v = values[3]; if (v.TypeCode == (int)LispDataType.Double || v.TypeCode == (int)LispDataType.Int16 || v.TypeCode == (int)LispDataType.Int32) { maxRadius = Convert.ToDouble(v.Value); } } List<NeighborResult> results = tree.FindNearest(target, count, maxRadius); if (results.Count == 0) return null; ResultBuffer res = new ResultBuffer(); if (count == 1) { res.Add(new TypedValue((int)LispDataType.Point3d, results[0].Node.Point)); } else { res.Add(new TypedValue((int)LispDataType.ListBegin)); foreach (NeighborResult item in results) { res.Add(new TypedValue((int)LispDataType.Point3d, item.Node.Point)); } res.Add(new TypedValue((int)LispDataType.ListEnd)); } return res; } // Signature: (KD-NEAREST-INFO handle targetPoint [count] [maxRadius]) [LispFunction("KD-NEAREST-INFO")] public static ResultBuffer FindNearestInfo(ResultBuffer args) { if (args == null) return null; List<TypedValue> values = ExtractValues(args); if (values.Count < 2) return null; if (values[0].TypeCode != (int)LispDataType.Text) return null; string handle = Convert.ToString(values[0].Value); if (!_trees.ContainsKey(handle)) return null; KdTree tree = _trees[handle]; Point3d target; if (values[1].TypeCode == (int)LispDataType.Point3d) target = (Point3d)values[1].Value; else if (values[1].TypeCode == (int)LispDataType.Point2d) target = new Point3d(((Point2d)values[1].Value).X, ((Point2d)values[1].Value).Y, 0.0); else return null; int count = 1; if (values.Count > 2 && (values[2].TypeCode == (int)LispDataType.Int16 || values[2].TypeCode == (int)LispDataType.Int32)) count = Convert.ToInt32(values[2].Value); double maxRadius = double.MaxValue; if (values.Count > 3) { TypedValue v = values[3]; if (v.TypeCode == (int)LispDataType.Double || v.TypeCode == (int)LispDataType.Int16 || v.TypeCode == (int)LispDataType.Int32) maxRadius = Convert.ToDouble(v.Value); } List<NeighborResult> results = tree.FindNearest(target, count, maxRadius); if (results.Count == 0) return null; ResultBuffer res = new ResultBuffer(); res.Add(new TypedValue((int)LispDataType.ListBegin)); foreach (NeighborResult item in results) { res.Add(new TypedValue((int)LispDataType.ListBegin)); res.Add(new TypedValue((int)LispDataType.Point3d, item.Node.Point)); res.Add(new TypedValue((int)LispDataType.Int32, item.Node.Id)); res.Add(new TypedValue((int)LispDataType.Double, Math.Sqrt(item.DistanceSq))); res.Add(new TypedValue((int)LispDataType.ListEnd)); } res.Add(new TypedValue((int)LispDataType.ListEnd)); return res; } // Signature: (KD-RANGE handle centerPoint radius) [LispFunction("KD-RANGE")] public static ResultBuffer RangeSearch(ResultBuffer args) { if (args == null) return null; List<TypedValue> values = ExtractValues(args); if (values.Count < 3) return null; if (values[0].TypeCode != (int)LispDataType.Text) return null; string handle = Convert.ToString(values[0].Value); if (!_trees.ContainsKey(handle)) return null; KdTree tree = _trees[handle]; Point3d center; if (values[1].TypeCode == (int)LispDataType.Point3d) center = (Point3d)values[1].Value; else if (values[1].TypeCode == (int)LispDataType.Point2d) center = new Point3d(((Point2d)values[1].Value).X, ((Point2d)values[1].Value).Y, 0.0); else return null; double radius = 0.0; TypedValue rVal = values[2]; if (rVal.TypeCode == (int)LispDataType.Double || rVal.TypeCode == (int)LispDataType.Int16 || rVal.TypeCode == (int)LispDataType.Int32) { radius = Convert.ToDouble(rVal.Value); } else { return null; } List<NeighborResult> results = tree.RangeSearch(center, radius); if (results.Count == 0) return null; ResultBuffer res = new ResultBuffer(); res.Add(new TypedValue((int)LispDataType.ListBegin)); foreach (NeighborResult item in results) { res.Add(new TypedValue((int)LispDataType.Point3d, item.Node.Point)); } res.Add(new TypedValue((int)LispDataType.ListEnd)); return res; } // Signature: (KD-FREE [handle]) [LispFunction("KD-FREE")] public static TypedValue FreeTree(ResultBuffer args) { if (args == null) { int count = _trees.Count; _trees.Clear(); return new TypedValue((int)LispDataType.Int32, count); } List<TypedValue> values = ExtractValues(args); if (values.Count == 0 || values[0].TypeCode != (int)LispDataType.Text) { int count = _trees.Count; _trees.Clear(); return new TypedValue((int)LispDataType.Int32, count); } string handle = Convert.ToString(values[0].Value); bool removed = _trees.Remove(handle); return new TypedValue((int)LispDataType.Int32, removed ? 1 : 0); } private static List<TypedValue> ExtractValues(ResultBuffer resbuf) { List<TypedValue> list = new List<TypedValue>(); foreach (TypedValue tv in resbuf.AsArray()) { if (tv.TypeCode != (int)LispDataType.ListBegin && tv.TypeCode != (int)LispDataType.ListEnd) { list.Add(tv); } } return list; } } } ymg KDtreeLisp.cs Build.bat 2 Quote
BIGAL Posted September 1 Posted September 1 Whilst I probably won't use it, have you looked at compiling for Non Acad, say Bricscad a lot of users here. Quote
ymg3 Posted September 1 Author Posted September 1 I am kind of a Green Horn when it comes to compiling. I don't even have vstudio. So I still use the legacy csc compiler. On top of that i only have access to acad 2017, so I am not aware of what would be needed to compile it for more recent version of Autocad. 1 Quote
Danielm103 Posted September 2 Posted September 2 (edited) Nice! I used Nanoflann for the python wrappers, Ge.Point2dTree, Ge.Point3dTree https://github.com/jlblancoc/nanoflann it builds the tree multi-threaded. Nanoflann can be made dynamic in that it can add or remove points, though I didn’t add that in to the python wrappers. I also created a wrapper for AutoLISP here https://github.com/CEXT-Dan/ads_geo Wrappers are limited though, in C++ you can create a payload I.e. { Point Data } Otherwise, you have to create a hashmap to link the point with the payload. A professional C# version might use an interface for X,Y,Z, so you can expand the tree to allow any class or structure that has the proper interface Edited September 2 by Danielm103 1 Quote
Danielm103 Posted September 2 Posted September 2 1 hour ago, BIGAL said: Whilst I probably won't use it, have you looked at compiling for Non Acad, say Bricscad a lot of users here. all you would need to do is edit the bat file to point at the target platform. AI says you can jut create a project file next to the .cs, then use “dotnet build -c Release” <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <!-- Use net48 for AutoCAD 2021-2024, or net8.0-windows for AutoCAD 2025+ --> <TargetFramework>net8.0-windows</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> <Platforms>x64</Platforms> </PropertyGroup> <!-- Reference your AutoCAD libraries --> <ItemGroup> <Reference Include="AcCoreMgd"> <HintPath>C:\Program Files\Autodesk\AutoCAD 2017\AcCoreMgd.dll</HintPath> <Private>False</Private> <!-- Prevents copying AutoCAD DLLs to your output folder --> </Reference> <Reference Include="AcDbMgd"> <HintPath>C:\Program Files\Autodesk\AutoCAD 2017\AcDbMgd.dll</HintPath> <Private>False</Private> </Reference> <Reference Include="AcMgd"> <HintPath>C:\Program Files\Autodesk\AutoCAD 2017\AcMgd.dll</HintPath> <Private>False</Private> </Reference> </ItemGroup> </Project> 2 Quote
ymg3 Posted September 2 Author Posted September 2 @Danielm103 Interesting, so many ways to skin a cat. 1 Quote
gile Posted Monday at 02:09 PM Posted Monday at 02:09 PM (edited) Hi, The GeometryExtensions.NET library provides a generic KdTree<T> class which uses parallel computation for building the tree. It also provides an example of Point3dTree class but can used with any type of object providing a 'getPosition' function. // public KdTree(IEnumerable<T> source, Func<T, Point3d>?getPosition, int dimension) var tree = new KdTree<BlockReference>(blockList, br => br.Position, 2); Edited Monday at 02:16 PM by gile 1 Quote
ymg3 Posted Monday at 08:52 PM Author Posted Monday at 08:52 PM (edited) @gile, Quote The GeometryExtensions.NET library provides a generic KdTree<T> class which uses parallel computation for building the tree. It also provides an example of Point3dTree class but can used with any type of object providing a 'getPosition' function. Did not know that. At least I learned something while doing mine. I've look at the extension and there are many interesting things in there. Edited Monday at 08:54 PM by ymg3 Quote
ymg3 Posted 53 minutes ago Author Posted 53 minutes ago @gile, Here I build a bridge to your GeometryExtensions.NET library, exposing essentially the same command that I had in mine . The only addition being GC:KD-MINNODE. I have also built a concavehull in C# using your kd-tree if anybody is interested. ymg using System; using System.IO; using System.Linq; using System.Globalization; using System.Collections.Generic; using Autodesk.AutoCAD.Runtime; using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.Geometry; using Gile.AutoCAD.R20.Geometry; namespace LispGileBridge { public class ExtensionLoader : IExtensionApplication { public void Initialize() { try { var ed = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument.Editor; // Extracts the absolute running file path dynamically via Reflection string assemblyPath = System.Reflection.Assembly.GetExecutingAssembly().Location; string assemblyName = System.IO.Path.GetFileName(assemblyPath); ed.WriteMessage($"\n--> Assembly File Loaded: \"{assemblyName}\""); ed.WriteMessage($"\n--> Full Path: {assemblyPath}"); ed.WriteMessage("\n--> Available commands:"); ed.WriteMessage("\n (GC:KD-BUILD pointList [dimensions] [customHandle])"); ed.WriteMessage("\n (GC:KD-NEAREST handle Point [count] [maxRadius] [sortFlag])"); ed.WriteMessage("\n (GC:KD-NEAREST-INFO handle Point [count] [maxRadius])"); ed.WriteMessage("\n (GC:KD-RANGE handle Point radius)"); ed.WriteMessage("\n (GC:KD-MINNODE handle dimension)"); ed.WriteMessage("\n (GC:KD-FREE [handle])"); } catch { // Fallback safety silent escape check } } public void Terminate() { } } public class KdTreeBridge { // Thread-safe dictionary tracking string handles to their specific tree instances and properties private static readonly Dictionary<string, (Point3dTree Tree, bool Is2D)> _registry = new Dictionary<string, (Point3dTree, bool)>(StringComparer.OrdinalIgnoreCase); private static int _autoId = 1; // -------------------------------------------------------------------------------- // 1. (gc:kd-build pointList [dimensions] [customHandle]) // -------------------------------------------------------------------------------- [LispFunction("GC:KD-BUILD")] public static object GileKdBuild(ResultBuffer args) { if (args == null) return null; TypedValue[] arr = args.AsArray(); if (arr.Length == 0) return null; try { List<Point3d> pts = ParseLispPoints(arr, 0, out int pointsReadCount); if (pts.Count == 0) return null; // Default dimension to 2D unless explicitly specified as 3 int dimensions = 2; string customHandle = null; if (arr.Length > pointsReadCount) { int nextIdx = pointsReadCount; if (arr[nextIdx].TypeCode == (int)LispDataType.Int32 || arr[nextIdx].TypeCode == (int)LispDataType.Int16) { dimensions = Convert.ToInt32(arr[nextIdx].Value); nextIdx++; } if (arr.Length > nextIdx && arr[nextIdx].TypeCode == (int)LispDataType.Text) { customHandle = Convert.ToString(arr[nextIdx].Value); } } bool is2D = (dimensions == 2); // Gilles' constructor: ignoreZ parameter directly matches our is2D rule Point3dTree tree = new Point3dTree(pts, is2D); string handle = string.IsNullOrEmpty(customHandle) ? $"<KD-TREE-{_autoId++}>" : customHandle; _registry[handle] = (tree, is2D); return handle; } catch (System.Exception ex) { Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage($"\nError in KD-BUILD: {ex.Message}\n"); return null; } } // -------------------------------------------------------------------------------- // 2. (gc:kd-nearest handle targetPoint [count] [maxRadius] [sortFlag]) // -------------------------------------------------------------------------------- [LispFunction("GC:KD-NEAREST")] public static object GileKdNearest(ResultBuffer args) { if (args == null) return null; TypedValue[] arr = args.AsArray(); if (arr.Length < 2) return null; try { string handle = Convert.ToString(arr[0].Value); if (!_registry.TryGetValue(handle, out var entry)) return null; if (arr[1].TypeCode != (int)LispDataType.Point3d) return null; Point3d targetPt = (Point3d)arr[1].Value; int count = 1; double maxRadius = double.MaxValue; int sortFlag = 1; // Default: Sort by Angle if (arr.Length >= 3) count = Convert.ToInt32(arr[2].Value); if (arr.Length >= 4) maxRadius = Convert.ToDouble(arr[3].Value); if (arr.Length >= 5) sortFlag = Convert.ToInt32(arr[4].Value); var searchCollection = entry.Tree.GetNearestNeighbours(targetPt, count); if (searchCollection == null) return null; // Filter by radius limit and compile working list List<Point3d> filtered = new List<Point3d>(); foreach (Point3d pt in searchCollection) { double dist = entry.Is2D ? targetPt.DistanceTo(new Point3d(pt.X, pt.Y, targetPt.Z)) : targetPt.DistanceTo(pt); if (dist <= maxRadius) filtered.Add(pt); } if (filtered.Count == 0) return null; // Execute selected Sort mode if (sortFlag == 0) { filtered = filtered.OrderBy(p => entry.Is2D ? targetPt.DistanceTo(new Point3d(p.X, p.Y, targetPt.Z)) : targetPt.DistanceTo(p)).ToList(); } else { filtered = filtered.OrderBy(p => { double angle = Math.Atan2(p.Y - targetPt.Y, p.X - targetPt.X); return angle < 0 ? angle + (2 * Math.PI) : angle; }).ToList(); } // API Standard Rule Check: If count is 1, return single item. Else, return list. if (count == 1) { return filtered[0]; } ResultBuffer rb = new ResultBuffer(); rb.Add(new TypedValue((int)LispDataType.ListBegin)); foreach (Point3d pt in filtered) rb.Add(new TypedValue((int)LispDataType.Point3d, pt)); rb.Add(new TypedValue((int)LispDataType.ListEnd)); return rb; } catch { return null; } } // -------------------------------------------------------------------------------- // 3. (gc:kd-nearest-info handle targetPoint [count] [maxRadius]) // -------------------------------------------------------------------------------- [LispFunction("GC:KD-NEAREST-INFO")] public static object GileKdNearestInfo(ResultBuffer args) { if (args == null) return null; TypedValue[] arr = args.AsArray(); if (arr.Length < 2) return null; try { string handle = Convert.ToString(arr[0].Value); if (!_registry.TryGetValue(handle, out var entry)) return null; Point3d targetPt = (Point3d)arr[1].Value; int count = 1; double maxRadius = double.MaxValue; if (arr.Length >= 3) count = Convert.ToInt32(arr[2].Value); if (arr.Length >= 4) maxRadius = Convert.ToDouble(arr[3].Value); var searchCollection = entry.Tree.GetNearestNeighbours(targetPt, count); if (searchCollection == null) return null; ResultBuffer rb = new ResultBuffer(); rb.Add(new TypedValue((int)LispDataType.ListBegin)); // Re-locate matches to extract array index mappings cleanly int idx = 0; foreach (Point3d pt in searchCollection) { double dist = entry.Is2D ? targetPt.DistanceTo(new Point3d(pt.X, pt.Y, targetPt.Z)) : targetPt.DistanceTo(pt); if (dist <= maxRadius) { rb.Add(new TypedValue((int)LispDataType.ListBegin)); rb.Add(new TypedValue((int)LispDataType.Point3d, pt)); rb.Add(new TypedValue((int)LispDataType.Int32, idx)); // Metadata index placement rb.Add(new TypedValue((int)LispDataType.Double, dist)); rb.Add(new TypedValue((int)LispDataType.ListEnd)); } idx++; } rb.Add(new TypedValue((int)LispDataType.ListEnd)); return rb; } catch { return null; } } // -------------------------------------------------------------------------------- // 4. (gc:kd-range handle centerPoint radius) // -------------------------------------------------------------------------------- [LispFunction("GC:KD_RANGE")] public static object GileKdRange(ResultBuffer args) { if (args == null) return null; TypedValue[] arr = args.AsArray(); if (arr.Length < 3) return null; try { string handle = Convert.ToString(arr[0].Value); if (!_registry.TryGetValue(handle, out var entry)) return null; Point3d centerPt = (Point3d)arr[1].Value; double radius = Convert.ToDouble(arr[2].Value); // Passing a double directly to GetNearestNeighbours switches Gilles' engine to range search var searchCollection = entry.Tree.GetNearestNeighbours(centerPt, radius); if (searchCollection == null) return null; // Enforce distance sort ordering var sorted = searchCollection.OrderBy(p => entry.Is2D ? centerPt.DistanceTo(new Point3d(p.X, p.Y, centerPt.Z)) : centerPt.DistanceTo(p)).ToList(); ResultBuffer rb = new ResultBuffer(); rb.Add(new TypedValue((int)LispDataType.ListBegin)); foreach (Point3d pt in sorted) rb.Add(new TypedValue((int)LispDataType.Point3d, pt)); rb.Add(new TypedValue((int)LispDataType.ListEnd)); return rb; } catch { return null; } } // -------------------------------------------------------------------------------- // 5. NEW! (gc:kd-minnode handle dimension) -> Useful for Concave Hull base logic // -------------------------------------------------------------------------------- [LispFunction("GC:KD-MINNODE")] public static object GileKdMinNode(ResultBuffer args) { if (args == null) return null; TypedValue[] arr = args.AsArray(); if (arr.Length < 2) return null; try { string handle = Convert.ToString(arr[0].Value); if (!_registry.TryGetValue(handle, out var entry)) return null; int dimension = Convert.ToInt32(arr[1].Value); // 0=X, 1=Y, 2=Z // Target standard range query over all nodes to run aggregate matching var allPoints = entry.Tree.GetNearestNeighbours(Point3d.Origin, double.MaxValue); if (allPoints == null || !allPoints.Any()) return null; Point3d minPoint; switch (dimension) { case 0: // Minimum X coordinate minPoint = allPoints.OrderBy(p => p.X).First(); break; case 1: // Minimum Y coordinate minPoint = allPoints.OrderBy(p => p.Y).First(); break; case 2: // Minimum Z coordinate minPoint = allPoints.OrderBy(p => p.Z).First(); break; default: return null; } return minPoint; } catch { return null; } } // -------------------------------------------------------------------------------- // 6. (gc:kd-free [handle]) // -------------------------------------------------------------------------------- [LispFunction("GC:KD-FREE")] public static object GileKdFree(ResultBuffer args) { int removedCount = 0; if (args == null) { removedCount = _registry.Count; _registry.Clear(); _autoId = 1; } else { TypedValue[] arr = args.AsArray(); if (arr.Length > 0 && arr[0].TypeCode == (int)LispDataType.Text) { string handle = Convert.ToString(arr[0].Value); if (_registry.Remove(handle)) removedCount = 1; } else { removedCount = _registry.Count; _registry.Clear(); _autoId = 1; } } GC.Collect(); GC.WaitForPendingFinalizers(); return removedCount; } // -------------------------------------------------------------------------------- // INTERNAL ROUTINE: Streamlined input coordinate list extractor // -------------------------------------------------------------------------------- private static List<Point3d> ParseLispPoints(TypedValue[] arr, int startIndex, out int readCount) { var collected = new List<Point3d>(); readCount = 0; for (int i = startIndex; i < arr.Length; i++) { if (arr[i].Value is Point3d pt) { collected.Add(pt); readCount++; } else if (arr[i].TypeCode == (int)LispDataType.ListEnd || arr[i].TypeCode == (int)LispDataType.Int32 || arr[i].TypeCode == (int)LispDataType.Text) { break; // Stopped at non-point parameter boundaries } } return collected; } } } essentially the same command that I had in my post. Quote
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.