Author Topic: AttSync for .Net  (Read 6041 times)

0 Members and 1 Guest are viewing this topic.

Andrey Bushman

  • Swamp Rat
  • Posts: 592
  • CAD admin, programmer.
AttSync for .Net
« on: October 09, 2011, 01:53:59 pm »
Hi all.
I have written .net-realization of command AttSync. If it is interesting to someone - the code here.
« Last Edit: October 09, 2011, 01:58:12 pm by Andrey »
Man is born for happyness like a bird for flying.

kaefer

  • Swamp Rat
  • Posts: 562
Re: AttSync for .Net
« Reply #1 on: October 10, 2011, 02:54:00 am »
Hi Andrey,

that's pretty cool. I've always wondered how to implement that little gem myself.

Some observations:

I find it easier to reason about the various collections of AttributeReferences and AttributeDefinitions in terms of set-theoretical operations, that is set difference and set intersection.
Code: [Select]
module Seq =
    let differenceBy f s t =
        Seq.filter (fun x -> Seq.exists (f x) s |> not) t
    let intersectBy f s t =
        Seq.filter (fun x -> Seq.exists (f x) s) t

Further, why don't you use Reflection to copy the various properties, all the more as the Textstyle property will fall flat from 2010 onwards: it's TextstyleId now.

And finally, the canonical way to effect attribute justification will involve a test for AttachmentPoint.Baseleft, like
Code: [Select]
                        let just = ad.Justify
                        attref.Justify <- just
                        if just = AttachmentPoint.BaseLeft then
                            attref.Position <- ad.Position.TransformBy br.BlockTransform
                        else
                            attref.AlignmentPoint <- ad.AlignmentPoint.TransformBy br.BlockTransform

Andrey Bushman

  • Swamp Rat
  • Posts: 592
  • CAD admin, programmer.
Re: AttSync for .Net
« Reply #2 on: October 10, 2011, 04:32:16 am »
2 kaefer

My test code:

Code: [Select]
        [CommandMethod("MySynch", CommandFlags.Modal)]
        public void MySynch() {
            Document dwg = acad.DocumentManager.MdiActiveDocument;
            Editor ed = dwg.Editor;
            Database db = dwg.Database;
            TypedValue[] types = new TypedValue[] { new TypedValue((int) DxfCode.Start, "INSERT") };
            SelectionFilter sf = new SelectionFilter(types);
            PromptSelectionOptions pso = new PromptSelectionOptions();
            pso.MessageForAdding = "Select block reference";
            pso.SingleOnly = true;
            pso.AllowDuplicates = false;
           
            PromptSelectionResult psr = ed.GetSelection(pso, sf);
            if (psr.Status == PromptStatus.OK) {
                using (Transaction t = db.TransactionManager.StartTransaction()) {
                    BlockTable bt = (BlockTable) t.GetObject(db.BlockTableId, OpenMode.ForRead);
                    BlockReference br = (BlockReference) t.GetObject(psr.Value[0].ObjectId, OpenMode.ForRead);
                    BlockTableRecord btr = (BlockTableRecord) t.GetObject(bt[br.Name], OpenMode.ForRead);
                    btr.AttSync(t, false, true, false);
                    t.Commit();
                }
            }
            else
                ed.WriteMessage("Bad selectionа.\n");
        }

Yes, I wrote this code for AutoCAD 2009. :)
Unfortunately, I don't know language F # and I can not understand your code. If it is not difficult, write it, please on a C #.

In the course of testing I have clarified that my code not always works correctly - in one situation at me the attribute receives an incorrect insertion point. I put a problem file.

In a dwg-file I have created two determinations of the unit: annotative ("www") and normal ("www2").

Has created entrances of these units. Then I have changed unit determination "www2" (yellow objects in the drawing). If to cause on this unit my command ""MySynch"the attribute"GGG"for already existing entrances of units is allocated in a point 0,0, instead of there where should.

If after a command "MySynch" I push CTRL keys + Z I will receive a fatal error:



« Last Edit: October 10, 2011, 08:48:28 am by Andrey »
Man is born for happyness like a bird for flying.

kaefer

  • Swamp Rat
  • Posts: 562
Re: AttSync for .Net
« Reply #3 on: October 10, 2011, 07:31:48 am »
Sorry for the F# example. The corresponding C# is little bit less sharp, for the want of partial application, but I think it's similar to what you have already, except for the cryptic join clauses.

Code: [Select]
        public static IEnumerable<T> DifferenceBy<T, U>(this IEnumerable<T> t, Func<T, U, bool> predicate, IEnumerable<U> s)
        {
            return t.Where(n => !s.Any(o => predicate(n, o)));
        }
        public static IEnumerable<T> IntersectBy<T, U>(this IEnumerable<T> t, Func<T, U, bool> predicate, IEnumerable<U> s)
        {
            return t.Where(n => s.Any(o => predicate(n, o)));
        }

BTW shouldn't line 162 of your current version be like this, attn. underline?
Code: [Select]
                    IEnumerable<AttributeDefinition> attdefs2 = _btr.Cast<ObjectId>()

Andrey Bushman

  • Swamp Rat
  • Posts: 592
  • CAD admin, programmer.
Re: AttSync for .Net
« Reply #4 on: October 10, 2011, 08:03:15 am »
I thank for the found out error!
I have made changes to the code and have published its new version.

Changes:

1. Superfluous AttributeReference I have moved removal so that this action was before editing and adding new AttributeReference. (code line 67).
2. Has eliminated from processed dial-up AttributeDefinition, at which property Const = true (code line 43).
3. Has rectified the error specified by you with underlining (code line 163).

Unfortunately, if after command MySynch to push Ctrl + Z автокад fails on the former.
« Last Edit: October 10, 2011, 08:12:22 am by Andrey »
Man is born for happyness like a bird for flying.

Andrey Bushman

  • Swamp Rat
  • Posts: 592
  • CAD admin, programmer.
Re: AttSync for .Net
« Reply #5 on: October 10, 2011, 08:38:08 am »
I have deleted all nested objects of transactions (to find an error) from the method code.
Now the object of transaction is transferred through method parameter.
Man is born for happyness like a bird for flying.

Andrey Bushman

  • Swamp Rat
  • Posts: 592
  • CAD admin, programmer.
Re: AttSync for .Net
« Reply #6 on: October 10, 2011, 11:34:52 am »
About everything, the code it is possible to test the item of the item further - all found out bugs are corrected. In the code source I have commented out problem lines and have specified, they caused which problems:
Quote
Lines 90, 94 128 and 132 Forced down positioning of attribute GGG
Lines 100 and 138 Caused collapse AutoCAD at pushing Ctrl + Z
Man is born for happyness like a bird for flying.

Andrey Bushman

  • Swamp Rat
  • Posts: 592
  • CAD admin, programmer.
Re: AttSync for .Net
« Reply #7 on: October 11, 2011, 03:54:56 am »
2 kaefer
I have just now understood yours F#-code about alignment assignment. :) I have made the correction to the source code. Now alignment too correctly works.

Thank you!
Man is born for happyness like a bird for flying.

kaefer

  • Swamp Rat
  • Posts: 562
Re: AttSync for .Net
« Reply #8 on: October 12, 2011, 03:03:45 am »
I couldn't stop playing with this little toy. Thank you, Andrey!

Attached you will a version based most faithfully on yours, demonstrating
1) attributes with non-unique tags,
2) property transfer via Reflection, and
3) Keywords to allow selection of boolean command options.

One additional minor change in an area where the original ATTSYNC may fail too is checking for erased ObjectIds when collecting the definitions.

Using KeyValuePair as a poor man's Tuple, I tried to harness the full power of Linq's Join() method:
Code: [Select]
        // Extension for set intersection based on unique keys
        static IEnumerable<KeyValuePair<S, T>> IntersectByKey<S, T>(this IEnumerable<KVP<S>> s, IEnumerable<KVP<T>> other)
        {
            return s.Join(other, x => x.Key, x => x.Key, (x, y) => new KeyValuePair<S, T>(x.Value, y.Value));
        }
        // Extension for set difference based on unique keys
        static IEnumerable<S> DifferenceByKey<S, T>(this IEnumerable<KVP<S>> s, IEnumerable<KVP<T>> other)
        {
            return s.Where(ik => !other.Any(jk => jk.Key.Equals(ik.Key))).Select(ik => ik.Value);
        }
        ...
    }
    struct KVP<T>
    {
        public KeyValuePair<int, string> Key;
        public T Value;
    }

Zip is attached.


Andrey Bushman

  • Swamp Rat
  • Posts: 592
  • CAD admin, programmer.
Re: AttSync for .Net
« Reply #9 on: October 12, 2011, 08:32:39 am »
I couldn't stop playing with this little toy. Thank you, Andrey!

Attached you will a version based most faithfully on yours, demonstrating
1) attributes with non-unique tags,
2) property transfer via Reflection, and
3) Keywords to allow selection of boolean command options.

One additional minor change in an area where the original ATTSYNC may fail too is checking for erased ObjectIds when collecting the definitions.

Using KeyValuePair as a poor man's Tuple, I tried to harness the full power of Linq's Join() method:
Code: [Select]
        // Extension for set intersection based on unique keys
        static IEnumerable<KeyValuePair<S, T>> IntersectByKey<S, T>(this IEnumerable<KVP<S>> s, IEnumerable<KVP<T>> other)
        {
            return s.Join(other, x => x.Key, x => x.Key, (x, y) => new KeyValuePair<S, T>(x.Value, y.Value));
        }
        // Extension for set difference based on unique keys
        static IEnumerable<S> DifferenceByKey<S, T>(this IEnumerable<KVP<S>> s, IEnumerable<KVP<T>> other)
        {
            return s.Where(ik => !other.Any(jk => jk.Key.Equals(ik.Key))).Select(ik => ik.Value);
        }
        ...
    }
    struct KVP<T>
    {
        public KeyValuePair<int, string> Key;
        public T Value;
    }

Zip is attached.
Reflection usage negatively influences speed of operation. If the volume of the processed information a bycicle - productivity falling is notable.
Man is born for happyness like a bird for flying.

kaefer

  • Swamp Rat
  • Posts: 562
Re: AttSync for .Net
« Reply #10 on: October 12, 2011, 06:28:35 pm »
Reflection usage negatively influences speed of operation.

You may have a point there. That would leave either 4.0 dynamics or runtime type tests to avoid the repetitive code, then. In favour of the latter, how about
Code: [Select]
        // Helper to copy properties between an AttributeDefinition and another DBText object
        static void CopyProps(DBText target, AttributeDefinition source, Matrix3d? transform)
        {
            AttributeReference ar = target as AttributeReference;
            AttributeDefinition ad = target as AttributeDefinition;
            target.Annotative = source.Annotative;
            target.Color = source.Color;
            target.Height = source.Height;
            target.HorizontalMode = source.HorizontalMode;
            target.IsMirroredInX = source.IsMirroredInX;
            target.IsMirroredInY = source.IsMirroredInY;
            target.Justify = source.Justify;
            target.LayerId = source.LayerId;
            target.LinetypeId = source.LinetypeId;
            target.LinetypeScale = source.LinetypeScale;
            target.LineWeight = source.LineWeight;
            target.MaterialId = source.MaterialId;
            target.Oblique = source.Oblique;
            target.Rotation = source.Rotation;
            target.Thickness = source.Thickness;
#if PRE2010
            target.TextStyle = source.TextStyle;
#else
            target.TextStyleId = source.TextStyleId;
#endif
            target.Transparency = source.Transparency;
            target.VerticalMode = source.VerticalMode;
            target.Visible = source.Visible;
            target.WidthFactor = source.WidthFactor;
           
            if (source.Justify == AttachmentPoint.BaseLeft)
                target.Position = transform.HasValue ? source.Position.TransformBy(transform.Value) : source.Position;
            else
                target.AlignmentPoint = transform.HasValue ? source.AlignmentPoint.TransformBy(transform.Value) : source.AlignmentPoint;

            if (ar != null)
            {
                ar.Invisible = source.Invisible;
                ar.CastShadows = source.CastShadows;
                ar.LockPositionInBlock = source.LockPositionInBlock;
            }
            else if (ad != null)
            {
                ad.Invisible = source.Invisible;
                ad.CastShadows = source.CastShadows;
                ad.LockPositionInBlock = source.LockPositionInBlock;
                ad.Constant = source.Constant;
                ad.FieldLength = source.FieldLength;
                ad.ForceAnnoAllVisible = source.ForceAnnoAllVisible;
                ad.IsMTextAttributeDefinition = source.IsMTextAttributeDefinition;
                ad.Preset = source.Preset;
                ad.Prompt = source.Prompt;
                ad.Tag = source.Tag;
                ad.Verifiable = source.Verifiable;
            }
        }

Andrey Bushman

  • Swamp Rat
  • Posts: 592
  • CAD admin, programmer.
Re: AttSync for .Net
« Reply #11 on: October 14, 2011, 08:09:37 am »
I update my code (has found an error at testing for group of files). Look code rows 34, 37 and 291. These code lines are mandatory!

I don't know why but if in block definition in target drawing to change geometry after synchronization this geometry won't change (dynamic block). It is problem... :(
Man is born for happyness like a bird for flying.

kaefer

  • Swamp Rat
  • Posts: 562
Re: AttSync for .Net
« Reply #12 on: October 14, 2011, 09:06:57 am »
I update my code (has found an error at testing for group of files). Look code rows 34, 37 and 291. These code lines are mandatory!

I wouldn't have thought that you need to set HostApplicationServices.WorkingDatabase when your application targets exclusively AutoCAD (as opposed to RealDWG). There may be one implicit reference to a foreign Database in line 243, SetDatabaseDefaults(null).

BTW, are you @gpsm.ru?

Andrey Bushman

  • Swamp Rat
  • Posts: 592
  • CAD admin, programmer.
Re: AttSync for .Net
« Reply #13 on: October 14, 2011, 09:17:06 am »
I wouldn't have thought that you need to set HostApplicationServices.WorkingDatabase when your application targets exclusively AutoCAD (as opposed to RealDWG). There may be one implicit reference to a foreign Database in line 243, SetDatabaseDefaults(null).

These files show an error which I have corrected. Remove the code lines added by me and test on these files - you will receive an error eInvalidContext.

I use AttSync code in this plug-in.

BTW, are you @gpsm.ru?

Yes, it is the company domain in which I work.
Man is born for happyness like a bird for flying.

kaefer

  • Swamp Rat
  • Posts: 562
Re: AttSync for .Net
« Reply #14 on: October 14, 2011, 10:40:07 am »
test on these files - you will receive an error eInvalidContext.

Can'tCan reproduce, in spite of lacking both source code and AutoCAD 2009. Just threw together a test rig, reading the source and applying it's blocks to the target:

Code: [Select]
let doit targetFile sourceFile =
    use database = new Database(false, true)
    let objectIdCollection = new ObjectIdCollection()
    let stringCollection = new ResizeArray<string>()
    database.ReadDwgFile(sourceFile, System.IO.FileShare.Read, true, null)
    use tr = database.TransactionManager.StartTransaction()
    let source = tr.GetObject(database.BlockTableId, OpenMode.ForRead, false) :?> BlockTable
    for oid in source do
        let btr = tr.GetObject(oid, OpenMode.ForRead, false) :?> BlockTableRecord
        if not btr.IsAnonymous && not btr.IsLayout then
            objectIdCollection.Add oid |> ignore
            stringCollection.Add btr.Name
    tr.Commit()
    use database2 = new Database(false, true)
    database2.ReadDwgFile(targetFile, System.IO.FileShare.ReadWrite, true, null)
    use tr = database2.TransactionManager.StartTransaction()
    database.WblockCloneObjects(objectIdCollection, database2.BlockTableId, new IdMapping(), DuplicateRecordCloning.Replace, false)
    let blockTable = tr.GetObject(database2.BlockTableId, OpenMode.ForRead, false) :?> BlockTable
    for name in stringCollection do
        if blockTable.Has name then
            let btr = tr.GetObject(blockTable.[name], OpenMode.ForRead, false) :?> BlockTableRecord
            btr.AttSync(true, true, false)
    tr.Commit()
    database2.SaveAs(database2.Filename, DwgVersion.Current) // = 29, was 27

The error is with the call to the AdjustAlignment(db) method.
« Last Edit: October 14, 2011, 10:52:51 am by kaefer »