setting Database.Cannoscale and deleting the old scale

setting Database.Cannoscale and deleting the old scale

Anonymous
Not applicable
1,027 Views
2 Replies
Message 1 of 3

setting Database.Cannoscale and deleting the old scale

Anonymous
Not applicable

What am I leaving out?  I'm trying to open a .dwg, set its Database.Cannoscale property, and delete all annoscales except one.  But whatever scale was active when I opened the drawing, can't be deleted because it's supposedly in use, and this happens ONLY when the dwg is opened as a side Database, not when it's open in the editor.

 

Example, let's say the .dwg's current annoscale when I open it is called "scale A."

I set Database.Cannoscale to "scale B."  (Later I can look at the saved dwg and confirm this was successful.)

I iteratively remove all annoscales except "scale B" from every annotative object in the dwg.

The drawing has no viewports in it, so no worries there.

So the only annoscale in use at this point should be scale B.

Next I try to delete all annoscales from the dwg except scale B.

If the drawing is currently open in the editor (i.e. if I'm using MdiActiveDocument.Database), this works.

But if it's open as a side Database, "scale A" can't be deleted because it's supposedly in use.  I get an eObjectIsReferenced exception from the ACAD runtime.

Basically whatever scale was active when opening the drawing, remains "in use" despite setting the .Cannoscale to something else.

 

I tried putting the Database.Cannoscale setting code inside a transaction - no luck.

I tried saving the Database file and reopening it, in the hope of forcing something to update - no luck.

I'm remembering to set HostApplicationServices.WorkingDatabase to my database temporarily, and restoring it at the end (both when using a side Database and when using MdiActiveDocument.Database) - no luck.

 

Is there some step or command I'm missing?

Code available on request, but first I thought I'd see if this sounds familiar to anybody.

0 Likes
1,028 Views
2 Replies
Replies (2)
Message 2 of 3

SENL1362
Advisor
Advisor

Thanks to Kean i've written some methodes to remove these Annotation Scales

Start with AnnotationScaleReset. It will create a "1:1" Scale if it not exists or reset that scale to "1:1"

Then all annotationscale references will be removed from the drawing objects, including Viewports.

Finally the annotationscales, except the "1:1", will be removed from the drawing.

 

This too solves the problems with the display of Linetypes beeing shown as solids in Modelspace.

Although this also may be corrected by the user manually:

    1. Check (Global)LineTypeScale //see pulldown linetypes/others
    2. LsLtScale=1, MsLtScale=0
    3. Show AnnotationScale //Statusbar(3 horizontal lines)/Anotationscale
    4. Click AnnotatioScale/Scroll Down at end, select Percentage
    5. Set Scale: 1:1/100%

 

 

Warning: Removing the AnnotationScales may alter the (look of) drawing.

Inspired by Kean:

  //https://www.keanw.com/2011/10/delete-all-but-current-annotation-scales-on-autocad-objects-using-net....

 

 

public static void AnnotationScaleReset(Database db)
{
    if (db == null)
    {
        throw new System.Exception($"Invalid Argument: {nameof(db)} IS NULL");
    }

    var antScales = GetAnnotationScales(db);
    if (antScales == null)
        return;
    var antScale1To1 = CreateAnnotationScale(db, "1:1", 1, 1);
    db.Cannoscale = antScale1To1;

    foreach (var kvp in antScales)
    {
        RemoveAnnotScale(db, kvp.Key);
    }
    PurgeAnnotScales(db, antScales);
}
public static Dictionary<string, AnnotationScale> GetAnnotationScales(Database db)
{
    Dictionary<string, AnnotationScale> annScales = null;

    if (db == null)
    {
        throw new System.Exception($"Invalid Argument: {nameof(db)} IS NULL");
    }

    var ocm = db.ObjectContextManager;
    if (ocm == null)
        throw new System.Exception($"Failed to Connect with ObjectContextManager");
    var occ = ocm.GetContextCollection("ACDB_ANNOTATIONSCALES");
    if (occ == null)
        return null;

    annScales = new Dictionary<string, AnnotationScale>(StringComparer.OrdinalIgnoreCase);
    using (var tr = db.TransactionManager.StartTransaction())
    {
        foreach (ObjectContext oc in occ)
        {
            var annScale = oc as AnnotationScale;
            if (annScale != null)
            {
                var id = new ObjectId(annScale.UniqueIdentifier);
                annScales[annScale.Name] = annScale;
            }
        }
        tr.Commit();
    }
    return annScales;
}
public static AnnotationScale CreateAnnotationScale(Database db, string scaleName, double paperUnits, double drawingUnits)
{
    AnnotationScale annotScale = null;

    if (db == null)
    {
        throw new System.Exception($"Invalid Argument: {nameof(db)} IS NULL");
    }
    if (string.IsNullOrWhiteSpace(scaleName))
    {
        throw new System.Exception($"Invalid Argument: {nameof(scaleName)} IS NULL");
    }
    if (paperUnits == 0)
    {
        throw new System.Exception($"Invalid Argument: {nameof(paperUnits)} IS NULL");
    }
    if (drawingUnits == 0)
    {
        throw new System.Exception($"Invalid Argument: {nameof(drawingUnits)} IS NULL");
    }

    var ocm = db.ObjectContextManager;
    if (ocm == null)
        throw new System.Exception($"Failed to Connect with ObjectContextManager");

    var occ = ocm.GetContextCollection("ACDB_ANNOTATIONSCALES");
    if (occ != null)
    {
        annotScale = occ.GetContext(scaleName) as AnnotationScale;
        if (annotScale == null)
        {
            annotScale = new AnnotationScale();
            annotScale.Name = scaleName;
            annotScale.PaperUnits = paperUnits;
            annotScale.DrawingUnits = drawingUnits;
            occ.AddContext(annotScale);
        }
        annotScale.PaperUnits = paperUnits;
        annotScale.DrawingUnits = drawingUnits;
    }
    return annotScale;
}
public static void RemoveAnnotScale(Database db, string antScaleName)
{
    if (db == null)
    {
        throw new System.Exception($"Invalid Argument: {nameof(db)} IS NULL");
    }

    if (string.IsNullOrWhiteSpace(antScaleName))
    {
        throw new System.Exception($"Invalid Argument: {nameof(antScaleName)} IS NULL");
    }

    var ocm = db.ObjectContextManager;
    if (ocm == null)
        throw new System.Exception($"Failed to Connect with ObjectContextManager");
    var occ = ocm.GetContextCollection("ACDB_ANNOTATIONSCALES");
    if (occ != null)
    {
        var oct = occ.GetContext(antScaleName);
        using (var tr = db.TransactionManager.StartTransaction())
        {
            var blkTbl = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
            foreach (ObjectId blkId in blkTbl)
            {
                var blk = (BlockTableRecord)tr.GetObject(blkId, OpenMode.ForRead);
                if (blk.Annotative == AnnotativeStates.True && blk.HasContext(oct))
                {
                    blk.UpgradeOpen();
                    //blk.Annotative = AnnotativeStates.False;
                    blk.RemoveContext(oct);
                }

                foreach (var id in blk)
                {
                    var obj = tr.GetObject(id, OpenMode.ForRead) as DBObject;
                    if (obj.Annotative == AnnotativeStates.True && blk.HasContext(oct))
                    {
                        obj.UpgradeOpen();
                        //obj.Annotative = AnnotativeStates.False;
                        obj.RemoveContext(oct);
                    }
                    else if (obj is Viewport)
                    {
                        var vp = obj as Viewport;
                        vp.UpgradeOpen();
                        vp.AnnotationScale = db.Cannoscale;
                        //Not Implemented Yet: vp.RemoveContext(oct);
                    }
                }
            }
            tr.Commit();
        }
    }
    return;
}

 

public static void PurgeAnnotScales(Database db, Dictionary<string, AnnotationScale> scalesToRemove)
{
    if (db == null)
    {
        throw new System.Exception($"Invalid Argument: {nameof(db)} IS NULL");
    }

    if (scalesToRemove == null || scalesToRemove.Count == 0)
        return;

    var ocm = db.ObjectContextManager;
    if (ocm == null)
        throw new System.Exception($"Failed to Connect with ObjectContextManager");

    var occ = ocm.GetContextCollection("ACDB_ANNOTATIONSCALES");
    if (occ != null)
    {
        using (var tr = db.TransactionManager.StartTransaction())
        {
            //Purge all ScaleList.
            var oic = new ObjectIdCollection();
            foreach (var kvp in scalesToRemove)
            {
                var antScale = kvp.Value;
                oic.Add(new ObjectId(antScale.UniqueIdentifier));
            }
            db.Purge(oic);
            foreach (ObjectId id in oic)
            {
                try
                {
                    var obj = tr.GetObject(id, OpenMode.ForWrite);
                    obj.Erase();
                }
                catch //(System.Exception ex)
                {
                }
            }
            tr.Commit();
            scalesToRemove = null;
        }
    }
}

 

 

 

 

 

 

 

 

 

0 Likes
Message 3 of 3

Anonymous
Not applicable

Thank you for your reply.  I like your examples, especially because they are very similar to mine! Smiley Happy

 

Yes, my code does much the same as yours - including setting any viewports to 1:1 (or deleting them outright, which I have the luxury of doing in some cases), and it also removes the unwanted scales from every annotative object I can find in the drawing (see first few code snips below).  But even after all this, whichever scale had been active when the file was first read into memory, was not "freed up" and made deleteable.

 

I came up with a hacky but satisfactory solution:  S ave and dispose the Database after setting Database.Cannoscale to 1:1, and then reopen the Database to continue with the rest of the process (see code snips more toward the bottom).  For some reason, that worked.  But I'm not marking my own answer as the solution, because I'm hoping someone out there has a better one, or can at least explain this weird behavior.

 

Deleting Scales from All Annotative Objects (kind of a detour, just for interest's sake)

First I get the ObjectIds of all annotative objects in all layouts:

public static List<ObjectId> GetAnnoObjectIds(Database db)
{
	var result = new List<ObjectId>();
	using (var trans = db.TransactionManager.StartTransaction())
	{
		var blockTable = (BlockTable)trans.GetObject(db.BlockTableId, OpenMode.ForRead);
		//iterate through all layouts including model space
		var layoutDict = (DBDictionary)trans.GetObject(db.LayoutDictionaryId, OpenMode.ForWrite);
		foreach (DBDictionaryEntry layoutEntry in layoutDict)
		{
			var layout = (Layout)trans.GetObject(layoutEntry.Value, OpenMode.ForRead);
			var layoutBTR = (BlockTableRecord)trans.GetObject(layout.BlockTableRecordId, OpenMode.ForRead);
			//iterate through objects in this layout
			foreach (ObjectId objId in layoutBTR)
			{
				var obj = trans.GetObject(objId, OpenMode.ForRead);
				if (obj.Annotative == AnnotativeStates.True) result.Add(obj.ObjectId);
				obj.Dispose();
			}
		}
		trans.Commit();
	}
	return result;
}

 

Then I get the current scale list, using a method similar to your GetAnnotationScales() that I won't show here because it's so similar.  The only difference is that instead of returning a Dictionary, it returns a List<> of a data transfer object I've defined (that I will show here), for working with scales elsewhere in my codebase:

public class ScaleDTO
{
	public bool isArchitectural { get; set; }
	public bool isCivil { get; set; }
	public string Name { get; set; }
	public string LongName { get; set; }
	public double ModelUnits { get; set; }
	public double PaperUnits { get; set; }
}

Now that I have the List<ObjectId> of annotative object IDs, and the List<ScaleDTO> of current scales, I use the below method to delete all those scales from all those objects:

public static void DeleteScalesFromObjects(Database db, List<ScaleDTO> scales, List<ObjectId> objectIds)
{
	//get ObjectContexts
	var occ = db.ObjectContextManager.GetContextCollection("ACDB_ANNOTATIONSCALES");
	var contextsToDelete = new List<ObjectContext>();
	foreach (var scale in scales)
		if (occ.HasContext(scale.Name) && scale.Name != "1:1" && scale.Name != db.Cannoscale.Name)
			contextsToDelete.Add(occ.GetContext(scale.Name));

	//iterate through DBObjects
	using (var trans = db.TransactionManager.StartTransaction())
	{
		foreach (var objectId in objectIds)
		{
			var obj = trans.GetObject(objectId, OpenMode.ForWrite);
			//remove contexts from object
			foreach (var context in contextsToDelete)
				if (obj.HasContext(context))
					obj.RemoveContext(context);
		}
		trans.Commit();
	}
}

Hacky Quasi-Solution: Saving & Disposing the Database in Between

Again, the above measures, combined with resetting or deleting viewports, and setting Database.Cannoscale, were for some reason not enough to free up the previous Database.Cannoscale for deletion.  I would get an eObjectIsReferenced exception.  So my hacky solution was to basically split the steps of the process into the below method (that only sets the .Cannoscale to 1:1, saves and disposes), and the rest of the process (i.e. delete or reset viewports, delete scales from annotative objects as above, and delete scales from the drawing itself).  For unknown reasons, that worked.  Note that the below method calls my method AddScalesToDwg() that I don't show here, but is similar to your CreateAnnotationScale().  (Except I use a List.  Apparently I love Lists.)

/// <summary>
/// Creates if necessary the annotative scale 1:1 in the given .dwg file, and sets it current in model space via the Database.Cannoscale property.
/// Returns a List&lt;string&gt; of errors, which is empty in the success condition.
/// </summary>
/// <param name="filePath">The full path to the .dwg file</param>
public static List<string> SetUnityScaleCurrent(string filePath)
{
	var errors = new List<string>();

	//make sure path exists and points to a .dwg
	if (Path.GetExtension(filePath).ToUpper() != ".DWG")
	{
		errors.Add("Can't add annoscale 1:1 to " + filePath + " (file isn't a .dwg).");
		return errors;
	}
	else if (!File.Exists(filePath))
	{
		errors.Add("Can't add annoscale 1:1 to " + filePath + " (file doesn't exist).");
		return errors;
	}

	//load file into side database
	var db = new Database(false, true);
	db.ReadDwgFile(filePath, FileOpenMode.OpenForReadAndWriteNoShare, false, null);
	db.CloseInput(true);

	//make sure drawing has unity scale
	var unityScaleDTO = new ScaleDTO() { Name = "1:1", LongName = "1:1", ModelUnits = 1, PaperUnits = 1, ScaleFactor = 1 };
	var occ = db.ObjectContextManager.GetContextCollection("ACDB_ANNOTATIONSCALES");
	if (!occ.HasContext("1:1"))
		errors.AddRange(AddScalesToDwg(db, new List<ScaleDTO>() { unityScaleDTO }));
	var unityScaleContext = occ.GetContext("1:1");

	//set unity scale current
	db.Cannoscale = (AnnotationScale)unityScaleContext;

	//save file & dispose
	db.SaveAs(db.Filename, db.OriginalFileVersion);
	db.Dispose();

	return errors;
}
0 Likes