Программирование BricsCAD.Net

Эта статья для программистов о портировании плагинов AutoCAD в BricsCAD с использованием .Net API. Я написал ее как шпаргалку для себя, но решил оформить и опубликовать для всех, кто хочет расширить область применения своих программ с Автокада на БриксКАД. Т.к. .Net API написаны не в Bricsys, а в ODA, то я подозреваю, что вы встретите ровно те же самые проблемы и в остальных программах на базе библиотек Teigha: NanoCAD, ZWCAD...

Я рад что у Автокада появилось несколько достойных альтернатив. Возможно это подстегнет Автодеск вытащить Автокад из многолетнего застоя. Я вовсе не планирую критиковать недоделки API БриксКАД. Хотя вся статья - это список недоделок, странностей и ошибок. Моя цель - помочь программистам не наступить на те же грабли по которым прошел я.

Все что написано, касается только .Net API. Примеры решений написаны на C#. Я использовал BricsCAD V17, V18, V19 когда работал над портированием. Вполне возможно, что эти ошибки не проявятся у вас. Я не разбирался, насколько они глобальны. Но вам стоит провести тестирование вашего плагина, если вы использует перечисленные далее методы. Я буду рад получить от читателей письма с конструктивной критикой и своими способами решения проблем.

И вот с чем я столкнулся:

Общие замечания

    • Не про API, но мешает: БриксКАД в большинстве случаев не может открывать файлы, созданные в Автокад с использованием видов _viewbase (ModelDoc). Решается вызовом команды _recover Исправлено V20?

    • Сделать одну dll для Автокада и Брикскада не получится. Нужны ссылки на другие библиотеки (BrxMgd.dll, TD_Mgd.dll и TD_MgdBrep.dll, аналога AcCui нет). Придется создавать отдельные проекты VS для БриксКАДа под каждый плагин.

    • БриксКАД невероятно быстр. НО не .Net API. Я был удивлен, как медленно исполняются мои команды. В лучшем случае в полтора раза медленней, чем под Автокадом. А в худшем зависают навсегда. Требуется более серьезная оптимизация кода.

    • Нет никакой документации по .Net API BricsCAD. Не ищите. На офсайте - пара слов, как будто нет никаких отличий от Автокада. Но все понимают, что это невозможно. Указано только, что нельзя использовать объекты после закрытия транзакции. В Автокаде это гарантирует фатал, зачем было писать? Приходится использовать документацию от Автокада и на каждом шагу сталкиваться с проблемами. Надеюсь моя статья-шпаргалка восполнит этот пробел.

    • Выбить Автокад с фаталом проще простого. BricsCAD куда более крепкий орешек. Однако, когда ваш код все-таки вызывает фатальную ошибку, то BricsCAD ведет себя странно: работа вашей команды обрывается, никаких сообщений об ошибке, прогресс-бар по прежнему на экране, но при этом вы можете работать с чертежем, вызывать другие команды. Можно даже сохранить чертеж. Но нельзя его изменять. И только при выходе из BricsCAD, все-таки выпадет сообщение о фатальной ошибке.

    • Как указано на офсайте требуется подключить к проекту другие библиотеки и изуродовать раздел using в каждом файле исходного кода. Не сложно, но нудно. Причем некоторые пространства имен разделились на Bricscad и Teigha и не понятно, что теперь где. Плюшки VS по оптимизации using станут недоступными.

    • Когда вы включаете галочку компилятора "оптимизировать код", то плагин может начать работать не так как в отладочном режиме. Может просто перестать работать. В Автокаде такое поведение тоже замечено, но в других случаях. Это очень тяжело отловить. Обязательно тестируйте Release. VS почему-то не позволяет отключать оптимизацию фрагмента кода C# (в отличии от C++). Решение - используйте атрибут [MethodImpl(MethodImplOptions.NoOptimization)] у подозреваемых методов.

    • Палитры Bricscad.Windows.PaletteSet совершенно не такие как стандартные палитры БриксКАД и никак не могут взаимодействовать со стандартными и друг с другом: ни накладываться вместе в виде закладок, ни складываться одна под другой. В версии V21 добавлены свои палитры Bricscad.Windows.Panel (не проверено). Впрочем все остальные возможности управления внешним видом БриксКАД из плагина и вовсе отсутствуют. И не ищите способов создать нативные панельки - интерфейс BricsCAD написан на линуксовых библиотеках и из C# вам туда вход закрыт.

    • А с V20 Брикскад вообще разучился загружать палитры при запуске - теперь он пытается вызвать команду палитры до того как стартует вся система загрузки плагинов. Решение - отключить все стартовые окна (переменная Startup = 0)

    • Отсутствую bundle-пакеты. Придется объяснять пользователям как запустить плагин через AppLoad. И загружать меню приходится программно, через костыли на LISP

    • AutoCAD сбрасывает выбор объектов при переключениях между окнами чертежей. BricsCAD запоминает выбор и восстанавливает его после переключений в другой чертеж. Это следует учитывать при отображении свойств объектов - надо показать свойства сразу по DocumentManager.DocumentActivated

Таблица сюрпризов BricsCAD.Net

С начала кратко, где собака порылась. Как лечить - см далее.

Методы решения проблем

Открытие файлов Автокада с видами ModelDoc

Исправлено V19.2.03

Тема не косается .Net API. Просто чтоб запомнить способ решения.

Вызывать каждый раз команду _recover, когда надо просто открыть чертеж - ужасно не удобно. Но все версии БриксКАД до 19.2 требуют _recover для Автокадовских чертежей с видами _viewbase (ModelDoc). Чтоб запускать БриксКАД из проводника и других программ, надо привязать к запуску DWG файлов следующий скриптовый файлик:

// Update paths for BricsCADRecover.js, BricsCADRecover.scr and bricscad.exe

// Registry command

// HKEY_CLASSES_ROOT\Bricscad.load.dwg\shell\open\command

// wscript.exe "C:\BricsCADRecover.js" "%1"

var fso = new ActiveXObject("Scripting.FileSystemObject");

var wsh = WScript.CreateObject("WScript.Shell");

var wsa = WScript.Arguments;

var dwg = wsa(0);

// WScript.Echo(dwg);

var scr = "C:\\TEMP\\BricsCADRecover.scr"; // Write access required.

var txtStr;

if (fso.FileExists(scr))

txtStr = fso.OpenTextFile(scr, 2);

else

txtStr = fso.CreateTextFile(scr);

txtStr.Write("_recover" + "\n" + "\"" + dwg + "\"");

txtStr.Close();

wsh.Run("\"c:\\Program Files\\Bricsys\\BricsCAD V19 en_US\\bricscad.exe\" //B " + "\"" + scr + "\"")

WScript.Quit();


Требуется изуродовать раздел using каждого файла

Просто шпаргалка, лишнее удаляю по месту:

using System;

#if BRICS

using Bricscad.ApplicationServices;

using Bricscad.EditorInput;

using Teigha.BoundaryRepresentation;

using Teigha.DatabaseServices;

using Teigha.Geometry;

using Teigha.Runtime;

using AcadApp = Bricscad.ApplicationServices.Application;

using Br = Teigha.BoundaryRepresentation;

using Db = Teigha.DatabaseServices;

using Rt = Teigha.Runtime;

#else

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Geometry;

using Autodesk.AutoCAD.BoundaryRepresentation;

using Autodesk.AutoCAD.Runtime;

using AcadApp = Autodesk.AutoCAD.ApplicationServices.Application;

using Br = Autodesk.AutoCAD.BoundaryRepresentation;

using Db = Autodesk.AutoCAD.DatabaseServices;

using Rt = Autodesk.AutoCAD.Runtime;

#endif


Document.LockDocument

Нельзя вызывать, когда документ уже заблокирован. В Автокаде можно вызывать бездумно.

Решение:

/// <summary>

/// Проверить наличие блокировки и если ее нет, то заблокировать. Возвратит null или блокировку (в BricsCAD нельзя блокировать повторно)

/// </summary>

public static DocumentLock LockIfNone(this Document doc)

{

if (doc==null || (doc.LockMode(true) != DocumentLockMode.None && doc.LockMode(true) != DocumentLockMode.NotLocked))

return null;

return doc.LockDocument();

}


TransactionManager – db и doc – отдельные

Исправлено в V19.2.07

В Автокаде можно получить текущую транзакцию из TopTransaction не зависимо от того какой TransactionManager ее создал. В BricsCAD TopTransaction не знает о транзакциях из другого TransactionManager и может вернуть null.

Решение: всегда использовать только db.TransactionManager

Примечание: с определенного момента я перестал обращаться к TopTransaction, а передаю транзакцию как параметр во все методы, где нет смысла открывать новую. Плагины стали на порядок стабильней работать. Все-таки TopTransaction опасная штука т.к. неизвестно, что это за транзакция, вовсе нет гарантии, что это именно та транзакции о которой я думаю.

Entity.Database != null vs Entity.ObjectId.IsNull

В BricsCAD Database может быть не null, даже если объект не сохранен в БД. Замечено у клонов объектов. И объектов выдаваемых методом Explode.

Решение: Для проверки сохранения объекта требуется заменить проверку Database == null на ObjectId.IsNull или IsNewObject

PromptEntityResult.StringResult

Жестокий сюрприз из-за которого пришлось перелопатить кучу кода. per.StringResult добавляет “_” ко всем Keyword и использует верхний регистр.

Решение:

peo.Keywords.Add(myKeyword);

PromptEntityResult per = ed.GetEntity(peo);

if (per.Status == PromptStatus.Keyword)

{

string com = per.StringResult.ToUpper();

if (com.StartsWith("_")) com = com.Substring(1);

if (com == myKeyword.ToUpper())


DisposableWrapper.IsDisposed

Странно. Свойство не видит компилятор и вызывает ошибку компиляции, хотя его видно в отладчике.


Document.CloseAndDiscard

Исправлено V19

Метода нет.

Решение: Надо вызывать команду закрытия через командную строку.


DocumentCollection.Open

Исправлено V19

В AutoCAD этот метод почему-то реализован как хелпер.

Autodesk.AutoCAD.ApplicationServices.DocumentCollectionExtension

public static Document Open(this DocumentCollection docCol, string fileName, bool forReadOnly, string password);

В BricsCAD отсутствует весь хелпер DocumentCollectionExtension и вы не сможете вызвать методы Add, CloseAll и Open

Решение:

Народ на форуме предлагает использовать COM и Invoke. К сожалению возвращает всегда null. Похоже у COM-функции не тот тип результата, не Document

public static object Get(this object obj, string propName, params object[] parameter) { return obj.GetType().InvokeMember(propName, BindingFlags.GetProperty, null, obj, parameter); }public static object Invoke(this object obj, string methName, params object[] parameter) { return obj.GetType().InvokeMember(methName, BindingFlags.OptionalParamBinding | BindingFlags.InvokeMethod, null, obj, parameter); }public static Document Open(this DocumentCollection dc, string dwgPath) // вообще-то параметр dc не нужен, добавлен чисто для совместимости { var documents = Application.AcadApplication.Get("Documents", null); if (documents != null) { var parameters = new List<object> {dwgPath, Type.Missing, Type.Missing}; return documents.Invoke("Open", parameters.ToArray()) as Document; } else return null; }


Curve3d.IsOn

cur.IsOn(point) и cur.IsOn(point, tolerance) BricsCAD может выдает false, даже если эта точка – начало дуги: point.IsEqual(cur.StartPoint, tolerance) выдает true. По крайней мере такое встречается у ExternalCurve3d, которые обычно возвращает свойство Br.Edge.Curve.

Решение: Для сравнения с конечными точками я использую IsEqual. Для других точек на ExternalCurve3d сначала получаю NativeCurve - у нее уже рабочий IsOn

// поиск точек пересечения Edge и Line3d

foreach (Br.Edge edge in brp.Edges)

{

using (ExternalCurve3d ext = edge.Curve as ExternalCurve3d) // у этой хрени не работет IsOn - всегда false

if (ext != null)

using (Curve3d cur = ext.NativeCurve)

if (cur != null)

{

PointOnCurve3d[] points = line.GetClosestPointTo(cur, tol);

foreach (PointOnCurve3d poc in points)

if (cur.IsOn(poc.Point, tol) && line.IsOn(poc.Point, tol))

// нашли точку poc.Point

....

}

}


CircularArc2d.IntersectWith(LineSegment2d)

Выдает 2 одинаковые точки вместо одной

Решение: Проверяйте результат на одинаковые точки. Можно использовать == без учета погрешности, т.к. точки абсолютно идентичны

Database.CurrentSpaceId

Property Database.CurrentSpaceId return PaperSpace id if user work inside viewport. It must return ModelSpace id

Решение: Не используйте CurrentSpaceId если вам надо отличить в модели или на бумаге работает сейчас пользователь. Вместо этого проверяйте CVPORT

public static bool PaperSpace

{

get { return (Convert.ToInt16(AcadApp.GetSystemVariable("CVPORT")) == 1); }

}


AssocArray

В BricsCAD отсутствует класс db. AssocArray и все связанные с ним. Решения нет.

BlockReference.DynamicBlockReferencePropertyCollection

BlockReference.DynamicBlockReferencePropertyCollection is null if block not contain parameters. It must return empty collection and not throw null-exception in foreach .

Решение: Вводите лишнюю проверку на null перед foreach.


Region.GetGripPoints

Может вернуть пустую коллекцию.

Решение: Если вам нужно несколько точек на регионе, то не используйте GetGripPoints, а возьмите, например, вертексы из Brep или взорвите регион рекурсивно, пока не найдете кривые и возьмите их концы.


ObjectContextCollection.CurrentContext

ПРОБЛЕМА НЕ ПОДТВЕРДИЛАСЬ В ДРУГОМ ПРОЕКТЕ!

не работает occ.CurrentContext. первый раз выдает нормальный AnnotationScale, а потом какой-то другой, не текущий.

Решение:

/// <summary>

/// Приписать аннотативному объекту текущий AnnotationScale

/// </summary>

public void AddCurrentAnnotationScale(Entity obj)

{

if (obj==null || obj.Annotative != AnnotativeStates.True) return;

ObjectContextManager ocm = m_db.ObjectContextManager;

ObjectContextCollection occ = ocm.GetContextCollection("ACDB_ANNOTATIONSCALES");

#if BRICS // не работает occ.CurrentContext. первый раз выдает нормальный AnnotationScale, а потом другой не текущий

double scale = (double)Application.GetSystemVariable("CANNOSCALEVALUE"); // сработает только в текущем документе

foreach(ObjectContext con in occ)

{

if (con is AnnotationScale an && an.Scale == scale)

obj.AddContext(an);

}

#else

obj.AddContext(occ.CurrentContext);

#endif

}


Brep.BoundBlock, Brep.GetVolume и Brep.GetMassProperties

Методы отсутствуют.

Решение: Придется использовать solid.MassProperties


Solid.MassProperties

Дает большую погрешность на сплайновых солидах (уже в 3ем знаке). Точный результат в BricsCAD дает Entity.GeometricExtents.

Решение:

#if BRICS

Extents3d ext = solid.GeometricExtents; // solid.MassProperties.Extents врет в BricsCAD на сплайновых солидах

minExt = ext.MinPoint;

maxExt = ext.MaxPoint;

Solid3dMassProperties mp = solid.MassProperties;

v = mp.Volume;

centroid = mp.Centroid;

#else

if (AcadApp.Version.Major <= 20) // в автокаде 2015 и ранее solid.GeometricExtents и brp.BoundBlock одинаково сильно врут на сплайнах. solid.MassProperties.Extents иногда срабатывает, хотя тормозит безбожно

{

Solid3dMassProperties mp = solid.MassProperties;

minExt = mp.Extents.MinPoint;

maxExt = mp.Extents.MaxPoint;

v = mp.Volume;

centroid = mp.Centroid;

}

else // AutoCAD 2016 и старше

{

try

{

using (Brep brp = new Brep(solid))

using (BoundBlock3d bound = brp.BoundBlock)

{

minExt = bound.GetMinimumPoint();

maxExt = bound.GetMaximumPoint();

v = brp.GetVolume(box * 0.1); // очень грубая (1 знак) оценка. но быстрая. для нормальных солидов даст точное значение, для сплайновых - угадывает только 1 десятичный разряд)

if (!IsBox)

{

double tol = v * 0.00000001; // запросим 8 десятичных знаков

MassProperties mp = brp.GetMassProperties(1, tol);

v = mp.Volume; // точная оценка объема

centroid = mp.Centroid;

}

}

}

catch { return false; } // сбой при получении Brep - возвращаем пустую метрику

}

#endif


Brep ==

Объекты Brep нельзя сравнивать через ==, т.к. каждый раз возвращаются разные указатели на объект. В результате нельзя, например, найти известный Edge в петле или соседние грани солида у заданной грани.

Решение: заменяем на Equals


Face.GetArea

Нет самого важного метода грани - получения площади.

Решение: Для солидов, сохраненных в БД, площадь граней можно получить через Db.Surface или Region. Для прочих объектов никак не получится вычислить площадь граней. В некоторых случаях можно сравнивать грани используя длину периметра и диагоналей вместо сравнения площадей.

/// <summary>

/// Получить Entity, соответствующий заданному элементу BRep.

/// В базе данных должна быть открытая транзакция.

/// BRep должен быть получен по FullSubentityPath объекта.

/// Объект должен быть сохранен в чертеже.

/// </summary>

public static Entity GetEntity(this BrepEntity be)

{

if (be == null) return null;

FullSubentityPath path;

try { path = be.SubentityPath; } catch { path = FullSubentityPath.Null; }

if (path == FullSubentityPath.Null) return null;

ObjectId solidId = path.GetObjectIds()[0];

Transaction tr = solidId.Database.TransactionManager.TopTransaction;

if (tr == null) return null;

using (Solid3d solid = tr.GetObject(solidId, OpenMode.ForRead) as Solid3d)

if (solid == null) return null;

else return solid.GetSubentity(path);

}

/// <summary>

/// Работает только для объектов, сохраненных в БД. В базе данных должна быть открытая транзакция.

/// </summary>

public static double GetArea(this Br.Face face)

{

if (face == null || face.IsNull) return 0;

using(Entity ent = face.GetEntity())

{

if (ent == null) return 0;

if (ent is Region reg) return reg.Area;

else if (ent is Db.Surface sur) return sur.GetArea();

}

return 0;

}


Edge.GetPerimeterLength

Метод отсуствует.

Решение:

public static double GetPerimeterLength(this Edge edge)

{

if (edge == null || edge.IsNull) return 0;

using (Curve3d cur = edge.Curve)

{

if (cur == null || cur.IsDisposed || !(cur is ExternalCurve3d)) return 0;

using (Interval interval = cur.GetInterval())

return cur.GetLength(interval.LowerBound, interval.UpperBound, STol.EqPoint); // Это моя настройка точности, общая для всех лагинов

}

}


BoundaryLoop.LoopType

Важнейшее свойство петли отсутствует. Полноценной замены нет.

Решение: Для получения наружного контура грани, можно найти самую длинную петлю. Это не сработает на замкнутых поверхностях типа цилиндров.

public static double GetLength(this BoundaryLoop loop)

{

if (loop == null || loop.IsNull)

return 0;

double ret = 0;

try

{

foreach (Edge edge in loop.Edges)

ret += edge.GetLength();

}

catch (Br.Exception) // отсутствие ребер (например, вершина конуса) вызывает Br.Exeption

{

return 0;

}

return ret;

}

/// <summary>

/// Получить наружный контур грани. Для Автокада - первый попавшийся внешний контур (хотя у цилиндра их 2). Для Брикс - самый длинный

/// </summary>

public static BoundaryLoop GetMaxLoop(this Br.Face face)

{

#if BRICS

double maxLen = 0;

BoundaryLoop ret = null;

foreach (BoundaryLoop loop in face.Loops)

{

double len = loop.GetLength();

if (len > maxLen)

{

maxLen = len; ret = loop;

}

}

return ret;

#else

try

{

foreach (BoundaryLoop loop in face.Loops)

if (loop.LoopType == LoopType.LoopExterior) return loop;

return null;

}

catch (Br.Exception) // отсутствие петель (сфера, тор) вызывает BrExeption

{

return null;

}

#endif

}


Brep.GetPointContainment

Brics возвращает не Edge а пустой новый объект BrepEntity.

Решение:

static bool OnEdge(Brep brp, Point3d pt)

{

#if BRICS

try

{

foreach (Edge edg in brp.Edges)

using (brp.GetPointContainment(pt, out PointContainment pc))

if (pc == PointContainment.OnBoundary) return true;

}

catch { return false; }

#else

using (BrepEntity be = brp.GetPointContainment(pt, out PointContainment pc)) // Brics возвращает не Edge а пустой новый объект BrepEntity

if (pc == PointContainment.OnBoundary)

return be is Edge;

#endif

return false;

}


Brep.GetLineContainment

Метод возвращает пустые точки, есть линия пересекла вершину или ребро солида. Так же пустые точки возвращаются при пересечении грани региона. Если hit имеет точку 0,0,0, а EntityHit == null - значит надо искать пересечения в ручную. Они точно есть.

Решение:

/// <summary>

/// Замена нерабочего GetLineContainment для БриксКАД

/// </summary>

public static Dictionary<Point3d, BrepEntity> GetHits(this Brep brp, Line3d line)

{

Dictionary<Point3d, BrepEntity> ret = new Dictionary<Point3d, BrepEntity>();

Hit[] hits = brp.GetLineContainment(line, brp.Faces.CountZ());

if (hits == null || hits.Length == 0) return ret;

foreach (Hit hit in hits)

{

#if BRICS

if (hit.Point == new Point3d() && hit.EntityHit == null) // Брикс сглючил. пересечение с вертексом или ребром солида или гранью региона

{

int count = ret.Count;

Tolerance tol = STol.UnitVector;

if (brp.Vertices.CountZ() > 0)

foreach (Br.Vertex ver in brp.Vertices)

if (line.IsOn(ver.Point, tol) && !ret.ContainsKey(ver.Point))

ret.Add(ver.Point, ver);

if (brp.Edges.CountZ() > 0)

foreach (Br.Edge edge in brp.Edges)

{

using (ExternalCurve3d ext = edge.Curve as ExternalCurve3d) // у этой хрени не работет IsOn - всегда false

if (ext != null)

using (Curve3d cur = ext.NativeCurve)

if (cur != null)

{

PointOnCurve3d[] points = line.GetClosestPointTo(cur, tol);

foreach (PointOnCurve3d poc in points)

if (!ret.ContainsKey(poc.Point) && cur.IsOn(poc.Point, tol) && line.IsOn(poc.Point, tol))

ret.Add(poc.Point, edge);

}

}

if (count == ret.Count && brp.Faces.CountZ() > 0)

foreach (Br.Face face in brp.Faces)

using (ExternalBoundedSurface ext = face.Surface as ExternalBoundedSurface)

if (ext != null)

{

if (ext.IsPlane && ext.BaseSurface is Plane pl)

{

Point3d[] points = line.IntersectWith(pl);

if (points!=null)

foreach (Point3d point in points)

if (ext.IsOn(point, tol) && line.IsOn(point, tol) && !ret.ContainsKey(point))

{ ret.Add(point, face); break; }

}

}

}

else

if (!ret.ContainsKey(hit.Point))

#endif

ret.Add(hit.Point, hit.EntityHit);

}

return ret;

}


BoundaryRepresentation.Hit.EntityHit.SubentityPath

Точки пересечения линии и солида, которые возвращает brp.GetLineContainment, содержат больной EntityHit, по которому нельзя понять, что за SubEntity попался на линии. Обращение к SubentityPath вызывает исключение eUnrecoverableErrors.

Решение: приходится перебирать исходный Brep в поисках этого элемента. С учетом того, что сравнивать объекты Brep напрямую оператором == нельзя, получаем весьма прожорливый костыль

using (Brep brp = sol.GetBrep())

{

Hit[] hits = brp.GetLineContainment(view3d, 1);

if (hits != null && hits.Length > 0)

foreach (Hit hit in hits)

{

FullSubentityPath path = GetPath(hit.EntityHit, brp));

....

static FullSubentityPath GetPath(BrepEntity ent, Brep brp)

{

#if BRICS

// ent.SubentityPath не рабоает

if (ent is Br.Face)

foreach (Br.Face face in brp.Faces) // нельзя брать Brep из face.Brep - даст ту же самую ошибку eUnrecoverableErrors

if (face.IsEqual(ent as Br.Face))

return face.SubentityPath;

if (ent is Br.Edge)

foreach (Br.Edge edge in brp.Edges)

if (edge.IsEqual(ent as Br.Edge))

return edge.SubentityPath;

if (ent is Br.Vertex)

foreach (Br.Vertex ver in brp.Vertices)

if (ver.IsEqual(ent as Br.Vertex))

return ver.SubentityPath;

return FullSubentityPath.Null;

#else

return ent.SubentityPath;

#endif

}


FullSubentityPath.IsNull

Отсутствует свойство.

Решение: сравнивать с FullSubentityPath.Null

Системная переменная SUBOBJSELECTIONMODE

Для выбора граней/ребер солида в Автокаде есть SUBOBJSELECTIONMODE, а в BricsCAD аналогичную работу делает SELECTIONMODES. Но значения переменных разные.

Для выбора сегментов полилиний в Автокаде ставим SUBOBJSELECTIONMODE = 2 (Edge) и порядок. В БриксКАД нет никакого способа запросить у пользователя сегменты полилиний. PromptSelectionOptions.ForceSubSelections тоже нерабочий.

Решение для солидов:

#if BRICS

[Flags]

public enum SubSelectFilterEnum { NoFilter = 0, Edge = 1, Face = 2, Contours = 4 }

/// <summary>

/// SELECTIONMODES

/// </summary>

public static SubSelectFilterEnum SubSelect

{

get

{

return (SubSelectFilterEnum)(Int16)AcadApp.GetSystemVariable("SELECTIONMODES");

}

set

{

AcadApp.SetSystemVariable("SELECTIONMODES", (Int16)value);

}

}

#else

public enum SubSelectFilterEnum { NoFilter = 0, Vertex = 1, Edge = 2, Face = 3, History = 4, View = 5 }

/// <summary>

/// SUBOBJSELECTIONMODE

/// </summary>

public static SubSelectFilterEnum SubSelect

{

get

{

return (SubSelectFilterEnum)(Int16)AcadApp.GetSystemVariable("SUBOBJSELECTIONMODE");

}

set

{

AcadApp.SetSystemVariable("SUBOBJSELECTIONMODE", (Int16)value);

}

}

#endif


Системная переменная ANNOMONITOR

Я отключал этот мусор в Автокаде во многих своих командах. В BricsCAD нет ни мусора, ни переменной.


Utilities.GetReservedString

#if BRICS

if (trans.IsByLayer) vs = "ByLayer";

else if (trans.IsByBlock) vs = "ByBlock";

#else

if (trans.IsByLayer) vs = Utilities.GetReservedString(ReservedStringEnumType.ByLayer, true);

else if (trans.IsByBlock) vs = Utilities.GetReservedString(ReservedStringEnumType.ByBlock, true);

#endif

Так же можно попытаться найти внешнюю функцию

const ACHAR* acdbGetReservedString(AcDb::reservedStringEnumType,bool = true)


AcadApp.DisplayingOptionDialog

Нет никакой возможности добавить свои настройки к диалогу настройки программы. Надо писать свою команду и свой диалог.


AcadApp.Preferences

Нет возможности получить настройки программно.

Решение: Брикс хранит в системных переменных все мыслимые настройки. Например для получения шаблона в Автокаде используем свой класс AcadPrefManager, а в БриксКАД переменную BASEFILE:

public static string QNewTemplate

{

get

{

try

{

#if BRICS

return (string)AcadApp.GetSystemVariable("BASEFILE");

#else

AcadPrefManager pref = new AcadPrefManager(AcadApp.Preferences);

return pref.QNewTemplateFile.ToUpper();

#endif

}

catch // может и не сработать считывание настроек - игнорим

{

return "";

}

}

}

public AcadPrefManager(object AcadPrefernces)

{

m_obj = AcadPrefernces;

}

object[] m_emptyArgs;

private object[] EmptyArgs

{

get

{

if (m_emptyArgs == null)

m_emptyArgs = new object[0];

return m_emptyArgs;

}

}

public string QNewTemplateFile

{

get

{

object files = m_obj.GetType().InvokeMember("Files", BindingFlags.GetProperty, null, m_obj, EmptyArgs);

string templateFile = (String)files.GetType().InvokeMember("QNewTemplateFile", BindingFlags.GetProperty, null, files, EmptyArgs);

return templateFile;

}

}


Customization и AcCui.dll

Отсутствуют Autodesk.AutoCAD.Customization и нет никакого аналога AcCui.dll

Кроме того отсутствую bundle-пакеты и загружать меню приходится программно. Вот так

#if BRICS

/// <summary>

/// загрузка CUIX если его еще нет в загруженных. не проверяет обновление файла

/// </summary>

public static void LoadMenu(string path, string name)

{

Document doc = Application.DocumentManager.MdiActiveDocument;

if (doc == null) return;

string file = path + "\\" + name + ".cuix";

if (!File.Exists(file))

{

Cns.Info(str[0, lng] + file);

return;

}

int oldFiledia = System.Convert.ToInt32(Application.GetSystemVariable("FILEDIA"));

Application.SetSystemVariable("FILEDIA", 0);

try

{

file = file.Replace("\\", "\\\\");

doc.SendStringToExecute(

"(if (eq (menugroup \"" + name + "\") nil) (command \"_CUILOAD\" \"" + file + "\")) " // костыль на LISP находит меню name или если его нет, то вызывает _CUILOAD

, false, true, true);

}

finally

{

Application.SetSystemVariable("FILEDIA", oldFiledia);

}

}

#else

/// <summary>

/// загрузка CUIX если его еще нет в загруженных. проверяет обновление файла

/// </summary>

public static void LoadMenu(string path, string name)

{

try

{

string cuix = name + ".cuix";

string file = Path.Combine(path, cuix);

if (!File.Exists(file))

{

Cns.Warning(str[0,lng] + file);

return;

}

string mainCui = Application.GetSystemVariable("MENUNAME") + ".cuix";

CustomizationSection cs = new CustomizationSection(mainCui);

if (cs == null) return;

PartialCuiFileCollection pcfc = cs.PartialCuiFiles;

if (pcfc.Contains(cuix)) //|| pcfc.Contains(path)

{ //уже загружен

FileInfo inf = new FileInfo(file);

FileInfo infMain = new FileInfo(mainCui);

if (inf.LastWriteTime > infMain.LastWriteTime)

{

Cns.Info(str[1,lng]+ name);

cs.RemovePartialMenu(cuix, name);

if (cs.IsModified == true) { cs.Save(); }

}

else

return;

}

if (cs.AddPartialMenu(file))

{

if (cs.IsModified == true)

{

cs.Save();

Application.ReloadAllMenus();

}

}

else

Cns.Err(str[2, lng] + name);

}

catch (System.Exception e)

{

Cns.Err(e, str[2,lng] + name);

}

}

#endif


Bricscad.Application

Отсутствуют все объекты для настройки интерфейса:

AcadApplication

DocumentWindowCollection

UserConfigurationManager

NonInPlaceMainWindow

StatusBar

IsInCustomizationMode

Preferences

MenuBar

MenuGroups

InfoCenter

UIBindings

Application.Version

In AutoCAD.Net property Application.Version is System.Version and I can get Application.Version.Major

In BricsCAD 17 property Version is string.

В BricsCAD 18 возвращает номер совместимой версии Автокада, а не номер версии Брикс.

Решение:

#if BRICS

/// <summary>

/// Настоящая версия BricsCAD, а не номер совместимой версии Автокада

/// </summary>

public static int Ver

{

get

{

try

{

string v = (string)AcadApp.GetSystemVariable("_VERNUM");

if (int.TryParse(v.Substring(0, 2), out int num))

return num;

}

catch { }

return 0;

}

}

#endif


MachineRegistryProductRootKey

All modern versions of AutoCAD.Net use property HostApplicationServices.Current.MachineRegistryProductRootKey.

Instead, BricsCAD has an obsolete property HostApplicationServices.Current.RegistryProductRootKey.

Решение:

public static string ProdKey

{

get

{

#if BRICS

return HostApplicationServices.Current.RegistryProductRootKey;

#else

object s = HostApplicationServices.Current;

PropertyInfo prop = s.GetType().GetProperty(AcadApp.Version.Major <= 18 ? "RegistryProductRootKey" : "MachineRegistryProductRootKey");

return (string)prop.GetValue(s, null);

#endif

}

}


ObjectContexts.AddContext

Autodesk.AutoCAD.Internal.ObjectContexts.AddContext Статический метод не работает, нужно вызывать метод экземпляра DBObject.AddConext

public void AddCurrentAnnotationScale(DBObject obj)

{

ObjectContextManager ocm = m_db.ObjectContextManager;

ObjectContextCollection occ = ocm.GetContextCollection("ACDB_ANNOTATIONSCALES");

//ObjectContexts.AddContext(obj, occ.CurrentContext); Это не сработает в Брикс

obj.AddContext(occ.CurrentContext); // а это работает везде

}


OpenFileDialogFlags

Не думаю что флаги из OpenFileDialogFlags кто-то использовал на самом деле. У меня были случайно скопированные откуда-то. Я их просто удалил. Без последствий.

Вот сравнение Autodesk.AutoCAD.Windows vs Bricscad.Windows

[Flags]

public enum OpenFileDialogFlags

{

AllowAnyExtension = 4,

SearchPath = 8,

DefaultIsFolder = 16,

DoNotTransferRemoteFiles = 64,

NoUrls = 128,

ForceDefaultFolder = 256,

NoFtpSites = 512,

NoShellExtensions = 1024,

AllowFoldersOnly = 2048,

AllowMultiple = 4096

}

public enum OpenFileDialogFlags

{

AllowAnyExtension = 4,

DefaultIsFolder = 16,

AllowMultiple = 4096

}


ProductLcid

The property SystemObjects.DynamicLinker.ProductLcid always returns zero.

Решение: Использовать системную переменную LOCALE


System variable LOCALE

in AutoCAD documentation:

"This code appears as a three-letter abbreviation returned by the Windows GetLocaleInfo function using the LOCALE_SABBREVLANGNAME constant."

In my system it contain "RUS" in all AutoCAD localization.

In BricsCAD it variable return LCID-string of BricsCAD, not a Windows. And not 3-litter. For Russian version it return "ru-RU" and for English "en-US". This is a more useful feature than AutoCAD. I like it. But this should be documented. Or it must be a variable with a different name .


Диалоговые окна на языке Windows

При вызове Application.ShowModalDialog(dlg) все локализованные диалоговые окна в Автокаде открываются на языке локали Автокада, а в BricsCAD открываются на языке Windows. Я смирился.


TransientManager

В БриксКАД не работает вообще. Никакие объекты никогда не отображаются. Не понятен смысл его существования в API. В Автокаде регулярно создает фаталы, поэтому использую только для отладки.

Решение: сохранять в чертеже все отладочные объекты. Например, на отдельном слое.


ProgressMeter

В версии V19 перестал работать индикатор прогресса. Причем это сделано умышлено:

Owen Wengerd

15-12-2018 18:09 UTC

Prior to V19, we had used some hacks to make a default ProgressMeter work correctly. The hacks were removed in order to properly separate implementation layers.

Однако появился путь в обход:

#if BRICS

pm = HostApplicationServices.Current.NewProgressMeter();

#else

pm = new ProgressMeter();

#endif


Region.IntersectWith Пересечение региона с линией

В БриксКАД:

- выдает 1 точку входа линии, а не 2 (вход/выход), когда линия совпадает с ребром региона.

- выдает 0 точек когда линия в плоскости региона или eGeneralModelingFailure.

А в Автокад в обоих случаях выдает 2 точки – вход линии в регион и выход.

Решение: рекурсивно взорвать регион и искать пересечения с его ребрами.

/// <summary>

/// Пересечения линии с периметром региона

/// </summary>

/// <param name="reg"></param>

/// <param name="extend">расширять линию до бесконечности</param>

public static Point3dCollection IntersectPerim(this Line line, Region reg, bool extend)

{

Point3dCollection ret = new Point3dCollection();

if (line == null || line.IsNull || reg == null || reg.IsNull) return ret;

DBObjectCollection col = new DBObjectCollection();

try

{

reg.Explode(col);

}

catch (Rt.Exception) { return ret; }

foreach (DBObject obj in col)

{

Point3dCollection points = new Point3dCollection();

if (obj is Region subreg) points = IntersectPerim(subreg, extend);

else

try

{

(obj as Entity).IntersectWith(line, extend ? Intersect.ExtendArgument : Intersect.OnBothOperands, points, IntPtr.Zero, IntPtr.Zero);

}

catch (Rt.Exception) { continue; }

foreach (Point3d p in points) ret.Add(p);

}

return ret;

}


Table.CopyFrom

Отсутствуют параметры для задания диапазонов копируемых ячеек.

Решение: писать свой велосипед. Я реализовал только нужные мне функции. На самом деле CopyFrom имеет очень много опций.

/// <summary>

/// Копировать все строки из source в конец таблицы target. Перевая строка source игнорируется, если там заголовки. Количество столбцов должно быть идентично

/// </summary>

public static void AddRows(this Table target, Table source)

{

if (target == null || target.IsErased || source == null || source.IsErased || source.Rows.Count == 0 || source.Columns.Count != target.Columns.Count) return;

int startRow = source.Rows[0].Style == "_HEADER" ? 1 : 0;

if (startRow == 1 && source.Rows.Count == 1) return;

int insertTo = target.Rows.Count;

#if BRICS // отсутствует CopyFrom с диапазоном ячеек

int insertCount = source.Rows.Count - startRow;

target.InsertRows(insertTo, target.Rows[target.Rows.Count - 1].Height, insertCount);

for (int row = startRow; row < source.Rows.Count; row++)

{

target.Rows[insertTo].Style = source.Rows[row].Style;

CellRange sourceRow = CellRange.Create(source, row, 0, row, source.Columns.Count - 1); // вся текущая строка

CellRange targetRow = CellRange.Create(target, insertTo, 0, insertTo, target.Columns.Count - 1);

targetRow.UnlockContent();

// объединение заголовков

try

{

if (sourceRow.IsMerged == true)

{

if (targetRow.IsMerged != true) target.MergeCells(targetRow);

}

else

{

if (targetRow.IsMerged != false) target.UnmergeCells(targetRow);

}

}

catch (RtException ex)

{ if (ex.ErrorStatus != ErrorStatus.IsWriteProtected) throw ex; }

for (int col = 0; col < target.Columns.Count; col++)

{

// запишем текст в ячейку

if (!target.FormatLocked(insertTo, col))

target.Cells[insertTo, col].DataType = source.Cells[row, col].DataType;

target.Cells[insertTo, col].TextString = ""; // необходимо сбрасывать, чтоб заменить одно Поле другим

target.Cells[insertTo, col].TextString = source.Cells[row, col].TextString;

// пропуск 2ой и далее объединенных ячеек

if (target.Cells[insertTo, col].IsMerged == true) // IsMerged == true только у первой из объединенных ячеек

{

CellRange cr = target.Cells[insertTo, col].GetMergeRange();

col += cr.RightColumn - cr.LeftColumn;

}

}

// сжатие высоты строк

if (target.Rows[insertTo].Height > target.Rows[insertTo].MinimumHeight)

target.Rows[insertTo].Height = target.Rows[insertTo].MinimumHeight;

insertTo++;

}

#else

CellRange sourceRange = CellRange.Create(source, startRow, 0, source.Rows.Count - 1, source.Columns.Count - 1);

target.InsertRows(insertTo, target.Rows[target.Rows.Count - 1].Height, 1); // добавим 1 - остальные добавятся сами

CellRange targetRange = CellRange.Create(target, insertTo, 0, insertTo, 0); // диапазон из 1 ячейки - расширится сам

target.CopyFrom(source, TableCopyOptions.ExpandOrContractTable | TableCopyOptions.TableCopyRowHeight, sourceRange, targetRange);

// копирование переносит стиль строк в стиль ячеек. вернем обратно

for (int i = insertTo; i < target.Rows.Count; i++)

target.Rows[i].Style = target.Cells[i, 0].Style;

#endif

}


CellRange.State

Присвоение значения свойству сбивает диапазон ячеек на рандомные значения.

Решение: сохранять диапазон при несении любых изменений. Например, для разблокировки ячеек:

public static void UnlockContent(this CellRange range)

{

if (range == null || range.IsNull) return;

CellRange save = CellRange.Create(range.ParentTable, range.TopRow, range.LeftColumn, range.BottomRow, range.RightColumn); // BricsCAD испахабит range

if (save.State == null)

save.State = CellStates.None;

else

save.State = save.State.Value & ~(CellStates.ContentLocked | CellStates.ContentModifiedAfterUpdate | CellStates.ContentReadOnly | CellStates.Linked);

}


MText.SetFromStyle

Вызывает eNotImplementedYet

Просто блокирую вызов. Авось присвоение стиля и так сработает. Не проверено.

Editor.GetEntity

Вызывает NullReferenceException при проверке типа объекта Tabel

Решение: не фильтруйте ввод по типу объектов, а отбросьте ненужные объекты потом

/// <summary>

/// Запросить пользователя выбрать таблицу. Без опций

/// </summary>

public static ObjectId SelectTable()

{

Document doc = AcadApp.DocumentManager.MdiActiveDocument;

if (doc == null) return ObjectId.Null;

Editor ed = doc.Editor;

ed.SetImpliedSelection(new ObjectId[0]);

PromptEntityOptions peo = new PromptEntityOptions(str[2, lng]);

peo.AllowNone = false;

peo.SetRejectMessage("\nNeed selected a Table!\n");

#if !BRICS

peo.AddAllowedClass(typeof(Table), false); // в Брикс вызывает NullReferenceException

#endif

peo.AllowObjectOnLockedLayer = false;

PromptEntityResult per = ed.GetEntity(peo);

if (per.Status != PromptStatus.OK) return ObjectId.Null;

if (!per.ObjectId.IsTable()) { Cns.Warning("\nNeed selected a Table!\n"); return ObjectId.Null; }

return per.ObjectId;

}


RXClass.GetClass(typeof(Table))

Возвращает null. Явная и глупая ошибка API.

Решение: Проверку на соответствие типа Таблице придется делать по строке имени типа

public static bool IsTable(this ObjectId id)

{

#if BRICS

return id.ObjectClass.Name == "AcDbTable";

#else

return id.ObjectClass == RXClass.GetClass(typeof(Table)); // в Брикс возвращает null

#endif

}


Ellipse.Spline

У db.Ellipse отсутствует преобразование в spline. Можно построить сплайн в ручную по точкам. Я не реализовывал т.к. нет проблем с проецированием эллипсов на плоскость (В Автокаде необходимо было преобразовывать в сплайн, иначе глючил)

Spline.ToPolyline

Отсутствует преобразование. Причем нет ни одной из перегрузок, которые есть в Автокаде. Аппроксимация невозможна. Можно построить линейную аппроксимацию по точкам.


BLOCKICON

в БриксКАД нет команды BLOCKICON и он вообще не умеет показывать иконки блоков. Иконок нет ни в одной команде работы с блоками.


Внешняя функция acedCommandS

Отсутствует функция acedCommandS, но есть функция acedCommand в файле brx18.dll. Как она работает – я не проверял.

#if AutoCAD2012

const String acOwner = "acad.exe";

#else

const String acOwner = "accore.dll";

#endif

[DllImport(acOwner, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl, EntryPoint = "acedCommandS")]

public static extern int acedCommand(int type1, string command, int type2, string blockName, int end);


Внешняя функция acedTrans

Переехала из accore.dll в brx18.dll

#if BRICS

[DllImport("brx17.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "acedTrans")]

public static extern Int32 AcedTrans17(Double[] point, IntPtr fromRb, IntPtr toRb, Int32 disp, Double[] result);

[DllImport("brx18.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "acedTrans")]

public static extern Int32 AcedTrans18(Double[] point, IntPtr fromRb, IntPtr toRb, Int32 disp, Double[] result);

public static Int32 AcedTrans(Double[] point, IntPtr fromRb, IntPtr toRb, Int32 disp, Double[] result)

{

switch (ver) // это не Application.Version, а первые символы _VERNUM

{

case 17: return AcedTrans17(point, fromRb, toRb, disp, result);

case 18: return AcedTrans18(point, fromRb, toRb, disp, result);

default: throw new NotImplementedException();

}

}

#else

#if AutoCAD2012

const String acOwner = "acad.exe";

#else

const String acOwner = "accore.dll";

#endif

[DllImport(acOwner, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl, EntryPoint = "acedCommandS")]

public static extern int acedCommand(int type1, string command, int type2, string blockName, int end);

const String acedTransName = "acedTrans";

[DllImport(acOwner, CallingConvention = CallingConvention.Cdecl, EntryPoint = "_" + acedTransName)] // до 2014(ver 19.1) x32 небыло _

static extern Int32 AcedTrans_x32(Double[] point, IntPtr fromRb, IntPtr toRb, Int32 disp, Double[] result);

[DllImport(acOwner, CallingConvention = CallingConvention.Cdecl, EntryPoint = acedTransName)]

static extern Int32 AcedTrans_x64(Double[] point, IntPtr fromRb, IntPtr toRb, Int32 disp, Double[] result);

public static Int32 AcedTrans(Double[] point, IntPtr fromRb, IntPtr toRb, Int32 disp, Double[] result)

{

if (IntPtr.Size == 4 && Application.Version > new Version(19,1))

return AcedTrans_x32(point, fromRb, toRb, disp, result);

else

return AcedTrans_x64(point, fromRb, toRb, disp, result);

}

#endif


Section.GenerateSectionGeometry

Одна из самых тяжелых проблем БриксКада. Нет никакого способа получить плоские слепки солидов.

The Section.GenerateSectionGeometry method does not work in the BricsCAD 17 and 18. The method produces the original solid at the output. It looks like the method ignores all the settings and always works in the SectionType.Section3d mode. I can explode the solid and project all the curves to a plane, but how to separate invisible lines?

https://forum.bricsys.com/discussion/33640/net-generatesectiongeometry-method-does-not-work?new=1

Я написал функцию проецирования ребер. Конечно она не различает видимость ребер и не делает силуэты. Увы. Решения нет.

public static List<Entity> ExplodeToCurves(this Entity ent)

{

List<Entity> ret = new List<Entity>();

if (ent == null || ent.IsErased)

return ret;

DBObjectCollection col = new DBObjectCollection();

try { ent.Explode(col); }

catch { return ret; }

if (col == null) return ret;

foreach(DBObject obj in col)

{

if (obj is Curve cur) ret.Add(cur);

else ret.AddRange((obj as Entity).ExplodeToCurves());

}

return ret;

}


Database.EvaluateFields

Метод отсутствует. Придется объяснять пользователям, как настроить обновление полей по UpdateField

Field

Еще одна фатальная проблема БриксКАД. Нет никакой возможности создавать поля в текстах в БриксКАД.

To create a field in the text, I used code like this:

MText mt = new MText();

mt.Contents = “%<\\AcObjProp Object(%<\\_ObjId 1438088896>%).Layer>%”;

This works great in AutoCAD. But in BricsCAD 18 this method does not work. In the drawing not a field appears, but just text disappear symbols “\A”. In my example it will be like this:

%<cObjProp Object(%<\_ObjId 1438088896>%).Layer>%

Such an expression is certainly not perceived by the program as a field.

I tried to assign a field like this:

MText mt = new MText();

if (text.Contains("%<\\"))

{

Field f = new Field(text);

f.Evaluate();

mt.SetField(f);

tr.AddNewlyCreatedDBObject(f, true);

}

else

mt.Contents = text;

And yet like this

MText mt = new MText();

if (text.Contains("%<\\"))

{

Field f = new Field(text);

f.EvaluationOption = FieldEvaluationOptions.Automatic;

f.Evaluate((int)(FieldEvaluationOptions.Automatic), db);

mt.SetField(f);

tr.AddNewlyCreatedDBObject(f, true);

}

else

mt.Contents = text;

In both cases, a non-working field is obtained. If the user tries to edit such a field, it disappears. If I use the debugger and check the status of the field after the evaluation, I always get InvalidContext.

Exactly the same problems occur when I insert field in a table.

Решения нет. Техподдержка Bricsys советует использовать LISP вызывая его из C# ( IAcadDocument.SendCommand(), or IAcadDocument.EvaluateLisp(), or Bricscad.EditorInput.Editor.WriteMessage(), or Bricscad.ApplicationServices.Document.SendStringToExecute() ) . Но для реализации этого костыля придется сохранять объекты, переоткрывать транзакции и блокировки документа. Со всеми вытекающими.

Viewport vs ViewBorder

По команде _ViewBase Автокад создает специальный объект ViewBorder и НЕВИДИМЫЕ вьюпорты. БриксКАД создает видимый viewport. Но работет он не стандартно, не отображает объекты из модели, а только из специального скрытого блока. Отличить этот вьюпорт от обычных поможет анализ xData

/// <summary>

/// Вьюпорт является не нормальным видом, а создан командой ViewBase. В Автокаде эти вьюпорты невидимые, а в БриксКад видимые. Отображают только специальный невидимый блок ViewRepBlockReference. Позиция vp.ViewCenter показывает на этот блок а не на реальные объекты модели.

/// </summary>

public static bool IsSynergy(this Viewport vp)

{

if (vp == null || vp.IsErased) return false;

using (ResultBuffer ret = vp.GetXDataForApplication("AcSynergy"))

return !(ret == null);

}


PaletteSet.Visible

Для видимой палитры Visible выдает false, когда палитра пристыкована к главному окну BricsCAD. Отловлен случай когда запрашивать видимость из события DocumentManager.DocumentToBeDeactivated - точно будет false. И не поддается отлову второй случай, но тоже вызов из обработчиков событий.

Решения нет. Вместо того чтоб проверять видимость палитры я отключаю всю обработку событий при скрытии панелей.