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

0 Members and 1 Guest are viewing this topic.

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: 6144
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: 6144
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: 6144
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