Wednesday, May 13, 2020

Prevent Certain Properties of an Entity from Being Changed

In the past I wrote about using ObjectOverrule to prevent entities in AutoCAD from being changed/modified and to force entities being changed in certain way.

An interesting question was raised recently in the Autodesk's discussion forum on how to disable geometry editing. To me, the question could have been a more general one: how to make some of properties of an entity not changeable, while other still can be changed? Once again, ObjectOverrule can play well in the game.

Here is the general idea of doing it with ObjectOverrule:

1. Overriding ObjectOverrule.Open() method: if an entity is opened for write, the values of some target properties, which we do not want them to be changed, will be saved, tagged with ObjectId.

2 Overriding ObjectOverrule.Close() method: check whether there is saved original property value for the entity (by its ObjectId), if found, restore the properties with original values.

Based on this idea, I designed a custom ObjectOverrule. Some explanations are following:

1. The Overrule applies to more general type Entity. It can be more specific, such as Curve, Line..., as needed;

2. No filter is set up. But if needed, proper filter would reduce the overhead in AutoCAD caused by Overrule, minor, or significant;

3. Paired Func/Action is injected into the Overrule whenever the Overrule is enabled. The pair of Func/Action plays the role of extracting original property values in Open() method, and restoring property values in Close() method. This way, the actual value extracting/restoring code is separated from the Overrule itself, so programmer can easily write code to decide what properties to target on different type of entities.

Note: It is even possible to abstract the paired Func/Action into an interface, and implement them in separate project, and make them configurable, so that the custom Overrule can be configured to target different properties, different entity types without having to update code at all. But I'll leave this out of this article.

Here are the code.

Class EntityTarget: it has EntityType property and the pair of Func/Action as properties, used to extract target entity's property values and restore them later.

public class EntityTarget
{
    public Type EntityType { setget; }
    public Func<EntityDictionary<stringobject>> PropExtractFunc
    { setget; }
    public Action<EntityDictionary<stringobject>> PropRestoreAction
    { setget; }
}

Class PropertyFreezeOverrule: its has overridden Open<> and Close() method, in which entity's target property values are extracted into a Dictionary, and then saved in Dictionary keyed with ObjectId at Overrule class level; when the entity is closed, the values of entity's target properties will be restored, if necessary. This, target properties of the entity become not changeable.

using System.Collections.Generic;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
 
namespace FreezePropertyOverrule
{
    public class PropertyFreezeOverrule : ObjectOverrule
    {
        private Dictionary<ObjectIdDictionary<stringobject>> _openedEntities = 
            new Dictionary<ObjectIdDictionary<stringobject>>();
        private List<EntityTarget> _targets = new List<EntityTarget>();
 
        private bool _overruling = false;
 
        private bool _started = false;
 
        public void Start(IEnumerable<EntityTargetoverruleTargets)
        {
            if (_started) Stop();
 
            _targets.Clear();
            _targets.AddRange(overruleTargets);
 
            _openedEntities.Clear();
 
            _overruling = Overrule.Overruling;
 
            Overrule.AddOverrule(RXClass.GetClass(typeof(Curve)), thistrue);
            Overruling = true;
            _started = true;
        }
 
        public void Stop()
        {
            Overrule.RemoveOverrule(RXClass.GetClass(typeof(Curve)), this);
            Overrule.Overruling = _overruling;
            _started = false;
        }
 
        public override void Open(DBObject dbObjectOpenMode mode)
        {
            if (mode == OpenMode.ForWrite)
            {
                ExtractTargetProperties(dbObject);
            }
            base.Open(dbObjectmode);
        }
 
        public override void Close(DBObject dbObject)
        {
            RestoreTargetProperties(dbObject);
            base.Close(dbObject);
        }
 
        #region private methods
 
        private void ExtractTargetProperties(DBObject dbObject)
        {
            foreach (var target in _targets)
            {
                if (dbObject.GetRXClass()==RXClass.GetClass(target.EntityType))
                {
                    var propData = target.PropExtractFunc(dbObject as Entity);
                    if (_openedEntities.ContainsKey(dbObject.ObjectId))
                    {
                        _openedEntities[dbObject.ObjectId] = propData;
                    }
                    else
                    {
                        _openedEntities.Add(dbObject.ObjectId, propData);
                    }
                    break;
                }
            }
        }
 
        private void RestoreTargetProperties(DBObject dbObject)
        {
            if (dbObject.IsUndoing) return;
            if (!dbObject.IsModified) return;
            if (dbObject.IsErased) return;
            if (!dbObject.IsWriteEnabled) return;
 
            if (!_openedEntities.ContainsKey(dbObject.ObjectId)) return;
 
            foreach (var target in _targets)
            {
                if (dbObject.GetRXClass() == RXClass.GetClass(target.EntityType))
                {
                    var propData = _openedEntities[dbObject.ObjectId];
                    if (propData!=null)
                    {
                        target.PropRestoreAction(dbObject as EntitypropData);
                        PropertyExtractRestoreUtils.SendMessageToCommandLine(
                            "\nWARNING: editing to this entity was overrule. No change is allowed!\n");
                    }
                    _openedEntities.Remove(dbObject.ObjectId);
                    break;
                }
            }
        }
        #endregion
    }
}

Class PropertyExtracRestoreUtils: it defines a series of paired Func/Action to extracting/restoring entity of specific type. I use the naming convention of GetXxxxProperties() and RestoreXxxxProperties(), where Xxxx is the entity type. For the simplicity, I only defined the pair methods for Line and Circle. But it is easy to add more pairs to target other entity types. Since I also only want to keep Line/Circle geometrically frozen, so, for Line, the properties to freeze are StartPoint and EndPoint; for Circle, Center and Radius.

public static class PropertyExtractRestoreUtils
{
    private const string LINE_START_POINT = "StartPoint";
    private const string LINE_END_POINT = "EndPoint";
 
    private const string CIRCLE_CENTER = "Center";
    private const string CIRCLE_RADIUS = "Radius";
 
    public static Dictionary<stringobjectGetLineProperties(Entity line)
    {
        Dictionary<stringobjectprops = null;
 
        var l = line as Line;
        if (l!=null)
        {
            props = new Dictionary<stringobject>();
            props.Add(LINE_START_POINT, l.StartPoint);
            props.Add(LINE_END_POINT, l.EndPoint);
        }
 
        return props;
    }
 
    public static void RestoreLineProperties(
        Entity lineDictionary<stringobjectproperties)
    {
        var l = line as Line;
        if (l != null)
        {
            if (properties.ContainsKey(LINE_START_POINT) &&
                properties.ContainsKey(LINE_END_POINT))
            {
                l.StartPoint = (Point3d)properties[LINE_START_POINT];
                l.EndPoint = (Point3d)properties[LINE_END_POINT];
            }
        }
    }
 
    public static Dictionary<stringobjectGetCircleProperties(Entity circle)
    {
        Dictionary<stringobjectprops = null;
        var c = circle as Circle;
        if (c!=null)
        {
            props = new Dictionary<stringobject>();
            props.Add(CIRCLE_CENTER, c.Center);
            props.Add(CIRCLE_RADIUS, c.Radius);
        }
 
        return props;
    }
 
    public static void RestoreCircleProperties(
        Entity circleDictionary<stringobjectproperties)
    {
        var c = circle as Circle;
        if (c != null)
        {
            if (properties.ContainsKey(CIRCLE_CENTER) &&
                properties.ContainsKey(CIRCLE_RADIUS))
            {
                c.Center = (Point3d)properties[CIRCLE_CENTER];
                c.Radius = (double)properties[CIRCLE_RADIUS];
            }
        }
    }
 
    public static void SendMessageToCommandLine(string msg)
    {
        var ed = Autodesk.AutoCAD.ApplicationServices.Application.
            DocumentManager.MdiActiveDocument.Editor;
        ed.WriteMessage(msg);
    }
}

Following CommandClass put all together into work:

using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
 
[assemblyCommandClass(typeof(FreezePropertyOverrule.MyCommands))]
 
namespace FreezePropertyOverrule
{
    public class MyCommands 
    {
        private static PropertyFreezeOverrule _freezeOverrule = null;
 
        [CommandMethod("StartFreeze")]
        public static void StartMyOverrule()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var ed = dwg.Editor;
 
            if (_freezeOverrule==null)
            {
                _freezeOverrule = new PropertyFreezeOverrule();
            }
 
            var targets = new EntityTarget[]
            {
                new EntityTarget
                {
                    EntityType=typeof(Line), 
                    PropExtractFunc=PropertyExtractRestoreUtils.GetLineProperties, 
                    PropRestoreAction=PropertyExtractRestoreUtils.RestoreLineProperties 
                },
                new EntityTarget
                {
                    EntityType=typeof(Circle),
                    PropExtractFunc=PropertyExtractRestoreUtils.GetCircleProperties,
                    PropRestoreAction=PropertyExtractRestoreUtils.RestoreCircleProperties
                }
            };
 
            _freezeOverrule.Start(targets);
            ed.WriteMessage(
                "\nEntity freezing overrule started\n");
        }
 
        [CommandMethod("StopFreeze")]
        public static void StopMyOverrule()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
 
            var ed = dwg.Editor;
            if (_freezeOverrule!=null)
            {
                _freezeOverrule.Stop();
            }
 
            ed.WriteMessage(
                "\nEntity freezing overrule stopped.\n");
        }
    }
}

The video clip below shows how the code works: if the Overrule is enabled (started), Line entity cannot be extended, shortened, moved, rotated..., while Circle also cannot be enlarged, shrunk, or moved. However, their other non-geometric properties, such as Layer, Color..., can still be changed. Once the Overrule is disabled/stopped, Line and Circle can be fully modified.



No comments:

Followers

About Me

My photo
After graduating from university, I worked as civil engineer for more than 10 years. It was AutoCAD use that led me to the path of computer programming. Although I now do more generic business software development, such as enterprise system, timesheet, billing, web services..., AutoCAD related programming is always interesting me and I still get AutoCAD programming tasks assigned to me from time to time. So, AutoCAD goes, I go.