This post continues on from the last one, which introduced some code that creates "Koch curves" inside AutoCAD. Not in itself something you'll want to do to your drawings, but the techniques shown may well prove helpful for your applications.
Last time we implemented support for Lines and Arcs - in this post we extend that to Polylines. These are a different animal, as rather than replacing the original entities with 4 times as many for each "level", in this case we add 3 new segments to each original segment. So we don't then mark the entities for erasure, either.
In addition to the code needed to support polylines, I also had to make some minor modifications. I've marked them below in red. Basically there were a few areas where I wasn't as careful as I might have been when it comes to supporting modifying planar entities in arbitrary 3D spaces. So I fixed a few bugs, and also reimplemented the helper function that converts CircularArc3ds to Arcs.
Here's the updated C# code:
1 using Autodesk.AutoCAD.ApplicationServices;
2 using Autodesk.AutoCAD.DatabaseServices;
3 using Autodesk.AutoCAD.EditorInput;
4 using Autodesk.AutoCAD.Runtime;
5 using Autodesk.AutoCAD.Geometry;
6 using System.Collections.Generic;
7 using System;
8
9 namespace Kochizer
10 {
11 public class Commands
12 {
13 // We generate 4 new entities for every old entity
14 // (unless a complex entity such as a polyline)
15
16 const int newEntsPerOldEnt = 4;
17
18 [CommandMethod("KA")]
19 public void KochizeAll()
20 {
21 Document doc =
22 Application.DocumentManager.MdiActiveDocument;
23 Database db = doc.Database;
24 Editor ed = doc.Editor;
25
26 // Acquire user input - whether to create the
27 // new geometry to the left or the right...
28
29 PromptKeywordOptions pko =
30 new PromptKeywordOptions(
31 "\nCreate fractal to side (Left/<Right>): "
32 );
33 pko.Keywords.Add("Left");
34 pko.Keywords.Add("Right");
35
36 PromptResult pr =
37 ed.GetKeywords(pko);
38 bool bLeft = false;
39
40 if (pr.Status != PromptStatus.None &&
41 pr.Status != PromptStatus.OK)
42 return;
43
44 if ((string)pr.StringResult == "Left")
45 bLeft = true;
46
47 // ... and the recursion depth for the command.
48
49 PromptIntegerOptions pio =
50 new PromptIntegerOptions(
51 "\nEnter recursion level <1>: "
52 );
53 pio.AllowZero = false;
54 pio.AllowNegative = false;
55 pio.AllowNone = true;
56
57 PromptIntegerResult pir =
58 ed.GetInteger(pio);
59 int recursionLevel = 1;
60
61 if (pir.Status != PromptStatus.None &&
62 pir.Status != PromptStatus.OK)
63 return;
64
65 if (pir.Status == PromptStatus.OK)
66 recursionLevel = pir.Value;
67
68 // Note: strictly speaking we're not recursing,
69 // we're iterating, but the effect to the user
70 // is the same.
71
72 Transaction tr =
73 doc.TransactionManager.StartTransaction();
74 using (tr)
75 {
76 BlockTable bt =
77 (BlockTable)tr.GetObject(
78 db.BlockTableId,
79 OpenMode.ForRead
80 );
81 using (bt)
82 {
83 // No need to open the block table record
84 // for write, as we're just reading data
85 // for now
86
87 BlockTableRecord btr =
88 (BlockTableRecord)tr.GetObject(
89 bt[BlockTableRecord.ModelSpace],
90 OpenMode.ForRead
91 );
92 using (btr)
93 {
94 // List of changed entities
95 // (will contain complex entities, such as
96 // polylines"
97
98 ObjectIdCollection modified =
99 new ObjectIdCollection();
100
101 // List of entities to erase
102 // (will contain replaced entities)
103
104 ObjectIdCollection toErase =
105 new ObjectIdCollection();
106
107 // List of new entitites to add
108 // (will be processed recursively or
109 // assed to the open block table record)
110
111 List<Entity> newEntities =
112 new List<Entity>(
113 db.ApproxNumObjects * newEntsPerOldEnt
114 );
115
116 // Kochize each entity in the open block
117 // table record
118
119 foreach (ObjectId objId in btr)
120 {
121 Entity ent =
122 (Entity)tr.GetObject(
123 objId,
124 OpenMode.ForRead
125 );
126 Kochize(
127 ent,
128 modified,
129 toErase,
130 newEntities,
131 bLeft
132 );
133 }
134
135 // If we need to loop,
136 // work on the returned entities
137
138 while (--recursionLevel > 0)
139 {
140 // Create an output array
141
142 List<Entity> newerEntities =
143 new List<Entity>(
144 newEntities.Count * newEntsPerOldEnt
145 );
146
147 // Kochize all the modified (complex) entities
148
149 foreach (ObjectId objId in modified)
150 {
151 Entity ent =
152 (Entity)tr.GetObject(
153 objId,
154 OpenMode.ForRead
155 );
156 Kochize(
157 ent,
158 modified,
159 toErase,
160 newerEntities,
161 bLeft
162 );
163 }
164
165 // Kochize all the non-db resident entities
166
167 foreach (Entity ent in newEntities)
168 {
169 Kochize(
170 ent,
171 modified,
172 toErase,
173 newerEntities,
174 bLeft
175 );
176 }
177
178 // We now longer need the intermediate entities
179 // previously output for the level above,
180 // we replace them with the latest output
181
182 newEntities.Clear();
183 newEntities = newerEntities;
184 }
185
186 // Erase each of the replaced db-resident entities
187
188 foreach (ObjectId objId in toErase)
189 {
190 Entity ent =
191 (Entity)tr.GetObject(
192 objId,
193 OpenMode.ForWrite
194 );
195 ent.Erase();
196 }
197
198 // Add the new entities
199
200 btr.UpgradeOpen();
201 foreach (Entity ent in newEntities)
202 {
203 btr.AppendEntity(ent);
204 tr.AddNewlyCreatedDBObject(ent, true);
205 }
206 tr.Commit();
207 }
208 }
209 }
210 }
211
212 // Dispatch function to call through to various per-type
213 // functions
214
215 private void Kochize(
216 Entity ent,
217 ObjectIdCollection modified,
218 ObjectIdCollection toErase,
219 List<Entity> toAdd,
220 bool bLeft
221 )
222 {
223 Line ln = ent as Line;
224 if (ln != null)
225 {
226 Kochize(ln, modified, toErase, toAdd, bLeft);
227 return;
228 }
229 Arc arc = ent as Arc;
230 if (arc != null)
231 {
232 Kochize(arc, modified, toErase, toAdd, bLeft);
233 return;
234 }
235 Polyline pl = ent as Polyline;
236 if (pl != null)
237 {
238 Kochize(pl, modified, toErase, toAdd, bLeft);
239 return;
240 }
241 }
242
243 // Create 4 new lines from a line passed in
244
245 private void Kochize(
246 Line ln,
247 ObjectIdCollection modified,
248 ObjectIdCollection toErase,
249 List<Entity> toAdd,
250 bool bLeft
251 )
252 {
253 // Get general info about the line
254 // and calculate the main 5 points
255
256 Point3d pt1 = ln.StartPoint,
257 pt5 = ln.EndPoint;
258 Vector3d vec1 = pt5 - pt1,
259 norm1 = vec1.GetNormal();
260 double d_3 = vec1.Length / 3;
261 Point3d pt2 = pt1 + (norm1 * d_3),
262 pt4 = pt1 + (2 * norm1 * d_3);
263 Vector3d vec2 = pt4 - pt2;
264
265 if (bLeft)
266 vec2 =
267 vec2.RotateBy(
268 Math.PI / 3, new Vector3d(0, 0, 1)
269 );
270 else
271 vec2 =
272 vec2.RotateBy(
273 5 * Math.PI / 3, new Vector3d(0, 0, 1)
274 );
275 Point3d pt3 = pt2 + vec2;
276
277 // Mark the original to be erased
278
279 if (ln.ObjectId != ObjectId.Null)
280 toErase.Add(ln.ObjectId);
281
282 // Create the first line
283
284 Line ln1 = new Line(pt1, pt2);
285 ln1.SetPropertiesFrom(ln);
286 ln1.Thickness = ln.Thickness;
287 toAdd.Add(ln1);
288
289 // Create the second line
290
291 Line ln2 = new Line(pt2, pt3);
292 ln2.SetPropertiesFrom(ln);
293 ln2.Thickness = ln.Thickness;
294 toAdd.Add(ln2);
295
296 // Create the third line
297
298 Line ln3 = new Line(pt3, pt4);
299 ln3.SetPropertiesFrom(ln);
300 ln3.Thickness = ln.Thickness;
301 toAdd.Add(ln3);
302
303 // Create the fourth line
304
305 Line ln4 = new Line(pt4, pt5);
306 ln4.SetPropertiesFrom(ln);
307 ln4.Thickness = ln.Thickness;
308 toAdd.Add(ln4);
309 }
310
311 // Create 4 new arcs from an arc passed in
312
313 private void Kochize(
314 Arc arc,
315 ObjectIdCollection modified,
316 ObjectIdCollection toErase,
317 List<Entity> toAdd,
318 bool bLeft
319 )
320 {
321 // Get general info about the arc
322 // and calculate the main 5 points
323
324 Point3d pt1 = arc.StartPoint,
325 pt5 = arc.EndPoint;
326 double length = arc.GetDistAtPoint(pt5),
327 angle = arc.StartAngle;
328 Vector3d full = pt5 - pt1;
329
330 Point3d pt2 = arc.GetPointAtDist(length / 3),
331 pt4 = arc.GetPointAtDist(2 * length / 3);
332
333 // Mark the original to be erased
334
335 if (arc.ObjectId != ObjectId.Null)
336 toErase.Add(arc.ObjectId);
337
338 // Create the first arc
339
340 Point3d mid = arc.GetPointAtDist(length / 6);
341 CircularArc3d tmpArc = new CircularArc3d(pt1, mid, pt2);
342 Arc arc1 = circArc2Arc(tmpArc);
343 arc1.SetPropertiesFrom(arc);
344 arc1.Thickness = arc.Thickness;
345 toAdd.Add(arc1);
346
347 // Create the second arc
348
349 mid = arc.GetPointAtDist(length / 2);
350 tmpArc.Set(pt2, mid, pt4);
351 if (bLeft)
352 tmpArc.RotateBy(Math.PI / 3, tmpArc.Normal, pt2);
353 else
354 tmpArc.RotateBy(5 * Math.PI / 3, tmpArc.Normal, pt2);
355 Arc arc2 = circArc2Arc(tmpArc);
356 arc2.SetPropertiesFrom(arc);
357 arc2.Thickness = arc.Thickness;
358 toAdd.Add(arc2);
359
360 // Create the third arc
361
362 tmpArc.Set(pt2, mid, pt4);
363 if (bLeft)
364 tmpArc.RotateBy(5 * Math.PI / 3, tmpArc.Normal, pt4);
365 else
366 tmpArc.RotateBy(Math.PI / 3, tmpArc.Normal, pt4);
367 Arc arc3 = circArc2Arc(tmpArc);
368 arc3.SetPropertiesFrom(arc);
369 arc3.Thickness = arc.Thickness;
370 toAdd.Add(arc3);
371
372 // Create the fourth arc
373
374 mid = arc.GetPointAtDist(5 * length / 6);
375 Arc arc4 =
376 circArc2Arc(new CircularArc3d(pt4, mid, pt5));
377 arc4.SetPropertiesFrom(arc);
378 arc4.Thickness = arc.Thickness;
379 toAdd.Add(arc4);
380 }
381
382 Arc circArc2Arc(CircularArc3d circArc)
383 {
384 Point3d center = circArc.Center;
385 Vector3d normal = circArc.Normal;
386 Vector3d refVec = circArc.ReferenceVector;
387 Plane plane = new Plane(center, normal);
388 double ang = refVec.AngleOnPlane(plane);
389 return new Arc(
390 center,
391 normal,
392 circArc.Radius,
393 circArc.StartAngle + ang,
394 circArc.EndAngle + ang
395 );
396 }
397
398 private void Kochize(
399 Polyline pl,
400 ObjectIdCollection modified,
401 ObjectIdCollection toErase,
402 List<Entity> toAdd,
403 bool bLeft
404 )
405 {
406 pl.UpgradeOpen();
407
408 if (pl.ObjectId != ObjectId.Null &&
409 !modified.Contains(pl.ObjectId))
410 {
411 modified.Add(pl.ObjectId);
412 }
413
414 for(int vn = 0; vn < pl.NumberOfVertices; vn++)
415 {
416 SegmentType st = pl.GetSegmentType(vn);
417 if (st != SegmentType.Line && st != SegmentType.Arc)
418 continue;
419
420 double sw = pl.GetStartWidthAt(vn),
421 ew = pl.GetEndWidthAt(vn);
422
423 if (st == SegmentType.Line)
424 {
425 if (vn + 1 == pl.NumberOfVertices)
426 continue;
427
428 LineSegment2d ls = pl.GetLineSegment2dAt(vn);
429 Point2d pt1 = ls.StartPoint,
430 pt5 = ls.EndPoint;
431 Vector2d vec = pt5 - pt1;
432 double d_3 = vec.Length / 3;
433 Point2d pt2 = pt1 + (vec.GetNormal() * d_3),
434 pt4 = pt1 + (vec.GetNormal() * 2 * d_3);
435 Vector2d vec2 = pt4 - pt2;
436
437 if (bLeft)
438 vec2 = vec2.RotateBy(Math.PI / 3);
439 else
440 vec2 = vec2.RotateBy(5 * Math.PI / 3);
441
442 Point2d pt3 = pt2 + vec2;
443
444 pl.AddVertexAt(++vn, pt2, 0, sw, ew);
445 pl.AddVertexAt(++vn, pt3, 0, sw, ew);
446 pl.AddVertexAt(++vn, pt4, 0, sw, ew);
447 }
448 else if (st == SegmentType.Arc)
449 {
450 CircularArc3d ca = pl.GetArcSegmentAt(vn);
451 double oldBulge = pl.GetBulgeAt(vn);
452
453 // Build a standard arc and use that for the calcs
454
455 Arc arc = circArc2Arc(ca);
456
457 // Get the main 5 points
458
459 Point3d pt1 = arc.StartPoint,
460 pt5 = arc.EndPoint;
461
462 double ln = arc.GetDistAtPoint(pt5);
463 Point3d pt2 = arc.GetPointAtDist(ln / 3),
464 pt4 = arc.GetPointAtDist(2 * ln / 3);
465
466 Point3d mid = arc.GetPointAtDist(ln / 2);
467
468 CircularArc3d tmpArc = new CircularArc3d(pt2, mid, pt4);
469 if (bLeft)
470 tmpArc.RotateBy(5 * Math.PI / 3, tmpArc.Normal, pt4);
471 else
472 tmpArc.RotateBy(Math.PI / 3, tmpArc.Normal, pt4);
473
474 Point3d pt3 = tmpArc.StartPoint;
475
476 // Now add the new segments, setting the bulge
477 // for the existing one and the new ones to a third
478 // (as the segments are a third as big as the old one)
479
480 CoordinateSystem3d ecs = pl.Ecs.CoordinateSystem3d;
481 Plane pn = new Plane(ecs.Origin, pl.Normal);
482 double bu = oldBulge / 3;
483
484 pl.SetBulgeAt(vn, bu);
485 pl.AddVertexAt(++vn, pt2.Convert2d(pn), bu, sw, ew);
486 pl.AddVertexAt(++vn, pt3.Convert2d(pn), bu, sw, ew);
487 pl.AddVertexAt(++vn, pt4.Convert2d(pn), bu, sw, ew);
488 }
489 }
490 pl.DowngradeOpen();
491 }
492 }
493 }
494
And here's the source file for download.
Here are the results of running the KA command on a drawing containing a single polyline with arc and line segments, choosing a recursion depth of 6: