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

0 Members and 2 Guests are viewing this topic.

Andrey Bushman

  • Swamp Rat
  • Posts: 864
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 »

kaefer

  • Guest
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: 864
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 »

kaefer

  • Guest
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: 864
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 »

Andrey Bushman

  • Swamp Rat
  • Posts: 864
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.

Andrey Bushman

  • Swamp Rat
  • Posts: 864
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

Andrey Bushman

  • Swamp Rat
  • Posts: 864
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!

kaefer

  • Guest
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: 864
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.

kaefer

  • Guest
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: 864
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... :(

kaefer

  • Guest
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: 864
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.

kaefer

  • Guest
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 »

Andrey Bushman

  • Swamp Rat
  • Posts: 864
Re: AttSync for .Net
« Reply #15 on: October 14, 2011, 12:41:30 PM »
2 kaefer
This error has proved in method BlockSync (code row 54). In the code of method BlockSync method AttSync is many times caused. Probably, what exactly such, the multiple call also promotes error origin because in a cycle different databases are processed.

On the found error to me have informed here. I have checked up and was convinced of its presence. The decision to me was prompted by Alexander Rivilis.

I have written additional class WorkingDatabaseSwitcher: IDisposable also has changed the code of method AttSync (used in it this class).
« Last Edit: October 15, 2011, 01:32:54 AM by Andrey »

Andrey Bushman

  • Swamp Rat
  • Posts: 864
Re: AttSync for .Net
« Reply #16 on: October 18, 2011, 08:06:19 AM »
Has corrected many errors in the code. On the same page has updated the code.

Jeff H

  • Needs a day job
  • Posts: 6150
Re: AttSync for .Net
« Reply #17 on: August 07, 2012, 01:51:10 PM »
Here is an idea from  MiscUtils by Jon Skeet and his blog
 
You could modify it not to throw errors if it does not contain the method but will take a performance hit first run but after that supposedly will run about as fast as hard-coding it and much faster than reflection. Seems like I saw somewhere for ideas to help or limit the first performance hit and will look for links.
A nice little Tree Expression example.
Code - C#: [Select]
  1.  
  2.  ////Written by Jon Skeet from MiscUtil
  3. ////source http://www.yoda.arachsys.com/csharp/miscutil/
  4. ////Jon Skeet blog's http://msmvps.com/blogs/jon_skeet/default.aspx
  5. #if DOTNET35
  6. using System;
  7. using System.Collections.Generic;
  8. using System.Linq.Expressions;
  9. using System.Reflection;
  10. namespace MiscUtil.Reflection
  11. {
  12.     /// <summary>
  13.     /// Generic class which copies to its target type from a source
  14.     /// type specified in the Copy method. The types are specified
  15.     /// separately to take advantage of type inference on generic
  16.     /// method arguments.
  17.     /// </summary>
  18.     public static class PropertyCopy<TTarget> where TTarget : class, new()
  19.     {
  20.         /// <summary>
  21.         /// Copies all readable properties from the source to a new instance
  22.         /// of TTarget.
  23.         /// </summary>
  24.         public static TTarget CopyFrom<TSource>(TSource source) where TSource : class
  25.         {
  26.             return PropertyCopier<TSource>.Copy(source);
  27.         }
  28.         /// <summary>
  29.         /// Static class to efficiently store the compiled delegate which can
  30.         /// do the copying. We need a bit of work to ensure that exceptions are
  31.         /// appropriately propagated, as the exception is generated at type initialization
  32.         /// time, but we wish it to be thrown as an ArgumentException.
  33.         /// </summary>
  34.         private static class PropertyCopier<TSource> where TSource : class
  35.         {
  36.             private static readonly Func<TSource, TTarget> copier;
  37.             private static readonly Exception initializationException;
  38.             internal static TTarget Copy(TSource source)
  39.             {
  40.                 if (initializationException != null)
  41.                 {
  42.                     throw initializationException;
  43.                 }
  44.                 if (source == null)
  45.                 {
  46.                     throw new ArgumentNullException("source");
  47.                 }
  48.                 return copier(source);
  49.             }
  50.             static PropertyCopier()
  51.             {
  52.                 try
  53.                 {
  54.                     copier = BuildCopier();
  55.                     initializationException = null;
  56.                 }
  57.                 catch (Exception e)
  58.                 {
  59.                     copier = null;
  60.                     initializationException = e;
  61.                 }
  62.             }
  63.             private static Func<TSource, TTarget> BuildCopier()
  64.             {
  65.                 ParameterExpression sourceParameter = Expression.Parameter(typeof(TSource), "source");
  66.                 var bindings = new List<MemberBinding>();
  67.                 foreach (PropertyInfo sourceProperty in typeof(TSource).GetProperties())
  68.                 {
  69.                     if (!sourceProperty.CanRead)
  70.                     {
  71.                         continue;
  72.                     }
  73.                     PropertyInfo targetProperty = typeof(TTarget).GetProperty(sourceProperty.Name);
  74.                     if (targetProperty == null)
  75.                     {
  76.                         throw new ArgumentException("Property "   sourceProperty.Name   " is not present and accessible in "   typeof(TTarget).FullName);
  77.                     }
  78.                     if (!targetProperty.CanWrite)
  79.                     {
  80.                         throw new ArgumentException("Property "   sourceProperty.Name   " is not writable in "   typeof(TTarget).FullName);
  81.                     }
  82.                     if (!targetProperty.PropertyType.IsAssignableFrom(sourceProperty.PropertyType))
  83.                     {
  84.                         throw new ArgumentException("Property "   sourceProperty.Name   " has an incompatible type in "   typeof(TTarget).FullName);
  85.                     }
  86.                     bindings.Add(Expression.Bind(targetProperty, Expression.Property(sourceParameter, sourceProperty)));
  87.                 }
  88.                 Expression initializer = Expression.MemberInit(Expression.New(typeof(TTarget)), bindings);
  89.                 return Expression.Lambda<Func<TSource,TTarget>>(initializer, sourceParameter).Compile();
  90.             }
  91.         }
  92.     }
  93. }
  94. #endif
  95.  

TheMaster

  • Guest
Re: AttSync for .Net
« Reply #18 on: August 07, 2012, 06:40:05 PM »
Here is an idea from  MiscUtils by Jon Skeet and his blog
 
You could modify it not to throw errors if it does not contain the method but will take a performance hit first run but after that supposedly will run about as fast as hard-coding it and much faster than reflection. Seems like I saw somewhere for ideas to help or limit the first performance hit and will look for links.
A nice little Tree Expression example.
Code - C#: [Select]
  1.  
  2.  ////Written by Jon Skeet from MiscUtil
  3. ////source http://www.yoda.arachsys.com/csharp/miscutil/
  4. ////Jon Skeet blog's http://msmvps.com/blogs/jon_skeet/default.aspx
  5. #if DOTNET35
  6. using System;
  7. using System.Collections.Generic;
  8. using System.Linq.Expressions;
  9. using System.Reflection;
  10. namespace MiscUtil.Reflection
  11. {
  12.     /// <summary>
  13.     /// Generic class which copies to its target type from a source
  14.     /// type specified in the Copy method. The types are specified
  15.     /// separately to take advantage of type inference on generic
  16.     /// method arguments.
  17.     /// </summary>
  18.     public static class PropertyCopy<TTarget> where TTarget : class, new()
  19.     {
  20.         /// <summary>
  21.         /// Copies all readable properties from the source to a new instance
  22.         /// of TTarget.
  23.         /// </summary>
  24.         public static TTarget CopyFrom<TSource>(TSource source) where TSource : class
  25.         {
  26.             return PropertyCopier<TSource>.Copy(source);
  27.         }
  28.         /// <summary>
  29.         /// Static class to efficiently store the compiled delegate which can
  30.         /// do the copying. We need a bit of work to ensure that exceptions are
  31.         /// appropriately propagated, as the exception is generated at type initialization
  32.         /// time, but we wish it to be thrown as an ArgumentException.
  33.         /// </summary>
  34.         private static class PropertyCopier<TSource> where TSource : class
  35.         {
  36.             private static readonly Func<TSource, TTarget> copier;
  37.             private static readonly Exception initializationException;
  38.             internal static TTarget Copy(TSource source)
  39.             {
  40.                 if (initializationException != null)
  41.                 {
  42.                     throw initializationException;
  43.                 }
  44.                 if (source == null)
  45.                 {
  46.                     throw new ArgumentNullException("source");
  47.                 }
  48.                 return copier(source);
  49.             }
  50.             static PropertyCopier()
  51.             {
  52.                 try
  53.                 {
  54.                     copier = BuildCopier();
  55.                     initializationException = null;
  56.                 }
  57.                 catch (Exception e)
  58.                 {
  59.                     copier = null;
  60.                     initializationException = e;
  61.                 }
  62.             }
  63.             private static Func<TSource, TTarget> BuildCopier()
  64.             {
  65.                 ParameterExpression sourceParameter = Expression.Parameter(typeof(TSource), "source");
  66.                 var bindings = new List<MemberBinding>();
  67.                 foreach (PropertyInfo sourceProperty in typeof(TSource).GetProperties())
  68.                 {
  69.                     if (!sourceProperty.CanRead)
  70.                     {
  71.                         continue;
  72.                     }
  73.                     PropertyInfo targetProperty = typeof(TTarget).GetProperty(sourceProperty.Name);
  74.                     if (targetProperty == null)
  75.                     {
  76.                         throw new ArgumentException("Property "   sourceProperty.Name   " is not present and accessible in "   typeof(TTarget).FullName);
  77.                     }
  78.                     if (!targetProperty.CanWrite)
  79.                     {
  80.                         throw new ArgumentException("Property "   sourceProperty.Name   " is not writable in "   typeof(TTarget).FullName);
  81.                     }
  82.                     if (!targetProperty.PropertyType.IsAssignableFrom(sourceProperty.PropertyType))
  83.                     {
  84.                         throw new ArgumentException("Property "   sourceProperty.Name   " has an incompatible type in "   typeof(TTarget).FullName);
  85.                     }
  86.                     bindings.Add(Expression.Bind(targetProperty, Expression.Property(sourceParameter, sourceProperty)));
  87.                 }
  88.                 Expression initializer = Expression.MemberInit(Expression.New(typeof(TTarget)), bindings);
  89.                 return Expression.Lambda<Func<TSource,TTarget>>(initializer, sourceParameter).Compile();
  90.             }
  91.         }
  92.     }
  93. }
  94. #endif
  95.  

It won't run as fast as static code, but about the same as System.Dynamic, which is about half as fast as static code, but still hundreds of times faster than reflection. The performance hit on the first invocation is because that is when the expression is built and compiled and there's no getting around that other than to sustain a longer delay by generating the expressions all at once for all properties.

However, given what the OP is doing, reflection is a really bad way to go about it in the first place, because it only saves you from a bit of typing, at the expense of immensely slower code. Sacrificing performance to save a few keystrokes?  I think not.

Dynamic expressions might be useful in cases where you are trying copy properties from one target type to another without knowing precisely what those types are at compile time, which is not the case here.

The CopyFrom() method might also do the job, but I haven't tried it.


Jeff H

  • Needs a day job
  • Posts: 6150
Re: AttSync for .Net
« Reply #19 on: August 07, 2012, 07:27:38 PM »
Sacrificing performance to save a few keystrokes?  I think not.

Thanks again Tony,
 
Of course the main point of your posts are tremendously helpful, but it is little comments like that; that also help greatly.
Looking through .NET literature and books for some reason makes me think typing out each property is the wrong way, some better neat pattern, a abstraction lies somewhere in there, or less lines of code is better. I guess that's why the examples they use never do or come close to actually doing anything useful. Maybe along the lines of your doing something wrong if you use an elseif statement.
 
Not sure if that made any sense but was just thanking you and your posts help much more than you may know.
 

TheMaster

  • Guest
Re: AttSync for .Net
« Reply #20 on: August 07, 2012, 08:15:54 PM »
Sacrificing performance to save a few keystrokes?  I think not.

Thanks again Tony,
 
Of course the main point of your posts are tremendously helpful, but it is little comments like that; that also help greatly.
Looking through .NET literature and books for some reason makes me think typing out each property is the wrong way, some better neat pattern, a abstraction lies somewhere in there, or less lines. of code is better I guess that's why the examples they use never do or come close to actually doing anything useful. Maybe along the lines of your doing something wrong if you use an elseif statement.
 
Not sure if that made any sense but was just thanking you and your posts help much more than you may know.

Fewer lines of code is better, provided there's no significant cost. I don't like having to type more than I have to either.  I've used reflection quite bit in my work over the years, especially with the code I wrote in support of Data Extraction and extending it, and I've been meaning to revise it to use Linq.Expressions when I have a chance, because it can speed things up dramatically.

Reflection (or better, dynamic  expressions) are good when you don't know what you're dealing with at compile time (Data Extraction being a case in point), but when you do know, it's probably better to go the route the OP took here.

TheMaster

  • Guest
Re: AttSync for .Net
« Reply #21 on: August 08, 2012, 07:51:30 PM »
Sacrificing performance to save a few keystrokes?  I think not.

Thanks again Tony,
 
Of course the main point of your posts are tremendously helpful, but it is little comments like that; that also help greatly.
Looking through .NET literature and books for some reason makes me think typing out each property is the wrong way, some better neat pattern, a abstraction lies somewhere in there, or less lines of code is better. I guess that's why the examples they use never do or come close to actually doing anything useful. Maybe along the lines of your doing something wrong if you use an elseif statement.
 
Not sure if that made any sense but was just thanking you and your posts help much more than you may know.

Give this a go:

Code - C#: [Select]
  1. // MatchPropertiesMgd AutoCAD .NET Code Example
  2. // Copyright (c) 2012  Tony Tanzillo
  3.  
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Linq;
  7. using System.Text;
  8. using Autodesk.AutoCAD.Runtime;
  9. using Autodesk.AutoCAD.EditorInput;
  10. using Autodesk.AutoCAD.ApplicationServices;
  11. using Autodesk.AutoCAD.DatabaseServices;
  12.  
  13. namespace MatchPropertiesMgdExample
  14. {
  15.    public static class MatchPropsMgd
  16.    {
  17.       // This example works specifically with TEXT objects, but
  18.       // the method demonstrated here will work with any type of
  19.       // entity.
  20.  
  21.       [CommandMethod( "MATCHPROPSMGD" )]
  22.       public static void MatchPropsmMgdCommand()
  23.       {
  24.          Document doc = Application.DocumentManager.MdiActiveDocument;
  25.          Editor ed = doc.Editor;
  26.  
  27.          // Get the runtime class for the AcDbMatchProperties protocol extension:
  28.          RXClass matchPropsClass = SystemObjects.ClassDictionary["AcDbMatchProperties"] as RXClass;
  29.  
  30.          if( matchPropsClass == null )
  31.          {
  32.             ed.WriteMessage( "\nAcDbMatchProperites Protocol Extension Runtime class not found!" );
  33.             return;
  34.          }
  35.  
  36.          // Get the source TEXT object
  37.          PromptEntityOptions peo = new PromptEntityOptions( "Select source TEXT entity: " );
  38.          peo.SetRejectMessage( "requires a text object" );
  39.          peo.AddAllowedClass( typeof( DBText ), false );
  40.          PromptEntityResult perSource = ed.GetEntity( peo );
  41.          if( perSource.Status != PromptStatus.OK )
  42.             return;
  43.  
  44.          // Get the destination TEXT object:
  45.          peo.Message = "Select Destination TEXT entity: ";
  46.          PromptEntityResult perDest = ed.GetEntity( peo );
  47.          if( perDest.Status != PromptStatus.OK )
  48.             return;
  49.  
  50.          // Get the MatchProps protocol extension from the source object.
  51.          // This requires acmatch.arx to be loaded, which we'll attempt
  52.          // to do if not already loaded:
  53.          
  54.          IntPtr p = perSource.ObjectId.ObjectClass.QueryX( matchPropsClass );
  55.          if( p == IntPtr.Zero )
  56.          {
  57.             SystemObjects.DynamicLinker.LoadModule( "acmatch.arx", false, false );
  58.             p = perSource.ObjectId.ObjectClass.QueryX( matchPropsClass );
  59.             if( p == IntPtr.Zero )
  60.             {
  61.                ed.WriteMessage( "\nFailed to load acmatch.arx" );
  62.                return;
  63.             }
  64.          }
  65.  
  66.          // Create an instance of the MatchProperties managed wrapper:
  67.          MatchProperties matcher = DisposableWrapper.Create( typeof( MatchProperties ), p, false ) as MatchProperties;
  68.  
  69.          if( matcher == null )
  70.          {
  71.             ed.WriteMessage( "\nFailed to create instance of MatchProperties class" );
  72.             return;
  73.          }
  74.          
  75.          // We'll copy all common entity properties and TEXT-specific properties:
  76.          int flags = (int) ( MatchPropFlags.Entity | MatchPropFlags.Text );
  77.  
  78.          // Open the source for read,
  79.          // open the destination for write,
  80.          // and bada bing bada boom
  81.          using( Transaction tr = doc.TransactionManager.StartTransaction() )
  82.          {
  83.             Entity source = (Entity) perSource.ObjectId.GetObject( OpenMode.ForRead );
  84.             Entity destination = (Entity) perDest.ObjectId.GetObject( OpenMode.ForWrite );
  85.             matcher.CopyProperties( source, destination, flags );
  86.             tr.Commit();
  87.          }
  88.       }
  89.    }
  90.    
  91.  
  92.    public enum MatchPropFlags
  93.    {
  94.       Color = 0x1,
  95.       Layer = 0x2,
  96.       Linetype = 0x4,
  97.       Thickness = 0x8,
  98.       LinetypeScale = 0x10,
  99.       Entity = Color | Layer | Linetype | Thickness | LinetypeScale | Material | Lineweight | PlotStyleName,
  100.       Text = 0x20,
  101.       Dimension = 0x40,
  102.       Hatching = 0x80,
  103.       Lineweight = 0x100,
  104.       PlotStyleName = 0x200,
  105.       Polyline = 0x400,
  106.       Viewport = 0x800,
  107.       Table = 0x1000,
  108.       Material = 0x2000,
  109.       ShadowDisplay = 0x4000,
  110.       Multileader = 0x8000,
  111.       All = 0xFFFF
  112.    };
  113.  
  114. }
  115.  
  116.  
  117.  
« Last Edit: August 08, 2012, 07:55:21 PM by TT »

TheMaster

  • Guest
Re: AttSync for .Net
« Reply #22 on: August 08, 2012, 08:26:22 PM »
Here's a somewhat more sophisticated version of the above sample,
that makes some of the code reusable in the form of extension methods:

[Revised after discovreing a few issues surrounding the MatchProperties
protocol extension]

[Revised again to better support getting the protocol extension through
an Entity in addition to ObjectId and RXClass, added support for module
unloading, and fixed some locked layer issues]

Code - C#: [Select]
  1.  
  2. // MatchPropertiesMgd AutoCAD .NET Code Samples
  3. // Copyright (c) 2012  Tony Tanzillo
  4.  
  5. using System;
  6. using System.Collections;
  7. using System.Collections.Generic;
  8. using System.Linq;
  9. using System.Text;
  10. using Autodesk.AutoCAD.Runtime;
  11. using Autodesk.AutoCAD.EditorInput;
  12. using Autodesk.AutoCAD.ApplicationServices;
  13. using Autodesk.AutoCAD.DatabaseServices;
  14. using AcDb = Autodesk.AutoCAD.DatabaseServices;
  15.  
  16. /// Abstract
  17. ///
  18. /// This sample code demonstrates how to programmatically
  19. /// consume the functionality provided by AutoCAD's MATCHPROP
  20. /// command, using the AcDbMatchProperties protocol extension.
  21. ///
  22. /// This code has been tested with AutoCAD 2010 and later
  23.  
  24. namespace MatchPropertiesMgdExamples
  25. {
  26.    public static class Commands
  27.    {
  28.       // This example works specifically with TEXT objects, but
  29.       // the method demonstrated here will work with any type of
  30.       // entity. See the additional command examples below for
  31.       // more-generic matchprop functionality.
  32.  
  33.       // The work done in this example is mainly for illustration of
  34.       // using the basic APIs included herein, and should not be viewed
  35.       // as the routine or best way of consuming the latter. The other
  36.       // sample commands that follow more correctly show the best way to
  37.       // consume the included APIs that automate some of the work done
  38.       // manually in this example.
  39.  
  40.       [CommandMethod( "MATCHTEXTPROPSMGD" )]
  41.       public static void MatchTextPropsMgdCommand()
  42.       {
  43.          Document doc = Application.DocumentManager.MdiActiveDocument;
  44.          Editor ed = doc.Editor;
  45.  
  46.          // Get the source TEXT object
  47.          PromptEntityOptions peo = new PromptEntityOptions( "Select source TEXT object: " );
  48.          peo.AllowObjectOnLockedLayer = true;
  49.          peo.SetRejectMessage( "requires a TEXT object" );
  50.          peo.AddAllowedClass( typeof( DBText ), false );
  51.          PromptEntityResult perSource = ed.GetEntity( peo );
  52.          if( perSource.Status != PromptStatus.OK )
  53.             return;
  54.  
  55.          // Get the destination TEXT object
  56.          peo.Message = "Select destination TEXT object: ";
  57.          peo.AllowObjectOnLockedLayer = false;
  58.          PromptEntityResult perDest = ed.GetEntity( peo );
  59.          if( perDest.Status != PromptStatus.OK )
  60.             return;
  61.          if( perDest.ObjectId == perSource.ObjectId )
  62.          {
  63.             ed.WriteMessage( "\nThe source and destination objects are the same" );
  64.             return;
  65.          }
  66.  
  67.          // Get the MatchProperties protocol extension object from the
  68.          // destination entity's runtime class, using the included
  69.          // GetPropertyPainter() extension method.
  70.          //
  71.          // Note that the protocol extension object must be obtained from the
  72.          // runtime class of the destination entity rather than the source entity.
  73.          // If the protocol extension object is obtained from the runtime class
  74.          // of the source entity, attempting to use it to copy properties to a
  75.          // destination entity of a dissimilar type usually results in a failure.
  76.          //
  77.          // This also requires acmatch.arx to be loaded, which the extension
  78.          // method will attempt to do, if not already loaded.
  79.          //
  80.          // See the GetMatchProperties() method of the MatchPropertiesExtensions
  81.          // class below for details on obtaining the protocol extension object.
  82.  
  83.          MatchProperties painter = perDest.ObjectId.GetPropertyPainter();
  84.  
  85.          // We'll copy all common entity properties and TEXT-specific properties:
  86.          int flags = (int) ( MatchPropFlags.Entity | MatchPropFlags.Text );
  87.  
  88.          // Open the source for read,
  89.          // open the destination for write,
  90.          // and bada bing bada boom
  91.          using( Transaction tr = doc.TransactionManager.StartTransaction() )
  92.          {
  93.             Entity source = (Entity) perSource.ObjectId.GetObject( OpenMode.ForRead );
  94.             Entity destination = (Entity) perDest.ObjectId.GetObject( OpenMode.ForWrite );
  95.             painter.CopyProperties( source, destination, flags );
  96.             tr.Commit();
  97.          }
  98.       }
  99.  
  100.       /// This example is a generalization of the above, accepting any source
  101.       /// and destination entity type. By default, all properties are copied,
  102.       /// which means that how this command bahaves depends on the type of the
  103.       /// source and destination objects. When they are the same type, and the
  104.       /// protocol extension supports type-specific properties, all of same are
  105.       /// copied along with common entity properties. Otherwise, only common
  106.       /// entity properties are copied.
  107.       ///
  108.       /// This example uses the Entity.CopyPropertiesFrom() extension method
  109.       /// included below, which automates the whole shebang.
  110.       ///
  111.  
  112.       [CommandMethod( "MATCHPROPSMGD" )]
  113.       public static void MatchPropMgdCommand()
  114.       {
  115.          Document doc = Application.DocumentManager.MdiActiveDocument;
  116.          Editor ed = doc.Editor;
  117.  
  118.          // Get the source entity:
  119.          PromptEntityOptions peo = new PromptEntityOptions( "\nSelect source object: " );
  120.          peo.AllowObjectOnLockedLayer = true;
  121.          PromptEntityResult perSource = ed.GetEntity( peo );
  122.          if( perSource.Status != PromptStatus.OK )
  123.             return;
  124.  
  125.          // Get the destination entity:
  126.          peo.Message = "\nSelect Destination object: ";
  127.          peo.AllowObjectOnLockedLayer = false;
  128.          PromptEntityResult perDest = ed.GetEntity( peo );
  129.          if( perDest.Status != PromptStatus.OK )
  130.             return;
  131.          if( perDest.ObjectId == perSource.ObjectId )
  132.          {
  133.             ed.WriteMessage( "\nThe source and destination objects are the same" );
  134.             return;
  135.          }
  136.  
  137.          using( Transaction tr = doc.TransactionManager.StartTransaction() )
  138.          {
  139.             Entity source = (Entity) perSource.ObjectId.GetObject( OpenMode.ForRead );
  140.             Entity destination = (Entity) perDest.ObjectId.GetObject( OpenMode.ForWrite );
  141.             destination.CopyPropertiesFrom( source );
  142.             tr.Commit();
  143.          }
  144.       }
  145.  
  146.       // This example accepts a single source Entity and
  147.       // any number of destination entities, and copies the
  148.       // properties of the source entity to the destination
  149.       // entities.
  150.       //
  151.       // This example uses the CopyPropertiesFrom<T>() extension
  152.       // method of IEnumerable<T> where T: Entity, along with the
  153.       // GetObjects<T>() extension method, which together fully-
  154.       // automate the entire process.
  155.  
  156.       [CommandMethod( "PAINTPROPSMGD", CommandFlags.UsePickSet )]
  157.       public static void PaintPropsMgdCommand()
  158.       {
  159.          Document doc = Application.DocumentManager.MdiActiveDocument;
  160.          Editor ed = doc.Editor;
  161.  
  162.          // Get the destination entities:
  163.          PromptSelectionOptions pso = new PromptSelectionOptions();
  164.          pso.MessageForAdding = "Select destination objects: ";
  165.          pso.RejectObjectsFromNonCurrentSpace = true;
  166.          pso.RejectObjectsOnLockedLayers = true;
  167.          PromptSelectionResult psr = ed.GetSelection( pso );
  168.          if( psr.Status != PromptStatus.OK )
  169.             return;
  170.  
  171.          // Get the source entity:
  172.          PromptEntityOptions peo = new PromptEntityOptions( "\nSelect source object: " );
  173.          peo.AllowObjectOnLockedLayer = true;
  174.          PromptEntityResult perSource = ed.GetEntity( peo );
  175.          if( perSource.Status != PromptStatus.OK )
  176.             return;
  177.  
  178.          using( Transaction tr = doc.TransactionManager.StartTransaction() )
  179.          {
  180.             Entity source = (Entity) perSource.ObjectId.GetObject( OpenMode.ForRead );
  181.             psr.Value.GetObjectIds().GetObjects<Entity>( OpenMode.ForWrite ).CopyPropertiesFrom( source );
  182.             tr.Commit();
  183.          }
  184.       }
  185.    }
  186.  
  187.    // This enum is used to indicate what properties should be copied:
  188.  
  189.    [Flags]
  190.    public enum MatchPropFlags
  191.    {
  192.       Color = 0x1,
  193.       Layer = 0x2,
  194.       Linetype = 0x4,
  195.       Thickness = 0x8,
  196.       LinetypeScale = 0x10,
  197.       Text = 0x20,
  198.       Dimension = 0x40,
  199.       Hatching = 0x80,
  200.       Lineweight = 0x100,
  201.       PlotStyleName = 0x200,
  202.       Polyline = 0x400,
  203.       Viewport = 0x800,
  204.       Table = 0x1000,
  205.       Material = 0x2000,
  206.       ShadowDisplay = 0x4000,
  207.       Multileader = 0x8000,
  208.       All = 0xFFFF,
  209.       Entity = Color | Layer | Linetype | Thickness
  210.          | LinetypeScale | Material | Lineweight
  211.          | PlotStyleName | ShadowDisplay
  212.    };
  213.  
  214.    // MatchProperties-related extension methods targeting ObjectId, RXClass, and Entity:
  215.  
  216.    public static class MatchPropertiesExtensions
  217.    {
  218.       static Dictionary<RXClass, MatchProperties> cache = new Dictionary<RXClass, MatchProperties>();
  219.       static RXClass matchPropsClass = RXClass.GetClass( typeof( MatchProperties ) );
  220.       static RXClass entityClass = RXClass.GetClass( typeof( Entity ) );
  221.  
  222.       static MatchPropertiesExtensions()
  223.       {
  224.          SystemObjects.DynamicLinker.ModuleUnloaded += new ModuleUnloadedEventHandler( onModuleUnloaded );
  225.       }
  226.  
  227.       // This event handler is required to invalidate cached protocol
  228.       // extensions in the unlikely event that the module that provides
  229.       // them is unloaded. It'll probably never happen in real life, but
  230.       // we want to cover our tails just in case.
  231.  
  232.       static void onModuleUnloaded( object sender, DynamicLinkerEventArgs e )
  233.       {
  234.          cache.Clear();
  235.       }
  236.  
  237.       // Adds an extension method to ObjectId that returns a MatchProperties instance
  238.       // that can be used to copy properties to the entity having the given ObjectId
  239.       // from to another source entity.
  240.  
  241.       public static MatchProperties GetPropertyPainter( this ObjectId id )
  242.       {
  243.          if( id.IsNull || !id.IsValid )
  244.             throw new ArgumentException( "Null or invalid ObjectId" );
  245.          if( !id.ObjectClass.IsDerivedFrom( entityClass ) )
  246.             throw new ArgumentException( "Requires the ObjectId of an entity or derived type" );
  247.          return GetPropertyPainter( id.ObjectClass );
  248.       }
  249.  
  250.       // This can also be used on entities that are not database-resident:
  251.  
  252.       public static MatchProperties GetPropertyPainter( this Entity entity )
  253.       {
  254.          if( entity == null )
  255.             throw new ArgumentNullException( "entity" );
  256.          return GetPropertyPainter( entity.GetRXClass() );
  257.       }
  258.  
  259.       // Adds an extension method to RXClass that returns a MatchProperties
  260.       // instance for the given runtime class. It is important to remember
  261.       // that you should always pass the runtime class of the 'destination'
  262.       // entity (the one whose properties are to be modified to match the
  263.       // properties of another 'source' entity), and use the returned object
  264.       // to copy properties to that entity using another entity as the source.
  265.       // If you instead get and use the MatchProperties instance from the
  266.       // source entity's runtime class, a failure will likely result if the
  267.       // destination and source entities are of dissimilar types.
  268.  
  269.       public static MatchProperties GetPropertyPainter( this RXClass rxclass )
  270.       {
  271.          MatchProperties result = null;
  272.          if( !cache.TryGetValue( rxclass, out result ) )
  273.          {
  274.             result = GetMatchProperties( rxclass );
  275.             cache.Add( rxclass, result );
  276.          }
  277.          return result;
  278.       }
  279.  
  280.       // It's possible but unlikely that a given runtime class may not
  281.       // produce a MatchProperties protocol extension, and this method
  282.       // can be used to try to get one without throwing an exception.
  283.       // If no protocol extension could be obtained, null is returned,
  284.       // and the result should be checked before attempting to use it.
  285.  
  286.       public static MatchProperties TryGetPropertyPainter( this RXClass rxclass )
  287.       {
  288.          MatchProperties result = null;
  289.          if( !cache.TryGetValue( rxclass, out result ) )
  290.          {
  291.             result = GetMatchProperties( rxclass, false );
  292.             if( result != null )
  293.                cache.Add( rxclass, result );
  294.          }
  295.          return result;
  296.       }
  297.  
  298.       public static MatchProperties TryGetPropertyPainter( this Entity entity )
  299.       {
  300.          return TryGetPropertyPainter( entity.GetRXClass() );
  301.       }
  302.  
  303.       public static MatchProperties TryGetPropertyPainter( this ObjectId id )
  304.       {
  305.          if( id.IsNull || !id.IsValid )
  306.             throw new ArgumentException( "Null or invalid ObjectId" );
  307.          if( !id.ObjectClass.IsDerivedFrom( entityClass ) )
  308.             throw new ArgumentException( "Requires the ObjectId of an entity or derived type" );
  309.          return TryGetPropertyPainter( id.ObjectClass );
  310.       }
  311.  
  312.       public static MatchProperties GetMatchProperties( RXClass rxclass )
  313.       {
  314.          return GetMatchProperties( rxclass, true );
  315.       }
  316.  
  317.       // This method shows the basics of getting a MatchProperties
  318.       // protocol extension object for a given runtime class:
  319.  
  320.       public static MatchProperties GetMatchProperties( RXClass rxclass, bool throwOnFailed )
  321.       {
  322.          if( rxclass == null )
  323.             throw new ArgumentNullException( "rxclass" );
  324.          IntPtr impobj = rxclass.QueryX( matchPropsClass );
  325.          if( impobj == IntPtr.Zero )
  326.          {
  327.             SystemObjects.DynamicLinker.LoadModule( "acmatch.arx", false, false );
  328.             impobj = rxclass.QueryX( matchPropsClass );
  329.             if( impobj == IntPtr.Zero && throwOnFailed )
  330.                throw new Autodesk.AutoCAD.Runtime.Exception( ErrorStatus.LoadFailed );
  331.          }
  332.          if( impobj != IntPtr.Zero )
  333.             return (MatchProperties) DisposableWrapper.Create( typeof( MatchProperties ), impobj, false );
  334.          else
  335.             return null;
  336.       }
  337.  
  338.       // Linq-friendly extension methods that copy properties from a source object
  339.       // to one or more destination objects:
  340.  
  341.       public static void CopyPropertiesFrom( this Entity destination, Entity source )
  342.       {
  343.          CopyPropertiesFrom( destination, source, MatchPropFlags.All );
  344.       }
  345.  
  346.       public static void CopyPropertiesFrom( this Entity destination, Entity source, MatchPropFlags flags )
  347.       {
  348.          if( source == null )
  349.             throw new ArgumentNullException( "source" );
  350.          if( destination == null )
  351.             throw new ArgumentNullException( "destination" );
  352.          CopyProperties( source, destination, (int) flags );
  353.       }
  354.  
  355.       public static void CopyPropertiesFrom<T>( this IEnumerable<T> destination, Entity source )
  356.          where T : Entity
  357.       {
  358.          CopyPropertiesFrom<T>( destination, source, MatchPropFlags.Entity );
  359.       }
  360.  
  361.       public static void CopyPropertiesFrom<T>( this IEnumerable<T> destination, Entity source, MatchPropFlags flags )
  362.          where T : Entity
  363.       {
  364.          if( destination == null )
  365.             throw new ArgumentNullException( "destination" );
  366.          if( source == null )
  367.             throw new ArgumentNullException( "source" );
  368.          int nFlags = (int) flags;
  369.          foreach( T dest in destination )
  370.          {
  371.             CopyProperties( source, dest, nFlags );
  372.          }
  373.       }
  374.  
  375.       static void CopyProperties( Entity source, Entity destination, int flags )
  376.       {
  377.          if( source != destination )
  378.          {
  379.             if( !destination.IsWriteEnabled )
  380.                throw new Autodesk.AutoCAD.Runtime.Exception( ErrorStatus.NotOpenForWrite );
  381.             GetPropertyPainter( destination ).CopyProperties( source, destination, flags );
  382.          }
  383.       }
  384.    }
  385.  
  386.    // Revised/abridged version of the IEnumerable<ObjectId>.GetObjects<T>() extension
  387.    // method. This code is excerpted from the AutoCAD Linq Extension Framework
  388.  
  389.    public static class AcDbLinqExtensions
  390.    {
  391.       public static IEnumerable<T> GetObjects<T>( this IEnumerable<ObjectId> source )
  392.          where T : DBObject
  393.       {
  394.          return GetObjects<T>( source, OpenMode.ForRead, false, false );
  395.       }
  396.  
  397.       public static IEnumerable<T> GetObjects<T>( this IEnumerable<ObjectId> source, OpenMode mode )
  398.          where T : DBObject
  399.       {
  400.          return GetObjects<T>( source, mode, false, false );
  401.       }
  402.  
  403.       public static IEnumerable<T> GetObjects<T>( this IEnumerable<ObjectId> source, OpenMode mode, bool openErased, bool openLocked )
  404.          where T : DBObject
  405.       {
  406.          if( source == null )
  407.             throw new ArgumentNullException( "source" );
  408.          if( source.Any() )
  409.          {
  410.             AcDb.TransactionManager tm = source.First().Database.TransactionManager;
  411.             if( tm.TopTransaction == null )
  412.                throw new Autodesk.AutoCAD.Runtime.Exception( ErrorStatus.NoActiveTransactions );
  413.             RXClass rxclass = RXClass.GetClass( typeof( T ) );
  414.             if( typeof( T ) == typeof( DBObject ) )
  415.             {
  416.                foreach( ObjectId id in source )
  417.                   yield return (T) tm.GetObject( id, mode, openErased, openLocked );
  418.             }
  419.             else if( rxclass.GetHasChildren() )
  420.             {
  421.                foreach( ObjectId id in source )
  422.                {
  423.                   if( id.ObjectClass.IsDerivedFrom( rxclass ) )
  424.                      yield return (T) tm.GetObject( id, mode, openErased, openLocked );
  425.                }
  426.             }
  427.             else
  428.             {
  429.                IntPtr impobj = rxclass.UnmanagedObject;
  430.                foreach( ObjectId id in source )
  431.                {
  432.                   if( id.ObjectClass.UnmanagedObject == impobj )
  433.                      yield return (T) tm.GetObject( id, mode, openErased, openLocked );
  434.                }
  435.             }
  436.          }
  437.       }
  438.  
  439.       // Upgrades a sequence of DBObjects from OpenMode.ForRead to OpenMode.ForWrite,
  440.       // omitting objects on locked layers from the resulting sequence.
  441.  
  442.       public static IEnumerable<T> UpgradeOpen<T>( this IEnumerable<T> src ) where T : DBObject
  443.       {
  444.          foreach( T item in src )
  445.          {
  446.             try
  447.             {
  448.                if( !item.IsWriteEnabled )
  449.                   item.UpgradeOpen();
  450.             }
  451.             catch( Autodesk.AutoCAD.Runtime.Exception ex )
  452.             {
  453.                if( ex.ErrorStatus == ErrorStatus.OnLockedLayer )
  454.                   continue;
  455.                else
  456.                   throw;
  457.             }
  458.             yield return item;
  459.          }
  460.       }
  461.  
  462.       // Returns true if at least one RXClass is derived from
  463.       // the given RXClass.
  464.  
  465.       public static bool GetHasChildren( this RXClass rxclass )
  466.       {
  467.          IntPtr impobj = rxclass.UnmanagedObject;
  468.          foreach( DictionaryEntry e in SystemObjects.ClassDictionary )
  469.          {
  470.             if( ( (RXClass) e.Value ).MyParent.UnmanagedObject == impobj )
  471.                return true;
  472.          }
  473.          return false;
  474.       }
  475.    }
  476. }
  477.  
  478.  
« Last Edit: August 10, 2012, 04:21:59 AM by TT »

Jeff H

  • Needs a day job
  • Posts: 6150
Re: AttSync for .Net
« Reply #23 on: August 11, 2012, 04:47:24 PM »
So thats how its done.
Thanks!

Matus

  • Guest
Re: AttSync for .Net
« Reply #24 on: April 03, 2013, 08:05:49 AM »
Hi Andrey,
I know this post is an old one, but I found it through google. I used your code and I found there's a problem with multiline attributes.
You have a placeholder for the condition, but it's not used. I solved it with the following code:

Code: [Select]
                   If attref.IsMTextAttribute Then
                       
                        Dim value As String = attref.MTextAttribute.Text
                        Dim newValue() As String = value.Split(New Char() {Chr(13)}, StringSplitOptions.RemoveEmptyEntries)
                        attref.SetAttributeFromBlock(ad, br.BlockTransform)
                       
                        attref.TextString = String.Join(vbCrLf, newValue)

                    Else
                       
                        Dim value As String = attref.TextString
                        attref.SetAttributeFromBlock(ad, br.BlockTransform)
                       
                        attref.TextString = value

                    End If

Sorry it's VB, but i guess you can understand it.

Also there's a thing in VB, you get a warning, that iteration variables should not be used in lambda expressions, because you can get unexpected results.
And I can confirm that the results are unexpected, occasional and unpredictable.
Maybe C# doesn't have this problems, but i modified this part too:

Code: [Select]

                    Dim attref1 As AttributeReference = attref
                    Dim ad As AttributeDefinition = attdefs.First(Function(n) n.Tag = attref1.Tag)


I also wanted to say thank you for this very useful utility.

Matus

Andrey Bushman

  • Swamp Rat
  • Posts: 864
Re: AttSync for .Net
« Reply #25 on: April 03, 2013, 08:57:08 AM »
Thank you, Matus.

Yes, you are right, my code is not ideal. Also it has problems with the Fields (as I remember). I'll try to fix the problems in the nearest future. Above TT wrote a lot of code, but I yet didn't read it (many letters). I wrote this code for a long time ago, and I forgot it. I need time to remember.

Regards