Sunday, November 5, 2023

Preventing "Explodable" Property of a Block Definition from Being Changed

A recent thread in the AutoCAD .NET API forum discussed a scenario of how to prevent user to use Block Editor to change the "Explodable" property of a block definition (the OP of that discussion thread wants to keep the block as non-explodable). In my reply to that question, I suggested to use SystemVariableChanged even handler to monitor the system variable "BlockEditor" to detect if user opens or close Block Editor.

Well, after digging in deeper, I found I was wrong: when system variable "BlockEditor" changes its value (0 or 1, when the Block Editor is opened, or closed), the SystemVariableChange event is not triggered. According to AutoCAD .NET API, it is not guaranteed that SystemVariableChanged event is triggered when these system variables' value is changed by certain commands. Unfortunately, system variable "BlockEditor" is one of them.

On the other handle, using code to test whether a block definition's "Explodable" property is true/false and change it is rather easy. So the real issue here is to decide when to run the code to detect the change and reverse it back if necessary. In fact, one can trigger the code running with many events that occur with AutoCAD application, document, or database, for example, Application.Idle event, or Document.CommandEnded event. The only issue is, with these event being fired, the chance of "Explodable" property being changed are quite low, so the code would run with nothing being done in most cases. While it is mostly harmless, it would be better to only run code when use does something, in which the "Explodable" property is likely being changed. Obviously, if user opens Block Editor, the chance of "Explodable" property being changed is higher.

With this in mind, I stick with the approach of watching SystemVariableChange events to see what happen when I open and close BlockEditor. Here are what I found:

1. When user opens Block Editor (selecting a block, right-clicking to show context menu and selecting "Block Editor...", or simply entering command "BEDIT"), SystemVariableChanged events fire against following system variables:

USCNAME, CLAYER, VIEWDIR

2. When user closes Block Editor, SystemVariableChanged events fire against following system variables:

UCSNAME, CLAYER, EXTMIN, EXTMAX, CANNOSCALE

So, I thought I can detect if Block Editor is opened or closed when these 2 group of system variables are changed. When Block Editor is detected being opened, I can safely assume its close will be detected, unless the user shuts down AutoCAD without closing it (then even use indeed changes a block's "Explodable" property, it will not take effect unless the Block Editor is closed properly). Therefore, as long as I detected Block Editor is opened and then closed, there is chance the user has changed the "Explodable" property of a block definition, thus, I need to run the code to make sure the change is reversed back after the Block Editor is closed.

The code is rather simple, as shown here:

using System;
using System.Collections.Generic;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
 
namespace StopBlockExplosion
{
    public class BlockExplosionGuard
    {
        private readonly IEnumerable<string> _blockNames;
        private bool _enabled = false;
        private bool _blockEditorOn = false;
 
        public BlockExplosionGuard(IEnumerable<stringtargetBlockNames)
        {
            _blockNames = targetBlockNames;
        }
 
        #region public methods
 
        public bool IsEnabled=>_enabled;
        public void Enable(bool enable)
        {
            _enabled= enable;
            if (_enabled)
            {
                CadApp.SystemVariableChanged += CadApp_SystemVariableChanged;
            }
            else
            {
                CadApp.SystemVariableChanged -= CadApp_SystemVariableChanged;
            }
        }
 
        #endregion
 
        #region private method: reverse changed "Explodable" property of the BlockTableRecord
 
        private void VerifyNonExplodableBlocksInCurrentDwg(Document dwg)
        {
            using (var tran = dwg.TransactionManager.StartTransaction())
            {
                var blkTable = (BlockTable)tran.GetObject(
                    dwg.Database.BlockTableId, OpenMode.ForRead);
                foreach (var blkName in _blockNames)
                {
                    if (!blkTable.Has(blkName)) continue;
                    var blk = (BlockTableRecord)tran.GetObject(
                        blkTable[blkName], OpenMode.ForRead);
                    if (blk.Explodable)
                    {
                        CadApp.ShowAlertDialog(
                            $"Block \"{blk.Name} has been accidently changed to EXPLODABLE!\n\n" +
                            "Our CAD standard now forces it back to NON-EXPLODABLE!");
                        blk.UpgradeOpen();
                        blk.Explodable = false;
                    }
                }
 
                tran.Commit();
            }
        }
 
        #endregion
 
        #region private methods
 
        private void CadApp_SystemVariableChanged(
            object sender, Autodesk.AutoCAD.ApplicationServices.SystemVariableChangedEventArgs e)
        {
            var vName = e.Name.ToUpper();
 
            if (!_blockEditorOn)
            {
                // detect Block Editor is turned on
                if (vName == "UCSNAME" || vName == "CLAYER" || vName == "VIEWDIR")
                {
                    var val = (short)CadApp.GetSystemVariable("BLOCKEDITOR");
                    if (val == 1)
                    {
                        _blockEditorOn = true;
                    }
                }
            }
            else
            {
                if (vName == "UCSNAME" || vName == "CLAYER" || 
                    vName == "EXTMIN" || vName =="EXTMAX" || vName=="CANNOSCALE")
                {
                    var val = (short)CadApp.GetSystemVariable("BLOCKEDITOR");
                    if (val == 0)
                    {
                        _blockEditorOn = false;
                        CadApp.Idle += CadApp_Idle;
                    }
                }
            }
        }
 
        private void CadApp_Idle(object sender, EventArgs e)
        {
            CadApp.Idle -= CadApp_Idle;
 
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            using (dwg.LockDocument())
            {
                VerifyNonExplodableBlocksInCurrentDwg(dwg);
            }
        }
 
        #endregion
    }
}

To place the code into work:

using Autodesk.AutoCAD.Runtime;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
 
[assembly: CommandClass(typeof(StopBlockExplosion.MyCommands))]
 
namespace StopBlockExplosion
{
    public class MyCommands
    {
        private static BlockExplosionGuard _blkExplosionGuard = null;
 
        [CommandMethod("NoBlkExp")]
        public static void RunMyCommand()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var editor = dwg.Editor;
 
            if (_blkExplosionGuard==null)
            {
                _blkExplosionGuard = new BlockExplosionGuard(
                    new[] { "TestBlk1""TestBlk2" });
            }
 
            if (!_blkExplosionGuard.IsEnabled)
            {
                _blkExplosionGuard.Enable(true);
                editor.WriteMessage(
                    "\nBlockExplosionGuard is enabled.");
            }
            else
            {
                _blkExplosionGuard.Enable(false);
                editor.WriteMessage(
                    "\nBlockExplosionGuard is disabled.");
            }
        }
    }
}

The video clip below showing the code in action:





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.