• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

DomCR / ACadSharp / 25596375546

09 May 2026 08:20AM UTC coverage: 76.821% (-0.03%) from 76.846%
25596375546

push

github

web-flow
Merge pull request #1064 from DomCR/issue/1061_insert-addattribute-refactor

Issue/1061 insert addattribute refactor

8578 of 12105 branches covered (70.86%)

Branch coverage included in aggregate %.

4 of 6 new or added lines in 1 file covered. (66.67%)

8 existing lines in 2 files now uncovered.

30782 of 39131 relevant lines covered (78.66%)

151518.64 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

58.78
/src/ACadSharp/Entities/Insert.cs
1
using ACadSharp.Attributes;
2
using ACadSharp.Extensions;
3
using ACadSharp.Objects;
4
using ACadSharp.Tables;
5
using CSMath;
6
using System;
7
using System.Collections.Generic;
8
using System.Linq;
9

10
namespace ACadSharp.Entities;
11

12
/// <summary>
13
/// Represents a <see cref="Insert"/> entity.
14
/// </summary>
15
/// <remarks>
16
/// Object name <see cref="DxfFileToken.EntityInsert"/> <br/>
17
/// Dxf class name <see cref="DxfSubclassMarker.Insert"/>
18
/// </remarks>
19
[DxfName(DxfFileToken.EntityInsert)]
20
[DxfSubClass(DxfSubclassMarker.Insert)]
21
public class Insert : Entity
22
{
23
        /// <summary>
24
        /// Attributes from the block reference
25
        /// </summary>
26
        /// <remarks>
27
        /// If an attribute should be added in this collection a definition will be added into the block reference as well
28
        /// </remarks>
29
        public SeqendCollection<AttributeEntity> Attributes { get; private set; }
19,599✔
30

31
        /// <summary>
32
        /// Gets the insert block definition.
33
        /// </summary>
34
        [DxfCodeValue(DxfReferenceType.Name, 2)]
35
        public BlockRecord Block { get; internal set; }
19,027✔
36

37
        /// <summary>
38
        /// Column count
39
        /// </summary>
40
        [DxfCodeValue(DxfReferenceType.Optional, 70)]
41
        public ushort ColumnCount { get; set; } = 1;
9,122✔
42

43
        /// <summary>
44
        /// Column spacing
45
        /// </summary>
46
        [DxfCodeValue(DxfReferenceType.Optional, 44)]
47
        public double ColumnSpacing { get; set; } = 0;
8,657✔
48

49
        /// <summary>
50
        /// True if the insert has attribute entities in it
51
        /// </summary>
52
        [DxfCodeValue(DxfReferenceType.Ignored, 66)]
53
        public bool HasAttributes
54
        { get { return this.Attributes.Any(); } }
1,350✔
55

56
        /// <inheritdoc/>
57
        public override bool HasDynamicSubclass => true;
2✔
58

59
        /// <summary>
60
        /// A 3D WCS coordinate representing the insertion or origin point.
61
        /// </summary>
62
        [DxfCodeValue(10, 20, 30)]
63
        public XYZ InsertPoint { get; set; } = XYZ.Zero;
46,543✔
64

65
        /// <summary>
66
        /// Specifies the rotation angle for the object.
67
        /// </summary>
68
        public bool IsMultiple { get { return this.RowCount > 1 || this.ColumnCount > 1; } }
1,233✔
69

70
        /// <summary>
71
        /// Specifies the three-dimensional normal unit vector for the object.
72
        /// </summary>
73
        [DxfCodeValue(210, 220, 230)]
74
        public XYZ Normal { get; set; } = XYZ.AxisZ;
10,970✔
75

76
        /// <inheritdoc/>
77
        public override string ObjectName => DxfFileToken.EntityInsert;
15,699✔
78

79
        /// <inheritdoc/>
80
        public override ObjectType ObjectType
81
        {
82
                get
83
                {
86✔
84
                        if (this.RowCount > 1 || this.ColumnCount > 1)
86✔
85
                        {
16✔
86
                                return ObjectType.MINSERT;
16✔
87
                        }
88
                        else
89
                        {
70✔
90
                                return ObjectType.INSERT;
70✔
91
                        }
92
                }
86✔
93
        }
94

95
        /// <summary>
96
        /// Specifies the rotation angle for the object.
97
        /// </summary>
98
        /// <value>
99
        /// The rotation angle in radians.
100
        /// </value>
101
        [DxfCodeValue(DxfReferenceType.IsAngle, 50)]
102
        public double Rotation { get; set; } = 0.0;
11,643✔
103

104
        /// <summary>
105
        /// Row count
106
        /// </summary>
107
        [DxfCodeValue(DxfReferenceType.Optional, 71)]
108
        public ushort RowCount { get; set; } = 1;
9,154✔
109

110
        /// <summary>
111
        /// Row spacing
112
        /// </summary>
113
        [DxfCodeValue(DxfReferenceType.Optional, 45)]
114
        public double RowSpacing { get; set; } = 0;
8,657✔
115

116
        /// <summary>
117
        /// Gets or set the spatial filter entry for this <see cref="Insert"/> entity.
118
        /// </summary>
119
        public SpatialFilter SpatialFilter
120
        {
121
                get
122
                {
×
123
                        if (this.XDictionary != null
×
124
                                && this.XDictionary.TryGetEntry(Filter.FilterEntryName, out CadDictionary filters))
×
125
                        {
×
126
                                return filters.GetEntry<SpatialFilter>(SpatialFilter.SpatialFilterEntryName);
×
127
                        }
128

129
                        return null;
×
130
                }
×
131
                set
132
                {
11✔
133
                        if (this.XDictionary == null)
11✔
134
                        {
11✔
135
                                this.CreateExtendedDictionary();
11✔
136
                        }
11✔
137

138
                        if (!this.XDictionary.TryGetEntry(Filter.FilterEntryName, out CadDictionary filters))
11✔
139
                        {
11✔
140
                                filters = new CadDictionary(Filter.FilterEntryName);
11✔
141
                                this.XDictionary.Add(filters);
11✔
142
                        }
11✔
143

144
                        filters.Remove(SpatialFilter.SpatialFilterEntryName);
11✔
145
                        filters.Add(SpatialFilter.SpatialFilterEntryName, value);
11✔
146
                }
11✔
147
        }
148

149
        /// <inheritdoc/>
150
        public override string SubclassMarker => this.IsMultiple ? DxfSubclassMarker.MInsert : DxfSubclassMarker.Insert;
322!
151

152
        /// <summary>
153
        /// X scale factor.
154
        /// </summary>
155
        [DxfCodeValue(41)]
156
        public double XScale
157
        {
158
                get
159
                {
471✔
160
                        return this._xscale;
471✔
161
                }
471✔
162
                set
163
                {
5,049✔
164
                        if (value.Equals(0))
5,049✔
165
                        {
1✔
166
                                string name = nameof(this.XScale);
1✔
167
                                throw new ArgumentOutOfRangeException(name, value, $"{name} value must be none zero.");
1✔
168
                        }
169
                        this._xscale = value;
5,048✔
170
                }
5,048✔
171
        }
172

173
        /// <summary>
174
        /// Y scale factor.
175
        /// </summary>
176
        [DxfCodeValue(42)]
177
        public double YScale
178
        {
179
                get
180
                {
410✔
181
                        return this._yscale;
410✔
182
                }
410✔
183
                set
184
                {
4,844✔
185
                        if (value.Equals(0))
4,844✔
186
                        {
1✔
187
                                string name = nameof(this.YScale);
1✔
188
                                throw new ArgumentOutOfRangeException(name, value, $"{name} value must be none zero.");
1✔
189
                        }
190
                        this._yscale = value;
4,843✔
191
                }
4,843✔
192
        }
193

194
        /// <summary>
195
        /// Z scale factor.
196
        /// </summary>
197
        [DxfCodeValue(43)]
198
        public double ZScale
199
        {
200
                get
201
                {
404✔
202
                        return this._zscale;
404✔
203
                }
404✔
204
                set
205
                {
4,833✔
206
                        if (value.Equals(0))
4,833✔
207
                        {
1✔
208
                                string name = nameof(this.ZScale);
1✔
209
                                throw new ArgumentOutOfRangeException(name, value, $"{name} value must be none zero.");
1✔
210
                        }
211
                        this._zscale = value;
4,832✔
212
                }
4,832✔
213
        }
214

215
        private double _xscale = 1;
8,297✔
216

217
        private double _yscale = 1;
8,297✔
218

219
        private double _zscale = 1;
8,297✔
220

221
        /// <summary>
222
        /// Constructor to reference an insert to a block record
223
        /// </summary>
224
        /// <param name="block">Block Record to reference</param>
225
        /// <exception cref="ArgumentNullException"></exception>
226
        public Insert(BlockRecord block) : this()
98✔
227
        {
98✔
228
                if (block is null) throw new ArgumentNullException(nameof(block));
98!
229

230
                if (block.Document != null)
98✔
231
                {
46✔
232
                        this.Block = (BlockRecord)block.Clone();
46✔
233
                }
46✔
234
                else
235
                {
52✔
236
                        this.Block = block;
52✔
237
                }
52✔
238

239
                foreach (var item in block.AttributeDefinitions)
344✔
240
                {
25✔
241
                        var att = new AttributeEntity(item);
25✔
242

243
                        Transform transform = this.GetTransform();
25✔
244
                        att.ApplyTransform(transform);
25✔
245

246
                        this.Attributes.Add(att);
25✔
247
                }
25✔
248
        }
98✔
249

250
        internal Insert() : base()
8,297✔
251
        {
8,297✔
252
                this.initCollections();
8,297✔
253
        }
8,297✔
254

255
        /// <inheritdoc/>
256
        public override void ApplyTransform(Transform transform)
257
        {
×
258
                XYZ newPosition = transform.ApplyTransform(this.InsertPoint);
×
259
                XYZ newNormal = this.transformNormal(transform, this.Normal);
×
260

261
                Matrix3 transOW = Matrix3.ArbitraryAxis(this.Normal);
×
262
                transOW *= Matrix3.RotationZ(this.Rotation);
×
263

264
                Matrix3 transWO = Matrix3.ArbitraryAxis(newNormal);
×
265
                transWO = transWO.Transpose();
×
266

267
                var transformation = new Matrix3(transform.Matrix);
×
268
                XYZ v = transOW * XYZ.AxisX;
×
269
                v = transformation * v;
×
270
                v = transWO * v;
×
271
                double newRotation = new XY(v.X, v.Y).GetAngle();
×
272

273
                transWO = Matrix3.RotationZ(newRotation).Transpose() * transWO;
×
274

275
                XYZ s = transOW * new XYZ(this.XScale, this.YScale, this.ZScale);
×
276
                s = transformation * s;
×
277
                s = transWO * s;
×
278
                XYZ newScale = new XYZ(
×
279
                        MathHelper.IsZero(s.X) ? MathHelper.Epsilon : s.X,
×
280
                        MathHelper.IsZero(s.Y) ? MathHelper.Epsilon : s.Y,
×
281
                        MathHelper.IsZero(s.Z) ? MathHelper.Epsilon : s.Z);
×
282

283
                this.Normal = newNormal;
×
284
                this.InsertPoint = newPosition;
×
285
                this.XScale = newScale.X;
×
286
                this.YScale = newScale.Y;
×
287
                this.ZScale = newScale.Z;
×
288
                this.Rotation = newRotation;
×
289

290
                foreach (AttributeEntity att in this.Attributes)
×
291
                {
×
292
                        att.ApplyTransform(transform);
×
293
                }
×
294
        }
×
295

296
        /// <inheritdoc/>
297
        public override CadObject Clone()
298
        {
181✔
299
                Insert clone = (Insert)base.Clone();
181✔
300

301
                clone.Block = (BlockRecord)this.Block?.Clone();
181✔
302

303
                clone.initCollections();
181✔
304
                foreach (var att in this.Attributes)
545✔
305
                {
1✔
306
                        clone.Attributes.Add((AttributeEntity)att.Clone());
1✔
307
                }
1✔
308

309
                return clone;
181✔
310
        }
181✔
311

312
        /// <summary>
313
        /// Returns an enumerable collection of entities representing the exploded contents of the block, with all entities
314
        /// transformed into the current coordinate system.
315
        /// </summary>
316
        /// <remarks>The returned entities are clones or converted equivalents of the original block entities, with
317
        /// geometric transformations applied. For example, arcs and circles are converted to their corresponding geometric
318
        /// representations. The original block and its entities remain unchanged.</remarks>
319
        /// <returns>An enumerable collection of <see cref="Entity"/> objects that make up the exploded block. Each entity is
320
        /// transformed according to the block's transform. The collection may be empty if the block contains no entities.</returns>
321
        public IEnumerable<Entity> Explode()
322
        {
×
323
                Transform transform = this.GetTransform();
×
324
                foreach (var e in this.Block.Entities)
×
325
                {
×
326
                        Entity c;
327
                        switch (e)
×
328
                        {
329
                                case Arc arc:
330
                                        arc.GetEndVertices(out XYZ start, out XYZ end);
×
331

332
                                        Arc a = new Arc(
×
333
                                                        transform.ApplyTransform(arc.Center),
×
334
                                                        transform.ApplyTransform(start),
×
335
                                                        transform.ApplyTransform(end),
×
336
                                                        arc.Normal);
×
337

338
                                        a.MatchProperties(e);
×
339

340
                                        yield return a;
×
341
                                        continue;
×
342
                                case Circle circle:
343
                                        c = new Ellipse()
×
344
                                        {
×
345
                                                MajorAxisEndPoint = XYZ.AxisX * circle.Radius,
×
346
                                                RadiusRatio = 1,
×
347
                                                Center = circle.Center,
×
348
                                                Normal = circle.Normal,
×
349
                                        };
×
350
                                        c.MatchProperties(e);
×
351
                                        break;
×
352
                                default:
353
                                        c = e.CloneTyped();
×
354
                                        break;
×
355
                        }
356

357
                        c.ApplyTransform(transform);
×
358

359
                        yield return c;
×
360
                }
×
361
        }
×
362

363
        /// <inheritdoc/>
364
        public override BoundingBox GetBoundingBox()
365
        {
5✔
366
                BoundingBox box = this.Block.GetBoundingBox();
5✔
367

368
                var t = this.GetTransform();
5✔
369

370
                var min = t.ApplyTransform(box.Min);
5✔
371
                var max = t.ApplyTransform(box.Max);
5✔
372

373
                return new BoundingBox(min, max);
5✔
374
        }
5✔
375

376
        /// <summary>
377
        /// Get the transform that will be applied to the entities in the <see cref="BlockRecord"/> when this entity is processed.
378
        /// </summary>
379
        /// <returns></returns>
380
        public Transform GetTransform()
381
        {
31✔
382
                var world = Matrix4.GetArbitraryAxis(this.Normal);
31✔
383
                var translation = Transform.CreateTranslation(this.InsertPoint);
31✔
384
                var rotation = Transform.CreateRotation(XYZ.AxisZ, this.Rotation);
31✔
385
                var scale = Transform.CreateScaling(new XYZ(this.XScale, this.YScale, this.ZScale));
31✔
386

387
                return new Transform(world * translation.Matrix * rotation.Matrix * scale.Matrix);
31✔
388
        }
31✔
389

390
        /// <summary>
391
        /// Updates all attribute definitions contained in the block reference as <see cref="AttributeDefinition"/> entities in the insert.
392
        /// </summary>
393
        /// <remarks>
394
        /// This will update the attributes based on their <see cref="AttributeBase.Tag"/>.
395
        /// </remarks>
396
        public void UpdateAttributes()
397
        {
×
398
                var atts = this.Attributes.ToArray();
×
399

400
                foreach (AttributeEntity att in atts)
×
401
                {
×
402
                        //Tags are not unique, is it needed? check how the different applications link the atts
403
                        if (!this.Block.AttributeDefinitions.Select(d => d.Tag).Contains(att.Tag))
×
404
                        {
×
405
                                this.Attributes.Remove(att);
×
406
                        }
×
407
                }
×
408

409
                foreach (AttributeDefinition attdef in this.Block.AttributeDefinitions)
×
410
                {
×
411
                        if (!this.Attributes.Select(d => d.Tag).Contains(attdef.Tag))
×
412
                        {
×
413
                                AttributeEntity att = new AttributeEntity(attdef);
×
414

NEW
415
                                Transform transform = this.GetTransform();
×
NEW
416
                                att.ApplyTransform(transform);
×
417

418
                                this.Attributes.Add(att);
×
419
                        }
×
420
                }
×
421
        }
×
422

423
        internal override void AssignDocument(CadDocument doc)
424
        {
8,472✔
425
                base.AssignDocument(doc);
8,472✔
426

427
                doc.RegisterCollection(this.Attributes);
8,472✔
428

429
                //Should only be triggered for internal use
430
                if (this.Block == null)
8,472✔
431
                        return;
8,003✔
432

433
                if (doc.BlockRecords.TryGetValue(this.Block.Name, out BlockRecord blk))
469✔
434
                {
171✔
435
                        this.Block = blk;
171✔
436
                }
171✔
437
                else
438
                {
298✔
439
                        doc.BlockRecords.Add(this.Block);
298✔
440
                }
298✔
441
        }
8,472✔
442

443
        internal override void UnassignDocument()
444
        {
205✔
445
                this.Block = (BlockRecord)this.Block.Clone();
205✔
446
                this.Document.UnregisterCollection(this.Attributes);
205✔
447

448
                base.UnassignDocument();
205✔
449
        }
205✔
450

451
        private void initCollections()
452
        {
8,478✔
453
                this.Attributes = new SeqendCollection<AttributeEntity>(this);
8,478✔
454
        }
8,478✔
455
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc