Author Topic: Erase doubled block references  (Read 1375 times)

0 Members and 1 Guest are viewing this topic.

Offline rom1

  • Visual Basic
  • *
  • Posts: 21
  • Karma: +3/-0
  • Gender: Male
    • prefered language: VB
    • Prog expertise: Good
    • View Profile
Erase doubled block references
« on: November 23, 2010, 08:16:23 AM »

Hi,

I need some help in order to use overrides function for 'Equals' and 'GetHashCode' into a structure:

You'll find enclose a sample function which erase the block references when they have the same name, layer, insertion point, scale and angle.

For that function, i wanted to use a dictionary in order to compare those parameters by using a structure.
This function seems to work this way, however, i think i have to improve the structure i used in order to compare the parameters exactly with the criteria i want but i don't really understand how to use override for the GetHashCode function.

Your help would be very appreciate.
Thanks.



target audience:{intermediate}

Offline (gile)

  • C#
  • *
  • Posts: 87
  • Karma: +8/-0
  • Gender: Male
    • prefered language: F
    • Prog expertise: Good
    • View Profile
Re: Erase doubled block references
« Reply #1 on: November 23, 2010, 12:00:19 PM »
Hi,

By my side, I'd rather use a 'Duplicate' extension method for the BlockReference class than create a strucure.

Here's an example, in a 'BlockRefExtension' static class, two extension methods are defined to be used in the DeletDuplicatedBlockRef() method (a Test command is provided to test the code).
The GetName() method is needed while targeting the 2007 SDK (and perhaps 2088 and 2009) which doesn't contains the BlockReference.Name property.

EDIT: the DeletDuplicatedBlockRef() method returns the number of erased blocks
EDIT: removed some unusefull statements

Code: [Select]
using System;
using System.Collections.Generic;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;

namespace DuplicateTest
{
    public static class BlockRefExtension
    {
        public static bool Duplicate(this BlockReference blk1, BlockReference blk2)
        {
            Tolerance tol = new Tolerance(1e-6, 1e-6);
            return
                blk1.GetName() == blk2.GetName() &&
                blk1.Layer == blk2.Layer &&
                Math.Round(blk1.Rotation, 5) == Math.Round(blk2.Rotation, 5) &&
                blk1.Position.IsEqualTo(blk2.Position, tol) &&
                blk1.ScaleFactors.IsEqualTo(blk2.ScaleFactors, tol);
        }

        public static string GetName(this BlockReference br)
        {
            using (Transaction tr = br.Database.TransactionManager.StartTransaction())
            {
                BlockTableRecord btr =
                    (BlockTableRecord)tr.GetObject(br.BlockTableRecord, OpenMode.ForRead);
                return btr.Name;
            }
        }
    }
    public class Commands
    {
        [CommandMethod("Test")]
        public void DelDupBlkRef()
        {
            try
            {
                int del = DeleteDuplicatedBlockRef();
                Application.ShowAlertDialog(del.ToString() + " duplicated block(s) have been erased");
            }
            catch (System.Exception e)
            {
                Application.ShowAlertDialog("\nError: " + e.Message);
            }
        }

        private int DeleteDuplicatedBlockRef()
        {
            Document doc = Application.DocumentManager.MdiActiveDocument;
            Database db = doc.Database;
            int result = 0;
            using (Transaction tr = db.TransactionManager.StartTransaction())
            {
                BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
                foreach (ObjectId id in bt)
                {
                    BlockTableRecord btr =
                        (BlockTableRecord)tr.GetObject(id, OpenMode.ForWrite);
                    ObjectIdCollection idCol = btr.GetBlockReferenceIds(true, false);
                    for (int i = 0; i < idCol.Count; i++)
                    {
                        BlockReference blkRef =
                            (BlockReference)tr.GetObject(idCol[i], OpenMode.ForRead);
                        for (int j = i + 1; j < idCol.Count; j++)
                        {
                            BlockReference br =
                                (BlockReference)tr.GetObject(idCol[j], OpenMode.ForRead);
                            if (br.OwnerId == blkRef.OwnerId && br.Duplicate(blkRef))
                            {
                                idCol.RemoveAt(j);
                                br.UpgradeOpen();
                                br.Erase();
                                j--;
                                result++;
                            }
                        }
                    }
                }
                tr.Commit();
            }
            return result;
        }
    }
}
« Last Edit: November 23, 2010, 02:18:58 PM by (gile) »

Offline rom1

  • Visual Basic
  • *
  • Posts: 21
  • Karma: +3/-0
  • Gender: Male
    • prefered language: VB
    • Prog expertise: Good
    • View Profile
Re: Erase doubled block references
« Reply #2 on: November 23, 2010, 01:13:45 PM »
Ok thanks, i will get a look at his solution.

Offline (gile)

  • C#
  • *
  • Posts: 87
  • Karma: +8/-0
  • Gender: Male
    • prefered language: F
    • Prog expertise: Good
    • View Profile
Re: Erase doubled block references
« Reply #3 on: November 23, 2010, 11:15:13 PM »
The Duplicate method can be defined inside the class too, as a private method with 2 arguments.

This is the way used in this F# implementation.

Code: [Select]
module DupBlks

open System
open System.Collections.Generic
open Autodesk.AutoCAD.ApplicationServices
open Autodesk.AutoCAD.DatabaseServices
open Autodesk.AutoCAD.EditorInput
open Autodesk.AutoCAD.Geometry
open Autodesk.AutoCAD.Runtime

let Duplicate (a: BlockReference) (b: BlockReference) =
    let tol = new Tolerance(1e-6, 1e-6)
    a.Name = b.Name &&
    a.OwnerId = b.OwnerId &&
    a.Position = b.Position &&
    a.Layer = b.Layer &&
    Math.Round(a.Rotation, 5) = Math.Round(b.Rotation, 5) &&
    a.ScaleFactors = b.ScaleFactors

let DeleteDuplicatedBlockRef() =
    let db = HostApplicationServices.WorkingDatabase
    use tr = db.TransactionManager.StartTransaction()
   
    let getRefs (bt: BlockTable) =
        let mutable lst = []
        for id in bt do
            let btr = tr.GetObject(id, OpenMode.ForRead) :?> BlockTableRecord
            for oId in btr.GetBlockReferenceIds(true, false) do
                lst <- tr.GetObject(oId, OpenMode.ForWrite) :?> BlockReference :: lst
        lst

    let rec findDups acc blks =
        match blks with
        | [] -> acc
        | h :: t -> let pair = List.partition (fun x -> Duplicate x h) t
                    findDups (acc @ (fst pair)) (snd pair)
    let delBlk lst =
        List.iter (fun (x: BlockReference) -> x.Erase()) lst
        lst.Length
               
    let cnt = tr.GetObject(db.BlockTableId, OpenMode.ForRead) :?> BlockTable
              |> getRefs |> findDups [] |> delBlk
    tr.Commit()
    cnt
       
[<CommandMethod("Test")>]
let Test() =
    try
        let del = DeleteDuplicatedBlockRef()
        Application.ShowAlertDialog(del.ToString() + " duplicated block(s) have been erased")
    with
        |ex -> Application.ShowAlertDialog(ex.Message)
« Last Edit: November 24, 2010, 12:17:05 AM by (gile) »

Offline rom1

  • Visual Basic
  • *
  • Posts: 21
  • Karma: +3/-0
  • Gender: Male
    • prefered language: VB
    • Prog expertise: Good
    • View Profile
Re: Erase doubled block references
« Reply #4 on: November 24, 2010, 08:06:39 AM »
Hi,

- I've tried to translate your code (the first one) in VB, but i didn't succeed in implementing the extension, it's said that extension must be into modules. I haven't really understood what is extension?

- Also, I found that by using dictionaries and checking if the key already exists, it's more "simple" since we don't have to "play" with counters and we get through the list of blocks only once. Actually, i previously used the lisp program "deldup_blk" (maybe yours?), so i wanted to implement in a way the same algorithm. However, i find that's not very clean if we don't know with which precision the comparison are done, but tell me if i'm wrong because sometimes i really don't understand how lisp can works without knowing the type of the variables.

- For your second solution, I've some difficulties to read F# code, but it seems that you don't have counters and that you manipulate list instead?
(Also, do you know some good code converters for this language? I open a new topic for this question)

Thanks


target audience:{advanced}

Offline (gile)

  • C#
  • *
  • Posts: 87
  • Karma: +8/-0
  • Gender: Male
    • prefered language: F
    • Prog expertise: Good
    • View Profile
Re: Erase doubled block references
« Reply #5 on: November 24, 2010, 01:02:33 PM »
1_ Extension methods is a way to add methods to an already existing class. Usually they're compiled in a separate dll so that while this dll is refered by another project, these methods are available the same way as built-in methods.
In the upper example, a 'Duplicate' method is added to the BlockReference class, it's visible in the intellisense as:
Bool BlockReference.Duplicate(BlockReference br).
But as I said, you can define a private method inside your class: Bool Duplicate(BlockReference br1, BlockReference br2).

2_ I really think the structure/GetHashCode... way isn't the simpler one because you have to compare objects with different types (String, Point3d, Double, ...) with tolerances. This way is easy using LISP which allows to build list of different type objects.
The 'for' statement used in the upper example is a classical way to run through collections. It's optimised here by removing the duplicated items from the collection at each loop.

3_ As all functional programming languages, F# provides specific higher order functions to deal with collections (List, Array, Seq) which have none equivalent ones in VB or C#.
« Last Edit: November 24, 2010, 08:29:05 PM by (gile) »

Offline (gile)

  • C#
  • *
  • Posts: 87
  • Karma: +8/-0
  • Gender: Male
    • prefered language: F
    • Prog expertise: Good
    • View Profile
Re: Erase doubled block references
« Reply #6 on: November 24, 2010, 02:54:52 PM »
I think I got something working with the strcture way using a ResultBuffer (the AutoCAD .NET/ObjectARX object which looks the most like a LISP DXF list).

In the following example:
The ParamBlockDoubled are stored in a List<ParamBlockDoubled>, there's no need to use a Dictionary as the items Value (objectId) are never used.
DoubleToErase is defined as a List<BlockReference> because it avoid to open them twice from their ObjectId.
It still uses the BlockTable -> BlockTableRecord _> GetReferenceIds route which avoid to run through the whole database.

Code: [Select]
using System;
using System.Collections.Generic;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;

namespace DuplicateTest
{
    public class Commands
    {
        [CommandMethod("Test")]
        public void DelDupBlkRef()
        {
            try
            {
                int del = DeleteDuplicatedBlockRef();
                Application.ShowAlertDialog(del.ToString() + " duplicated block(s) have been erased");
            }
            catch (System.Exception e)
            {
                Application.ShowAlertDialog("\nError: " + e.Message);
            }
        }

        private int DeleteDuplicatedBlockRef()
        {
            Document doc = Application.DocumentManager.MdiActiveDocument;
            Database db = doc.Database;
            int result = 0;
            using (Transaction tr = db.TransactionManager.StartTransaction())
            {
                BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
                List<ParamBlockDoubled> CollWithoutDouble = new List<ParamBlockDoubled>();
                List<BlockReference> DoubleToErase = new List<BlockReference>();
                foreach (ObjectId id in bt)
                {
                    BlockTableRecord btr =
                        (BlockTableRecord)tr.GetObject(id, OpenMode.ForWrite);
                    ObjectIdCollection idCol = btr.GetBlockReferenceIds(true, false);
                    foreach (ObjectId oId in idCol)
                    {
                        BlockReference br = (BlockReference)tr.GetObject(oId, OpenMode.ForRead, false);
                        ParamBlockDoubled pbd = new ParamBlockDoubled(br);
                        if (!CollWithoutDouble.Contains(pbd))
                            CollWithoutDouble.Add(pbd);
                        else
                            DoubleToErase.Add(br);
                    }
                }
                foreach (BlockReference b in DoubleToErase)
                {
                    b.UpgradeOpen();
                    b.Erase();
                    result ++;
                }
                tr.Commit();
            }
            return result;
        }

        public struct ParamBlockDoubled
        {
            // fields
            public ResultBuffer resBuf;

            // constructor
            public ParamBlockDoubled(BlockReference bRef)
            {
                Point3d pos = new Point3d(
                    Math.Round(bRef.Position.X, 6),
                    Math.Round(bRef.Position.Y, 6),
                    Math.Round(bRef.Position.Z, 6));
                resBuf =
                    new ResultBuffer(
                        new TypedValue[8] {
                            new TypedValue(330, bRef.OwnerId),
                            new TypedValue(2, bRef.Name),
                            new TypedValue(10, pos),
                            new TypedValue(8, bRef.Layer),
                            new TypedValue(50, Math.Round(bRef.Rotation, 6)),
                            new TypedValue(40, Math.Round(bRef.ScaleFactors.X, 6)),
                            new TypedValue(41, Math.Round(bRef.ScaleFactors.Y, 6)),
                            new TypedValue(42, Math.Round(bRef.ScaleFactors.Z, 6))});
            }

            // override method
            public override bool Equals(object obj)
            {
                if (obj.GetType() != typeof(ParamBlockDoubled))
                    return false;
                ParamBlockDoubled pbd = (ParamBlockDoubled)obj;
                return this.resBuf.Equals(pbd.resBuf);
            }

            public override int GetHashCode()
            {
                return base.GetHashCode();
            }
        }
    }
}
« Last Edit: November 24, 2010, 02:56:23 PM by (gile) »

Offline (gile)

  • C#
  • *
  • Posts: 87
  • Karma: +8/-0
  • Gender: Male
    • prefered language: F
    • Prog expertise: Good
    • View Profile
Re: Erase doubled block references
« Reply #7 on: November 24, 2010, 08:32:38 PM »
With the hel of kaefer (@TheSwamp), I learned some functions of the F# Seq module (IEnumerable) which made me able to rewrite a more concise and faster routine.

Code: [Select]
let duplicate (b: BlockReference) =
    b.Name,
    b.OwnerId,
    Math.Round(b.Position.X, 6),
    Math.Round(b.Position.Y, 6),
    Math.Round(b.Position.Z, 6),
    b.Layer,
    Math.Round(b.Rotation, 6),
    Math.Round(b.ScaleFactors.X, 6),
    Math.Round(b.ScaleFactors.Y, 6),
    Math.Round(b.ScaleFactors.Z, 6) 

let deleteDuplicatedBlockRef() =
    let db = HostApplicationServices.WorkingDatabase
    use tr = db.TransactionManager.StartTransaction()

    let getRefs btrId =
        let btr = tr.GetObject(btrId, OpenMode.ForRead) :?> BlockTableRecord
        btr.GetBlockReferenceIds(true, false)
        |> Seq.cast<_>
       
    let openRefs brefId =
        tr.GetObject(brefId, OpenMode.ForRead) :?> BlockReference
       
    let cnt = tr.GetObject(db.BlockTableId, OpenMode.ForRead) :?> BlockTable
              |> Seq.cast<_>
              |> Seq.collect getRefs
              |> Seq.map openRefs
              |> Seq.groupBy duplicate
              |> Seq.collect (fun x -> snd x |> Seq.skip 1)
              |> Seq.fold (fun cnt bref ->
                            bref.UpgradeOpen()
                            bref.Erase()
                            cnt + 1 ) 0
    tr.Commit()
    cnt
« Last Edit: November 24, 2010, 11:13:16 PM by Patriiick »

Offline rom1

  • Visual Basic
  • *
  • Posts: 21
  • Karma: +3/-0
  • Gender: Male
    • prefered language: VB
    • Prog expertise: Good
    • View Profile
Re: Erase doubled block references
« Reply #8 on: November 25, 2010, 08:12:45 AM »
Hi,

Thanks very much for those 2 examples.

I need to look at that precisely but I need firstly to document myself about ResultBuffer, GetHashCode and F# ...

Just one question, In your C# code, what does 'base' represent in 'base.GetHashCode', the converters translate it as 'MyBase'?

Thanks

Offline Jeff H

  • Newbie
  • *
  • Posts: 10
  • Karma: +3/-0
  • Gender: Male
    • prefered language: C
    • Prog expertise: Beginner
    • View Profile
Re: Erase doubled block references
« Reply #9 on: November 25, 2010, 10:15:46 AM »
MyBase is the same as C# Base

If a class inherits from a class it can use MyBase to call methods from the class it inherits from.

Here is a simple console App that overrides the the base class MessageBox method then calls the base class MessageBox method

Code: [Select]
Module Module1

    Sub Main()
        Dim a As New InheritsBaseClass
        a.MessageBox()
        a.MessageBoxBase()

    End Sub

End Module
Public Class BaseClass

    Public Overridable Sub MessageBox()
        MsgBox("Base Class")
    End Sub

End Class

Public Class InheritsBaseClass : Inherits BaseClass


    Public Overrides Sub MessageBox()
        MsgBox("Derived Class")
    End Sub

    Public Sub MessageBoxBase()
        MyBase.MessageBox()

    End Sub



End Class

You can also find your answers @TheSwamp

Offline rom1

  • Visual Basic
  • *
  • Posts: 21
  • Karma: +3/-0
  • Gender: Male
    • prefered language: VB
    • Prog expertise: Good
    • View Profile
Re: Erase doubled block references
« Reply #10 on: November 25, 2010, 10:36:26 AM »
Ok,

But in vb.net, Visual Studio says that 'MyBase' is not valid into a structure. May I use the keyword 'Me' instead?

Thanks

Offline (gile)

  • C#
  • *
  • Posts: 87
  • Karma: +8/-0
  • Gender: Male
    • prefered language: F
    • Prog expertise: Good
    • View Profile
Re: Erase doubled block references
« Reply #11 on: November 25, 2010, 01:44:33 PM »
Sorry, no need to use a ResultBuffer and it seems to be faster using a SelectionSet

Here's a new way:
Code: [Select]
        struct DuplicatePattern
        {
            public string Name;
            public string Layer;
            public ObjectId OwnerId;
            public double PositionX;
            public double PositionY;
            public double PositionZ;
            public double Rotation;
            public double ScaleX;
            public double ScaleY;
            public double ScaleZ;
        }

        private int DeleteDuplicatedBlockRef()
        {
            Document doc = Application.DocumentManager.MdiActiveDocument;
            Editor ed = doc.Editor;
            Database db = doc.Database;
            int result = 0;
            SelectionFilter filter = new SelectionFilter(new TypedValue[1] { new TypedValue(0, "INSERT") });
            PromptSelectionResult psr = ed.SelectAll(filter);
            if (psr.Status != PromptStatus.OK)
                return 0;
            using (Transaction tr = db.TransactionManager.StartTransaction())
            {
                BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
                List<DuplicatePattern> Patterns = new List<DuplicatePattern>();
                foreach (ObjectId id in psr.Value.GetObjectIds())
                {
                    BlockReference br = (BlockReference)tr.GetObject(id, OpenMode.ForRead, false);
                    DuplicatePattern dp = new DuplicatePattern();
                    dp.OwnerId = br.OwnerId;
                    dp.Name = br.Name;
                    dp.Layer = br.Layer;
                    dp.PositionX = Math.Round(br.Position.X, 6);
                    dp.PositionY = Math.Round(br.Position.Y, 6);
                    dp.PositionZ = Math.Round(br.Position.Z, 6);
                    dp.Rotation = Math.Round(br.Rotation, 6);
                    dp.ScaleX = Math.Round(br.ScaleFactors.X, 6);
                    dp.ScaleY = Math.Round(br.ScaleFactors.Y, 6);
                    dp.ScaleZ = Math.Round(br.ScaleFactors.Z, 6);
                    if (Patterns.Contains(dp))
                    {
                        br.UpgradeOpen();
                        br.Erase();
                        result++;
                    }
                    else
                        Patterns.Add(dp);
                }
                tr.Commit();
            }
            return result;
        }
« Last Edit: November 25, 2010, 01:50:17 PM by (gile) »

Offline (gile)

  • C#
  • *
  • Posts: 87
  • Karma: +8/-0
  • Gender: Male
    • prefered language: F
    • Prog expertise: Good
    • View Profile
Re: Erase doubled block references
« Reply #12 on: November 26, 2010, 03:06:58 PM »
As this topic became a kind of challenge at TheSwamp, you can find some other solutions there (using Linq for example).

It also appears that using List<T> seems to be much more time expansive than Dictionary<key,value> while seaching an item (even if never mind of the value).

So, in the upper code, replacing:

List<DuplicatePattern> with Dictionary<DuplicatePattern, int>

Patterns.Contains(dp) with Patterns.ContainsKey(db)

Patterns.Add(dp) with Patterns.Add(dp, 0)

should increase the execution speed.
« Last Edit: November 26, 2010, 11:01:28 PM by (gile) »