На мой взгляд, один из лучших способов научится чему-то это поделится знаниями с другими людьми.
В этот раз мне понадобилось понять, как создаются пользовательские объекты в NanoCAD с помощью MultiCAD.NET API. В блоге компании Нанософт есть статья от 2013 года, в которой объясняются базовые вопросы создания пользовательских примитивов. Но согласитесь было бы не интересно, просто воспроизвести эту статью, поэтому мы ее немного дополним.
В нашем случае мы создадим псевдотрехмерную дверь, которая к тому же будет уметь открываться и закрываться. А чтобы нашей маленькой дверке не было одиноко, мы создадим ей подружку — такую же стенку.
Под понятием «псевдо-3D» в данном случае я имею ввиду, что наши объекты не будут обладать свойствами модели твёрдого тела, то есть это будет просто набор связанных геометрических примитивов в трёхмерной системе координат. Может это не совсем корректный термин, но я пока лучше ничего не подобрал.
К сожалению, программистом при всем желании меня сейчас назвать нельзя, поэтому данная статья будет в стиле от новичка-новичкам и наверняка весь код, и приемы, которые вы в ней встретите можно будет улучшить.
Так или иначе если вы интересуетесь: проектированием, САПР, NanoCAD, разработкой под .NET и в частности на C#, а также овцами и Улицей Сезам, то возможно эта статья как раз для вас.
Вам тоже интересно причем тут овцы и Улица Сезам? Тогда милости прошу под кат.
Не буду нагнетать драматическую паузу, овцы – это некоторая метафора поясняющая бессмысленность, того чем мы сегодня займемся (картинка чуть позже).
Ну, а Улица Сезам, здесь просто потому, что я недавно про нее вспомнил и меня разбила жуткая ностальгия по куклам «Маппетам», так что они помогут нам выдержать единую стилистику повествования.
Наверное, глупо было так быстро раскрыть всю интригу? Но я надеюсь, что вы все же продолжите читать статью.
Содержание:
Часть I: С новым CADом! (Введение).
Часть II: Пишем код под NanoCAD 8.5
Часть III: Пробуем адаптировать код под бесплатный NanoCAD 5.1.
Часть IV: МультиКукиш (Заключение)
Начать хотелось бы с того, что на портале для разработчиков NanoCAD стала доступна стабильная версия свежего NanoCAD 8.5 SDK и в этот раз мы будем ориентироваться именно на нее.
В своей прошлой статье ориентированной на NanoCAD 8.1, я поделился своим мнением относительно платформы, мы разобрали процесс подготовки проекта к сборке и написали простенькую команду, поэтому если Вы её пропустили и совсем незнакомы с NanoCAD и разработкой с помощью MultiCAD .NET API, то можно начать со статьи «Лицо без шрама» или первые шаги в Multicad.NET API 7 (для Nanocad 8.1)
В этот раз я планирую поменьше «лить воды» и побольше уделить внимания технической стороне.
Единственное перед тем как перейти к разработке наших объектов скажу, что до начала подготовки этой статьи, я по сути пользовался только бесплатной версий NanoCAD (NC 5.1), которая была выпущена аж в 2013 году.
Причиной этому были две: она полностью бесплатна для любых целей, а вторая причина — у меня весьма слабенький комп, поэтому AutoCAD на нем подтормаживает и чертовски долго грузится.
Но поскольку перед тем как писать эту статью, мне надо был потренироваться «ручками» чертить объект, а также разобратся как работает трехмерный просмотр объекта, ну и самое главное 10000 раз перезапустить CAD в процессе отладки, то я успел чуть-чуть рассмотреть и NanoCAD 8.5.
Так вот на первый взгляд могу сказать следующее, чертить приятней чем в старой бесплатной версии, а грузится также быстро как старый NanoCAD 5.1, то есть NC 8.5 стартует в несколько раз быстрее чем его сверстник — AutoCAD 2017 (если кому любопытно пишите в комментарии, сделаю замер с секундомером). Остается только надеется, что однажды компания обновит бесплатную версию, перенеся в нее новые API и новые фишки в части функций «электронного кульмана».
Ну и последнее, как я понимаю в версиях NanoCAD доступных для разработчиков, включен модуль трехмерного твердотельного моделирования, но я не смог так сходу разобраться с API к нему, особенно для объектов, создаваемых пользователем. Может быть в другой раз мы его изучим. А пока мы будем довольствоваться «псевдо-3D» объектами.
В этом даже есть свое преимущество, наша с вами библиотека после небольшой адаптации запустится и в бесплатном NanoCAD 5.1, где твердотельного моделирования нет и в помине. Но об этом чуть позже.
Да, да, граф фон Знак всё верно сосчитал! Забегая вперед именно столько овец, стен и дверей мы получим в конце. Теперь у графа Знака будет новое задание — считать просмотры и голоса за статью. Я уже прям слышу это: «один, один просмотр, два – два просмотра, три…»
Как обычно, полный код классов и пример dwg фалов вы найдете на GitHub.
А сейчас мы начнем разбирать его по частям. Я не стал прилагать готовые сборки, думаю вы сможете собрать проект самостоятельно, в прошлой статье , я подробно с картинками рассказывал, как создать и настроить проект под MS Visual Studio 2015, для NanoCAD 8.1, так вот с того момента ничего сильно не поменялось.
Поэтому в этот раз я лишь кратко упомяну порядок действий для сборки под Nanocad 8.5:
У вас естественно название проекта и путь к нему могут отличаться.
Ну вот и все мы готовы к разработке, теперь у нас по нажатию F5 автоматически запускается NC 8.5 и сразу подгружается наша сборка, останется только вводить разработанные команды.
Еще раз оговорюсь, я не программист, поэтому скорей всего в коде будет куча огрехов: сбои при сохранении, перемещении или копировании объектов, да и просто неоптимальные решения. Если кто-то, не сильно усложняя код сможет эго довести до ума – «земной поклон».
Но так или иначе, на базе этого кода мы с вами сможем немного разобраться в том, как создавать свои объекты, а значит свою главную цель он выполняет.
Ну и безусловно надо сказать большое спасибо Александру Полховскому с форума разработчиков NanoCAD, он мне очень помог с переопределением функционала, связанного с перемещением и поворотом объекта (пригодилось для открытия/закрытия двери). Да и всем другим участникам форума тоже спасибо, напомню это на данный момент один из самых доступных источников информации по MultiCAD.NET API.
Начнем мы с вами со стенки, потому, что она попроще в исполнении.
Для начала добавим пространства имен.
Затем создадим класс пользовательского объекта.
Название класса возьмите какое хотите (можно оставить и мое), главное чтобы он наследовал от McCustomBase.
Все атрибуты класса – обязательны, я если честно параметры атрибута CustomEntity не до конца понимаю, поэтому тупо переделал по аналогии.
«8b0986c0-4163-42a4-b005-187111b499d7» — в моем примере это GUID, я видимо «прощелкал» тот момент, где в документации по .NET объяснялось как с ним работать. Могу сказать только одно, я для простоты брал его из файла настроек сборки, заменяя последнюю цифру для обеспечения уникальности. Если GUID у классов двери и стены будет полностью одинаковым, то начнутся чудеса: стены при копировании будут превращаться в двери, а двери после сохранения файла терять свой функционал, у себя я это вроде исправил надеюсь и у вас проблем не будет.
Определим поля класса.
Поля _pnt1 и _pnt12, это базовые точки по которым будет строится геометрия нашей стены (длина стены по сути), _h это высота стены по умолчанию (после создания объекта можно будет поправить).
Дальше создадим команду по вызову, которой будет обрисовываться наша дверь.
DrawWall в атрибуте CommandMethod, это имя команды, которое вы будете вводить в командную строку, чтобы вызвать объект, вы можете его сократить например на DWall, без потери функциональности.
Мы в классе реализующем нашу команду создаем новый экземпляр класса стена (если этого не сделать, то у меня все стенки начинают восприниматься как одна «убер» стенка). А метод PlaceObject мы определим чуть позже.
Определим процедуру отрисовки объекта.
Я не полностью понимаю этот кусок, но так или иначе в API есть класс GeometryBuilder, на основании, которого мы и будем дальше ваять нашу стенку.
Dc.Clear, по всей видимости очищает каждый раз всю ранее построенную для экземпляра класса геометрию.
Дальше проще.
Мы определяем четыре базовые точки на основании которых будет строится основание и верхушка стены, причем первая и вторая точка увязываются с полями класса, а значит именно ими мы потом и будем манипулировать. Наша стенка в длину будет строиться по расстоянию между точками _pnt1 и pnt2, а вот ширина стенки задана жестко её поправить нельзя (так сделано для простоты), но вы легко можете переопределить логику работы по аналогии.
dc.Color – похоже задает свойство «цвет по блоку» для объекта.
Вектор hvec это высота нашей стенки, которую мы будем прибавлять к основанию, чтобы построить верхушку.
Дальше чертим нижнюю и верхнюю стороны стенки.
Соединяем низ и верх ребрами.
Создаем контур из поллиний для штриховки и затем заливаем его штриховкой кирпич (названия штриховок можно посмотреть в самой программе). Причем у меня не получилось заштриховать через список полилиний пришлось повторять процедуру два раза. Думаю, я что-то не учел.
Мы с вами штрихуем только 2 поверхности стенки — самые длинные, если захотите можете самостоятельно заштриховать остальное.
Определим пользовательское свойство для объекта, поскольку на мой взгляд чертить удобней в двухмерном виде, то высоту стенки в момент черчения выставлять не удобно, можно было сделать установку высоты стенки как часть процедуры ее отрисовки, но мы пойдем более простым путем, просто добавим свойство, с помощью которого будем менять высоту уже после того как её начертим.
По атрибутам [DisplayName(«Height»)] – имя которое будет в окне свойств, [Description(«Height of wall»)], это описание, но я не понял, где оно отображается, [Category(«Wall options»)] – категория полей, как вы позже увидите на примере дверей, наши поля модно сгруппировать для удобства.
Ну а дальше идет обычно свойство, если вы когда-нибудь делали публичные свойства в Unity 3D, то механизм похож, можем иметь доступ к полям класса прямо из редактора (в нашем случае из САПР).
TryModify() – это обязательный метод, его надо вызывать перед каждым изменением свойств объекта, как я понял. Мы с ним еще пару раз встретимся.
Дальше переопределяем метод отвечающий за размещение объекта на чертеже (помните мы его раньше командой вызывали).
Этот код почти полностью позаимствован из примера от Нанософт о котором я упоминал в первой главе, я его не на 100% понимаю, но если вкратце мы вызываем команду для ввода первой точки стены (jig.GetPoint), затем помещаем её в чертеж (DbEntity.AddToCurrentDocument()), после чего исключаем объект из привязок, чтобы он нам не мешал вводить вторую точку (_pnt2).
Если все нормально, то объект размещается в чертеже, если нет (например не завершён ввод), то объект удаляется.
И последнее делаем ручки для изменения размера стенки.
Как я понимаю, код из примера который я упоминал выше, в части ручек устарел (для NC 8.X) и лучше ориентироваться на код из этого примера.
Ну и естественно я предупреждаю, вас что мой код далек от идеала поэтому адекватным, вменяемым правкам буду рад.
Теперь рассмотрим дверь. Начало — аналогичное.
Разве что добавилось поле, которое будет отвечать за то открыта или закрыта наша дверь, а также появлюсь два вектора _vecStraightDirection — отвечает за текущий поворот двери, _vecDirectionClosed — хранит данные о повороте двери в закрытом состоянии. Это нам все пригодится позже.
А вот непосредственно в геометрии двери, как и следовало ожидать есть маленькие изменения.
Во-первых, обратите внимание, что мы строим дверь по одной точке, то есть размер двери по ширине и высоте у нас жестко закреплен (ну чтобы она от стены отличалась). Также добавилась секция «// Drawing a Door Handle», там 2 линии которые обозначают условную ручку, ну и еще тип штриховки мы заменили на JIS_WOOD
А вот метод PlaceObject у нас упростился, за счет того, что не нужна вторая ручка.
Дальше идет полная новинка по отношению к классу стенки. За которую я в начале статьи поблагодарил Александра.
Ниже мы переопределим метод, который каким-то мистическим образом отвечает за создании матрицы трансформации (перемещения и поворота) нашей двери.
Я не полностью понимаю работу этого метода, но похоже при каждом перемещении он преобразует нашу дверь в зависимости от базовой точки и вектора поворота, это в конечном счете позволяет нам открывать или закрывать дверь на чертеже.
Дальше мы переопределяем событие которое похоже наступает при трансформации объекта.
Для начала скажу, что чтобы не застревать с разработкой, некоторые моменты я упрощал, особенно те решения, которые мне давались с трудом. Одно из таких упрощений — это ограничение на манипуляции с дверью.
Двигать, поворочать, копировать и как-либо еще изменять дверь можно только в закрытом состоянии (оно установлено по умолчанию).
Для того, чтобы оно так и работало мы создаем объект undo и отмечаем точку старта для фиксации изменений.
После чего если все нормально передаем точке _pnt1 и вектору ._vecStraightDirection их состояние после трансформации.
Затем идет проверка условия, если дверь была закрыта, то изменения применяются и дополнительно заносятся в вектор который хранит данные о положении закрытой двери.
Если дверь была открыта (или приоткрыта) мы выдаем сообщение об ошибке и отменяем все изменения.
Поле высоты двери – аналогично стене.
А вот следующее поле – новенькое
Именно оно у нас и отвечает за состояние двери, в окне свойств появляется выпадающий список со значениями: closed, middle, open (один в один, как определение перечисления вначале класса).
При выборе каждого из значений изменяется в конечном счете вектор отвечающий за поворот двери.
При закрытой он выставляется в заранее сохранённое состояние _vecDirectionClosed;
При полуоткрытом состоянии получается результирующий вектор, который поворачивает нашу дверь на угол примерно 30 градусов, чтобы было похоже на обозначение по ГОСТ.
При открытом состоянии мы просто берем перпендикуляр к нашему вектору закрытого состояния с отрицательным значением (чтобы дверь открывалась по умолчанию вниз).
Дефолтный случай думаю не нужен вовсе, но я оставил.
Ну и последнее это ручка для манипулирования.
За нее можно перетаскивать и все. Ручки у меня время от времени у обоих объектов скачут, куда попало, но отлаживать это у меня уже нет сил (я думал, что закончу статью быстрее, а убил уже три полных дня).
Итак, жмем F5 и с помощью команд DRAWWALL и DRAWDOOR вставляем наши двери и стены.
В результате получим то что на рисунке. На нём я демонстрирую вам работу библиотеки с 4-х разных ракурсов. Овцы к сожалению плоские, да и чертил я их от руки. Ну и двери со стенками чуть-чуть отличаются от тех, что в последней версии .dwg файла на GitHub, просто внес пару правок, а переснимать снимки экрана было лень.
Если будете загружать вашу библиотеку вручную командой NETLOAD помните, что она должна быть загружена до открытия файла с нашими объектами, или они распознаются как proxy объекты.
Для тех, кто новичок в работе с Нанокад, напомню, что получить трехмерный вид на ваши объекты удобно сделав так: вид-> орбита-> зависимая орбита, а вернуть двухмерный вид назад, можно так: вид-> виды и проекции-> вид в плане-> текущая ПСК.
В прошлой статье, у меня почему-то не заработала команда рисующая лицо, а вот в этот раз удалось адаптировать код и наш объект с небольшими ограничениями запускается и в бесплатной версии NanoCAD 5.1.
Для начала кратко расскажу, как настроить среду, отличий почти никаких.
Поэтому я опять лишь кратко упомяну порядок действий для сборки под Nanocad 5.1:
У вас пути к файлам будут свои.
Код кардинально различаться не будет поэтому я спрячу под спойлер оба класса и поясню, только различия.
Итак, стена:
В чем разница, во-первых, когда я пытался вставлять объекты в пустой чертеж, то Нанокад мне его так масштабировал, что было не видно ни стенку ни дверь и я добавил поле с масштабом.
Для стены оно изменяет толщину стены, а для двери — толщину и длину.
Соответственно нужные координаты теперь домножаются на масштаб, для которого есть открытое свойство.
И второе отличие в старой версии MultiCAD.NET API — нет класса для работы со штриховкой, могу предположить, что её можно реализовать через API для обычного .NET, но я не стал.
Теперь дверь:
Опять почти все тоже самое, единственное, в версии 5.1 похоже поле по-другому обрабатывает перечисления и в окошке свойств объекта вместо слов closed/open, мы увидим значения перечисления: 0, 1, 2 это не очень наглядно, поэтому мы немножко изменили предупреждение об ошибке. Также у двери нет штриховки и есть лишнее свойство для масштаба (его кстати при желании можно реализовать и в классах для NC 8.5).
Получится примерно так:
Как вы помните из прошлой статьи, я и Нанософт никак не связаны, а значит могу себе позволить небольшой элемент критики. Разработчики заявляют о поддержке MultiCAD.NET API в AutoCAD и ZWCAD через определенную прослойку, есть даже статья про это.
Но похоже, это неприоритетное направление разработки. В прошлой статье я писал, что не смог протестировать эту функцию потому, что у меня на компьютере установлен AutoCAD 2017, а последняя размещенная на сайте разработчиков прослойка — «MultiCAD_AC_ZC_Enabler_2209_RU.zip» (которой уже 1.5 года), не поддерживает ничего старше AutoCAD 2016. Ставить ради такого удовольствия еще одну версию Автокада мне не захотелось.
В этот раз я решил попробовать другой вариант, скачал пробную версию ZWCAD+ 2015, опять-таки последнюю версию, которую поддерживает данная прослойка. Не знаю, может быть я «рукожоп», но ни эта библиотека, ни библиотека из прошлой статьи у меня так и не «взлетела» в ZWCAD. Поэтому если у кого-то получится запустить, и он поделится скриншотом буду признателен.
Ну а в остальном, надо сказать, что чем больше возишься с этим API тем больше втягиваешься и даже начинает понемногу нравится, заметно что API улучшается, часть членов классов API уже имеет вменяемое описание на русском, да и само по себе API становится более самодостаточным.
Думаю, что когда выйдет NanoCAD 9 (или как его там назовут) с поддержкой DWG 2018, то станет еще лучше (особенно если как обещали на форуме разработчики, за ним следом выйдет новый бесплатный Нанокад).
Так что хочется сказать всем участникам форума разработчиков Нанокад — спасибо за помощь, разработчикам — спасибо за то, что выложили NC 8.5, а всем читателям — спасибо за то, что осилили статью до конца.
P.S. Изначально у статьи в заголовке должна была быть другая картинка, но я решил, что она «не айс» и в итоге заменил её на политкорректных Кермита и Гровера (кстати часть картинок ведут на те видео, что я смог найти). А вот исходную картинку я решил спрятать в конце статьи под…
В этот раз мне понадобилось понять, как создаются пользовательские объекты в NanoCAD с помощью MultiCAD.NET API. В блоге компании Нанософт есть статья от 2013 года, в которой объясняются базовые вопросы создания пользовательских примитивов. Но согласитесь было бы не интересно, просто воспроизвести эту статью, поэтому мы ее немного дополним.
В нашем случае мы создадим псевдотрехмерную дверь, которая к тому же будет уметь открываться и закрываться. А чтобы нашей маленькой дверке не было одиноко, мы создадим ей подружку — такую же стенку.
Под понятием «псевдо-3D» в данном случае я имею ввиду, что наши объекты не будут обладать свойствами модели твёрдого тела, то есть это будет просто набор связанных геометрических примитивов в трёхмерной системе координат. Может это не совсем корректный термин, но я пока лучше ничего не подобрал.
К сожалению, программистом при всем желании меня сейчас назвать нельзя, поэтому данная статья будет в стиле от новичка-новичкам и наверняка весь код, и приемы, которые вы в ней встретите можно будет улучшить.
Так или иначе если вы интересуетесь: проектированием, САПР, NanoCAD, разработкой под .NET и в частности на C#, а также овцами и Улицей Сезам, то возможно эта статья как раз для вас.
Вам тоже интересно причем тут овцы и Улица Сезам? Тогда милости прошу под кат.
Не буду нагнетать драматическую паузу, овцы – это некоторая метафора поясняющая бессмысленность, того чем мы сегодня займемся (картинка чуть позже).
Ну, а Улица Сезам, здесь просто потому, что я недавно про нее вспомнил и меня разбила жуткая ностальгия по куклам «Маппетам», так что они помогут нам выдержать единую стилистику повествования.
Наверное, глупо было так быстро раскрыть всю интригу? Но я надеюсь, что вы все же продолжите читать статью.
Содержание:
Часть I: С новым CADом! (Введение).
Часть II: Пишем код под NanoCAD 8.5
Часть III: Пробуем адаптировать код под бесплатный NanoCAD 5.1.
Часть IV: МультиКукиш (Заключение)
1. С новым CADом! (Введение).
Начать хотелось бы с того, что на портале для разработчиков NanoCAD стала доступна стабильная версия свежего NanoCAD 8.5 SDK и в этот раз мы будем ориентироваться именно на нее.
В своей прошлой статье ориентированной на NanoCAD 8.1, я поделился своим мнением относительно платформы, мы разобрали процесс подготовки проекта к сборке и написали простенькую команду, поэтому если Вы её пропустили и совсем незнакомы с NanoCAD и разработкой с помощью MultiCAD .NET API, то можно начать со статьи «Лицо без шрама» или первые шаги в Multicad.NET API 7 (для Nanocad 8.1)
В этот раз я планирую поменьше «лить воды» и побольше уделить внимания технической стороне.
Единственное перед тем как перейти к разработке наших объектов скажу, что до начала подготовки этой статьи, я по сути пользовался только бесплатной версий NanoCAD (NC 5.1), которая была выпущена аж в 2013 году.
Причиной этому были две: она полностью бесплатна для любых целей, а вторая причина — у меня весьма слабенький комп, поэтому AutoCAD на нем подтормаживает и чертовски долго грузится.
Но поскольку перед тем как писать эту статью, мне надо был потренироваться «ручками» чертить объект, а также разобратся как работает трехмерный просмотр объекта, ну и самое главное 10000 раз перезапустить CAD в процессе отладки, то я успел чуть-чуть рассмотреть и NanoCAD 8.5.
Так вот на первый взгляд могу сказать следующее, чертить приятней чем в старой бесплатной версии, а грузится также быстро как старый NanoCAD 5.1, то есть NC 8.5 стартует в несколько раз быстрее чем его сверстник — AutoCAD 2017 (если кому любопытно пишите в комментарии, сделаю замер с секундомером). Остается только надеется, что однажды компания обновит бесплатную версию, перенеся в нее новые API и новые фишки в части функций «электронного кульмана».
Ну и последнее, как я понимаю в версиях NanoCAD доступных для разработчиков, включен модуль трехмерного твердотельного моделирования, но я не смог так сходу разобраться с API к нему, особенно для объектов, создаваемых пользователем. Может быть в другой раз мы его изучим. А пока мы будем довольствоваться «псевдо-3D» объектами.
В этом даже есть свое преимущество, наша с вами библиотека после небольшой адаптации запустится и в бесплатном NanoCAD 5.1, где твердотельного моделирования нет и в помине. Но об этом чуть позже.
2. Пишем код под NanoCAD 8.5
Да, да, граф фон Знак всё верно сосчитал! Забегая вперед именно столько овец, стен и дверей мы получим в конце. Теперь у графа Знака будет новое задание — считать просмотры и голоса за статью. Я уже прям слышу это: «один, один просмотр, два – два просмотра, три…»
Как обычно, полный код классов и пример dwg фалов вы найдете на GitHub.
А сейчас мы начнем разбирать его по частям. Я не стал прилагать готовые сборки, думаю вы сможете собрать проект самостоятельно, в прошлой статье , я подробно с картинками рассказывал, как создать и настроить проект под MS Visual Studio 2015, для NanoCAD 8.1, так вот с того момента ничего сильно не поменялось.
Поэтому в этот раз я лишь кратко упомяну порядок действий для сборки под Nanocad 8.5:
- Создать новый проект выбрать платформу .NET Framework 4, в качестве шаблона выбрать библиотеку классов C#.
- Для версии Нанокада x64 (а у меня такая) из папки SDK\include-x64\ добавить в проект ссылки на: mapibasetypes.dll, mapimgd.dll, imapimgd.dll. Не забудьте для всех трех библиотек свойство копировать локально установить в False.
- Также добавим ссылки на сборки от Микрософт: System.Windows.Forms.dll, System.Drawing.dll.
- В свойствах проекта, на вкладке «Отладка», в качестве действия при запуске выберем «открывать во внешней программе» и укажем путь к исполняемому файлу NC 8.5 (у меня — C:\Program Files\Nanosoft\nanoCAD x64 Plus 8.5\nCad.exe)
- Создадим два класса DoorPseudo3D.cs и WalllPseudo3D.cs для двери и стены соответственно.
- Перейдем по адресу C:\ProgramData\Nanosoft\nanoCAD x64 Plus 8.5\DataRW (у вас может отличаться) и найдем или создадим файл load.config следующего содержания
<root>
<list>
<module path="C:\Users\...\bin\Debug\nanodoor2.dll"/>
</list>
</root>
У вас естественно название проекта и путь к нему могут отличаться.
Ну вот и все мы готовы к разработке, теперь у нас по нажатию F5 автоматически запускается NC 8.5 и сразу подгружается наша сборка, останется только вводить разработанные команды.
Еще раз оговорюсь, я не программист, поэтому скорей всего в коде будет куча огрехов: сбои при сохранении, перемещении или копировании объектов, да и просто неоптимальные решения. Если кто-то, не сильно усложняя код сможет эго довести до ума – «земной поклон».
Но так или иначе, на базе этого кода мы с вами сможем немного разобраться в том, как создавать свои объекты, а значит свою главную цель он выполняет.
Ну и безусловно надо сказать большое спасибо Александру Полховскому с форума разработчиков NanoCAD, он мне очень помог с переопределением функционала, связанного с перемещением и поворотом объекта (пригодилось для открытия/закрытия двери). Да и всем другим участникам форума тоже спасибо, напомню это на данный момент один из самых доступных источников информации по MultiCAD.NET API.
Начнем мы с вами со стенки, потому, что она попроще в исполнении.
Для начала добавим пространства имен.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using Multicad.Runtime;
using Multicad.DatabaseServices;
using Multicad.Geometry;
using Multicad.CustomObjectBase;
using Multicad;
Затем создадим класс пользовательского объекта.
namespace nanowall2
{
//change "8b0986c0-4163-42a4-b005-187111b499d7" for your Guid from Assembly.
// Be careful GUID for door and wall classes must be different!
// Otherwise there will be problems with saving and moving
[CustomEntity(typeof(WalllPseudo3D), "8b0986c0-4163-42a4-b005-187111b499d7", "WalllPseudo3D", "WalllPseudo3D Sample Entity")]
[Serializable]
public class WalllPseudo3D : McCustomBase
{
Название класса возьмите какое хотите (можно оставить и мое), главное чтобы он наследовал от McCustomBase.
Все атрибуты класса – обязательны, я если честно параметры атрибута CustomEntity не до конца понимаю, поэтому тупо переделал по аналогии.
«8b0986c0-4163-42a4-b005-187111b499d7» — в моем примере это GUID, я видимо «прощелкал» тот момент, где в документации по .NET объяснялось как с ним работать. Могу сказать только одно, я для простоты брал его из файла настроек сборки, заменяя последнюю цифру для обеспечения уникальности. Если GUID у классов двери и стены будет полностью одинаковым, то начнутся чудеса: стены при копировании будут превращаться в двери, а двери после сохранения файла терять свой функционал, у себя я это вроде исправил надеюсь и у вас проблем не будет.
Определим поля класса.
private Point3d _pnt1 = new Point3d(100, 100, 0);
private Point3d _pnt2 = new Point3d(500, 100, 0);
private double _h = 2085;
Поля _pnt1 и _pnt12, это базовые точки по которым будет строится геометрия нашей стены (длина стены по сути), _h это высота стены по умолчанию (после создания объекта можно будет поправить).
Дальше создадим команду по вызову, которой будет обрисовываться наша дверь.
DrawWall в атрибуте CommandMethod, это имя команды, которое вы будете вводить в командную строку, чтобы вызвать объект, вы можете его сократить например на DWall, без потери функциональности.
[CommandMethod("DrawWall", CommandFlags.NoCheck | CommandFlags.NoPrefix)]
public void DrawWall() {
WalllPseudo3D wall = new WalllPseudo3D();
wall.PlaceObject();
}
Мы в классе реализующем нашу команду создаем новый экземпляр класса стена (если этого не сделать, то у меня все стенки начинают восприниматься как одна «убер» стенка). А метод PlaceObject мы определим чуть позже.
Определим процедуру отрисовки объекта.
public override void OnDraw(GeometryBuilder dc)
{
dc.Clear();
Я не полностью понимаю этот кусок, но так или иначе в API есть класс GeometryBuilder, на основании, которого мы и будем дальше ваять нашу стенку.
Dc.Clear, по всей видимости очищает каждый раз всю ранее построенную для экземпляра класса геометрию.
Дальше проще.
Point3d pnt1 = _pnt1;
Point3d pnt2 = new Point3d(_pnt2.X, pnt1.Y, 0);
Point3d pnt3 = new Point3d(pnt2.X, pnt1.Y+150, 0);
Point3d pnt4 = new Point3d(pnt1.X , pnt3.Y, 0);
// Set the color to ByObject value
dc.Color = McDbEntity.ByObject;
Vector3d hvec = new Vector3d(0, 0, _h);
Мы определяем четыре базовые точки на основании которых будет строится основание и верхушка стены, причем первая и вторая точка увязываются с полями класса, а значит именно ими мы потом и будем манипулировать. Наша стенка в длину будет строиться по расстоянию между точками _pnt1 и pnt2, а вот ширина стенки задана жестко её поправить нельзя (так сделано для простоты), но вы легко можете переопределить логику работы по аналогии.
dc.Color – похоже задает свойство «цвет по блоку» для объекта.
Вектор hvec это высота нашей стенки, которую мы будем прибавлять к основанию, чтобы построить верхушку.
Дальше чертим нижнюю и верхнюю стороны стенки.
dc.DrawPolyline(new Point3d[] { pnt1, pnt2, pnt3, pnt4, _pnt1 });
dc.DrawPolyline(new Point3d[] { _pnt1.Add(hvec),
pnt2.Add(hvec), pnt3.Add(hvec), pnt4.Add(hvec), pnt1.Add(hvec)});
Соединяем низ и верх ребрами.
dc.DrawLine(pnt1, pnt1.Add(hvec));
dc.DrawLine(pnt2, pnt2.Add(hvec));
dc.DrawLine(pnt3, pnt3.Add(hvec));
dc.DrawLine(pnt4, pnt4.Add(hvec));
Создаем контур из поллиний для штриховки и затем заливаем его штриховкой кирпич (названия штриховок можно посмотреть в самой программе). Причем у меня не получилось заштриховать через список полилиний пришлось повторять процедуру два раза. Думаю, я что-то не учел.
Мы с вами штрихуем только 2 поверхности стенки — самые длинные, если захотите можете самостоятельно заштриховать остальное.
// Create contours for the front and rear sides and hatch them
// In this demo, we hatch only two sides, you can tailor the others yourself
List<Polyline3d> c1 = new List<Polyline3d>();
c1.Add(new Polyline3d(
new List<Point3d>() { pnt1, pnt1.Add(hvec), pnt2.Add(hvec), pnt2, pnt1, }));
dc.DrawGeometry(new Hatch(c1, "BRICK", 0, 20, false, HatchStyle.Normal, PatternType.PreDefined, 30), 1);
List<Polyline3d> c2 = new List<Polyline3d>();
c2.Add(new Polyline3d(
new List<Point3d>() { pnt4, pnt4.Add(hvec), pnt3.Add(hvec), pnt3, pnt4, }));
dc.DrawGeometry(new Hatch(c2, "BRICK", 0, 20, false, HatchStyle.Normal, PatternType.PreDefined, 30), 1);
}
Определим пользовательское свойство для объекта, поскольку на мой взгляд чертить удобней в двухмерном виде, то высоту стенки в момент черчения выставлять не удобно, можно было сделать установку высоты стенки как часть процедуры ее отрисовки, но мы пойдем более простым путем, просто добавим свойство, с помощью которого будем менять высоту уже после того как её начертим.
//Define the custom properties of the object
[DisplayName("Height")]
[Description("Height of wall")]
[Category("Wall options")]
public double HWall
{
get
{
return _h;
}
set
{
//Save Undo state and set the object status to "Changed"
if (!TryModify())
return;
_h = value;
}
}
По атрибутам [DisplayName(«Height»)] – имя которое будет в окне свойств, [Description(«Height of wall»)], это описание, но я не понял, где оно отображается, [Category(«Wall options»)] – категория полей, как вы позже увидите на примере дверей, наши поля модно сгруппировать для удобства.
Ну а дальше идет обычно свойство, если вы когда-нибудь делали публичные свойства в Unity 3D, то механизм похож, можем иметь доступ к полям класса прямо из редактора (в нашем случае из САПР).
TryModify() – это обязательный метод, его надо вызывать перед каждым изменением свойств объекта, как я понял. Мы с ним еще пару раз встретимся.
Дальше переопределяем метод отвечающий за размещение объекта на чертеже (помните мы его раньше командой вызывали).
public override hresult PlaceObject(PlaceFlags lInsertType)
{
InputJig jig = new InputJig();
// Get the first box point from the jig
InputResult res = jig.GetPoint("Select first point:");
if (res.Result != InputResult.ResultCode.Normal)
return hresult.e_Fail;
_pnt1 = res.Point;
// Add the object to the database
this.DbEntity.AddToCurrentDocument();
//Exclude the object from snap points
jig.ExcludeObject(ID);
// Monitoring mouse moving and interactive entity redrawing
jig.MouseMove = (s, a) => { TryModify(); _pnt2 = a.Point; this.DbEntity.Update(); };
// Get the second box point from the jig
res = jig.GetPoint("Select second point:");
if (res.Result != InputResult.ResultCode.Normal)
{
this.DbEntity.Erase();
return hresult.e_Fail;
}
_pnt2 = res.Point;
return hresult.s_Ok;
}
Этот код почти полностью позаимствован из примера от Нанософт о котором я упоминал в первой главе, я его не на 100% понимаю, но если вкратце мы вызываем команду для ввода первой точки стены (jig.GetPoint), затем помещаем её в чертеж (DbEntity.AddToCurrentDocument()), после чего исключаем объект из привязок, чтобы он нам не мешал вводить вторую точку (_pnt2).
Если все нормально, то объект размещается в чертеже, если нет (например не завершён ввод), то объект удаляется.
И последнее делаем ручки для изменения размера стенки.
// Create a grip for the base point of the object
public override bool GetGripPoints(GripPointsInfo info)
{
info.AppendGrip(new McSmartGrip<WalllPseudo3D>(_pnt1, (obj, g, offset) => { obj.TryModify(); obj._pnt1 += offset; }));
info.AppendGrip(new McSmartGrip<WalllPseudo3D>(_pnt2, (obj, g, offset) => { obj.TryModify(); obj._pnt2 += offset; }));
return true;
}
}
// TODO: There are many shortcomings in this code.
// Including failures when working with copying, moving objects and saving files, you can improve it if you want.
}
Как я понимаю, код из примера который я упоминал выше, в части ручек устарел (для NC 8.X) и лучше ориентироваться на код из этого примера.
Ну и естественно я предупреждаю, вас что мой код далек от идеала поэтому адекватным, вменяемым правкам буду рад.
Теперь рассмотрим дверь. Начало — аналогичное.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
using Multicad.Runtime;
using Multicad.DatabaseServices;
using Multicad.Geometry;
using Multicad.CustomObjectBase;
using Multicad;
namespace nanodoor2
{
//change "8b0986c0-4163-42a4-b005-187111b499d7" for your Guid from Assembly.
// Be careful GUID for door and wall classes must be different!
// Otherwise there will be problems with saving and moving
[CustomEntity(typeof(DoorPseudo3D), "8b0986c0-4163-42a4-b005-187111b499d9", "DoorPseudo3D", "DoorPseudo3D Sample Entity")]
[Serializable]
public class DoorPseudo3D : McCustomBase
{
// First and second vertices of the box
private Point3d _pnt1 = new Point3d(0, 0, 0);
private double _h = 2085;
private Vector3d _vecStraightDirection = new Vector3d(1, 0, 0);
private Vector3d _vecDirectionClosed = new Vector3d(1, 0, 0);
public enum status { closed , middle, open };
private status _dStatus = status.closed;
[CommandMethod("DrawDoor", CommandFlags.NoCheck | CommandFlags.NoPrefix)]
public void DrawDoor() {
DoorPseudo3D door = new DoorPseudo3D();
door.PlaceObject();
}
Разве что добавилось поле, которое будет отвечать за то открыта или закрыта наша дверь, а также появлюсь два вектора _vecStraightDirection — отвечает за текущий поворот двери, _vecDirectionClosed — хранит данные о повороте двери в закрытом состоянии. Это нам все пригодится позже.
А вот непосредственно в геометрии двери, как и следовало ожидать есть маленькие изменения.
public override void OnDraw(GeometryBuilder dc)
{
dc.Clear();
// Define the basic points for drawing
Point3d pnt1 = new Point3d(0, 0, 0);
Point3d pnt2 = new Point3d(pnt1.X + 984, pnt1.Y, 0);
Point3d pnt3 = new Point3d(pnt2.X + 0, pnt1.Y+50, 0);
Point3d pnt4 = new Point3d(pnt1.X , pnt3.Y, 0);
// Set the color to ByObject value
dc.Color = McDbEntity.ByObject;
Vector3d hvec = new Vector3d(0, 0, _h);
// Draw the upper and lower sides
dc.DrawPolyline(new Point3d[] { pnt1, pnt2, pnt3, pnt4, pnt1 });
dc.DrawPolyline(new Point3d[] { pnt1.Add(hvec),
pnt2.Add(hvec), pnt3.Add(hvec), pnt4.Add(hvec), pnt1.Add(hvec)});
// Draw the edges
dc.DrawLine(pnt1, pnt1.Add(hvec));
dc.DrawLine(pnt2, pnt2.Add(hvec));
dc.DrawLine(pnt3, pnt3.Add(hvec));
dc.DrawLine(pnt4, pnt4.Add(hvec));
// Drawing a Door Handle
dc.DrawLine(pnt2.Add(new Vector3d( - 190, -0, _h*0.45)),
pnt2.Add(new Vector3d(-100, 0, _h * 0.45)));
dc.DrawLine(pnt3.Add(new Vector3d(-190, 0, _h * 0.45)),
pnt3.Add(new Vector3d(-100, 0, _h * 0.45)));
// Create contours for the front and rear sides and hatch them
// In this demo, we hatch only two sides, you can tailor the others yourself
List<Polyline3d> c1 = new List<Polyline3d>();
c1.Add(new Polyline3d(
new List<Point3d>() { pnt1, pnt1.Add(hvec), pnt2.Add(hvec), pnt2, pnt1, }));
List<Polyline3d> c2 = new List<Polyline3d>();
c2.Add(new Polyline3d(
new List<Point3d>() { pnt4, pnt4.Add(hvec), pnt3.Add(hvec), pnt3, pnt4, }));
dc.DrawGeometry(new Hatch(c1, "JIS_WOOD", 0, 170, false, HatchStyle.Normal, PatternType.PreDefined, 500), 1);
dc.DrawGeometry(new Hatch(c2, "JIS_WOOD", 0, 170, false, HatchStyle.Normal, PatternType.PreDefined, 500), 1);
}
Во-первых, обратите внимание, что мы строим дверь по одной точке, то есть размер двери по ширине и высоте у нас жестко закреплен (ну чтобы она от стены отличалась). Также добавилась секция «// Drawing a Door Handle», там 2 линии которые обозначают условную ручку, ну и еще тип штриховки мы заменили на JIS_WOOD
А вот метод PlaceObject у нас упростился, за счет того, что не нужна вторая ручка.
public override hresult PlaceObject(PlaceFlags lInsertType)
{
InputJig jig = new InputJig();
// Get the first box point from the jig
InputResult res = jig.GetPoint("Select first point:");
if (res.Result != InputResult.ResultCode.Normal)
return hresult.e_Fail;
_pnt1 = res.Point;
// Add the object to the database
DbEntity.AddToCurrentDocument();
return hresult.s_Ok;
}
Дальше идет полная новинка по отношению к классу стенки. За которую я в начале статьи поблагодарил Александра.
Ниже мы переопределим метод, который каким-то мистическим образом отвечает за создании матрицы трансформации (перемещения и поворота) нашей двери.
/// <summary>
/// Method for changing the object's SC (the graph is built at the origin of coordinates).
/// </ summary>
/// <param name = "tfm"> The matrix for changing the position of the object. </ param>
/// <returns> True - if the matrix is passed, False - if not. </ returns>
public override bool GetECS(out Matrix3d tfm)
{
// Create a matrix that transforms the object.
// The object is drawn in coordinates(0.0), then it is transformed with the help of this matrix.
tfm = Matrix3d.Displacement(this._pnt1.GetAsVector()) * Matrix3d.Rotation
(-this._vecStraightDirection.GetAngleTo(Vector3d.XAxis, Vector3d.ZAxis), Vector3d.ZAxis, Point3d.Origin);
return true;
}
Я не полностью понимаю работу этого метода, но похоже при каждом перемещении он преобразует нашу дверь в зависимости от базовой точки и вектора поворота, это в конечном счете позволяет нам открывать или закрывать дверь на чертеже.
Дальше мы переопределяем событие которое похоже наступает при трансформации объекта.
public override void OnTransform(Matrix3d tfm)
{
// To be able to cancel(Undo)
McUndoPoint undo = new McUndoPoint();
undo.Start();
// Get the coordinates of the base point and the rotation vector
this.TryModify();
this._pnt1 = this._pnt1.TransformBy(tfm);
this.TryModify();
this._vecStraightDirection = this._vecStraightDirection.TransformBy(tfm);
// We move the door only when it is closed if not - undo
if (_dStatus == status.closed) _vecDirectionClosed = _vecStraightDirection;
else
{
MessageBox.Show("Please transform only closed door");
undo.Undo();
}
undo.Stop();
}
Для начала скажу, что чтобы не застревать с разработкой, некоторые моменты я упрощал, особенно те решения, которые мне давались с трудом. Одно из таких упрощений — это ограничение на манипуляции с дверью.
Двигать, поворочать, копировать и как-либо еще изменять дверь можно только в закрытом состоянии (оно установлено по умолчанию).
Для того, чтобы оно так и работало мы создаем объект undo и отмечаем точку старта для фиксации изменений.
После чего если все нормально передаем точке _pnt1 и вектору ._vecStraightDirection их состояние после трансформации.
Затем идет проверка условия, если дверь была закрыта, то изменения применяются и дополнительно заносятся в вектор который хранит данные о положении закрытой двери.
Если дверь была открыта (или приоткрыта) мы выдаем сообщение об ошибке и отменяем все изменения.
Поле высоты двери – аналогично стене.
//Define the custom properties of the object
[DisplayName("Height")]
[Description("Height of door")]
[Category("Door options")]
public double HDoor
{
get
{
return _h;
}
set
{
//Save Undo state and set the object status to "Changed"
if (!TryModify())
return;
_h = value;
}
}
А вот следующее поле – новенькое
[DisplayName("Door status")]
[Description("Door may be: closed, middle, open")]
[Category("Door options")]
public status Stat
{
get
{
return _dStatus;
}
set
{
//Save Undo state and set the object status to "Changed"
if (!TryModify())
return;
// Change the rotation vector for each of the door states
switch (value)
{
case status.closed:
_vecStraightDirection = _vecDirectionClosed;
break;
case status.middle:
_vecStraightDirection = _vecDirectionClosed.Add(_vecDirectionClosed.GetPerpendicularVector().Negate() * 0.575) ;
break;
case status.open:
_vecStraightDirection = _vecDirectionClosed.GetPerpendicularVector()*-1;
break;
default:
_vecStraightDirection = _vecDirectionClosed;
break;
}
_dStatus = value;
}
}
Именно оно у нас и отвечает за состояние двери, в окне свойств появляется выпадающий список со значениями: closed, middle, open (один в один, как определение перечисления вначале класса).
При выборе каждого из значений изменяется в конечном счете вектор отвечающий за поворот двери.
При закрытой он выставляется в заранее сохранённое состояние _vecDirectionClosed;
При полуоткрытом состоянии получается результирующий вектор, который поворачивает нашу дверь на угол примерно 30 градусов, чтобы было похоже на обозначение по ГОСТ.
При открытом состоянии мы просто берем перпендикуляр к нашему вектору закрытого состояния с отрицательным значением (чтобы дверь открывалась по умолчанию вниз).
Дефолтный случай думаю не нужен вовсе, но я оставил.
Ну и последнее это ручка для манипулирования.
// Create a grip for the base point of the object
public override bool GetGripPoints(GripPointsInfo info)
{
info.AppendGrip(new McSmartGrip<DoorPseudo3D>(_pnt1, (obj, g, offset) => { obj.TryModify(); obj._pnt1 += offset; }));
return true;
}
// TODO: There are many shortcomings in this code.
// Including failures when working with copying, moving objects and saving files, you can improve it if you want.
}
За нее можно перетаскивать и все. Ручки у меня время от времени у обоих объектов скачут, куда попало, но отлаживать это у меня уже нет сил (я думал, что закончу статью быстрее, а убил уже три полных дня).
Итак, жмем F5 и с помощью команд DRAWWALL и DRAWDOOR вставляем наши двери и стены.
В результате получим то что на рисунке. На нём я демонстрирую вам работу библиотеки с 4-х разных ракурсов. Овцы к сожалению плоские, да и чертил я их от руки. Ну и двери со стенками чуть-чуть отличаются от тех, что в последней версии .dwg файла на GitHub, просто внес пару правок, а переснимать снимки экрана было лень.
Если будете загружать вашу библиотеку вручную командой NETLOAD помните, что она должна быть загружена до открытия файла с нашими объектами, или они распознаются как proxy объекты.
Для тех, кто новичок в работе с Нанокад, напомню, что получить трехмерный вид на ваши объекты удобно сделав так: вид-> орбита-> зависимая орбита, а вернуть двухмерный вид назад, можно так: вид-> виды и проекции-> вид в плане-> текущая ПСК.
3. Пробуем адаптировать код под бесплатный NanoCAD 5.1.
В прошлой статье, у меня почему-то не заработала команда рисующая лицо, а вот в этот раз удалось адаптировать код и наш объект с небольшими ограничениями запускается и в бесплатной версии NanoCAD 5.1.
Для начала кратко расскажу, как настроить среду, отличий почти никаких.
Поэтому я опять лишь кратко упомяну порядок действий для сборки под Nanocad 5.1:
- Создать новый проект выбрать платформу .NET Framework 3.5, в качестве шаблона выбрать библиотеку классов C#.
- Для версии Нанокада x32 (а 5.1 только такая) из папки SDK\include\ добавить в проект ссылку на: mapimgd. Не забудьте свойство копировать локально установить в False.
- Также добавим ссылки на сборки от Микрософт: System.Windows.Forms.dll, System.Drawing.dll.
- В свойствах проекта, на вкладке «Отладка», в качестве действия при запуске выберем «открывать во внешней программе» и укажем путь к исполняемому файлу NC 5.1 (у меня — C:\Program Files (x86)\Nanosoft\nanoCAD 5.1\nCad.exe)
- Я еще до кучи в разделе «Сборка» установил конечную платформу – x86.
- Создадим два класса DoorPseudo3D_nc51.cs и WalllPseudo3D_nc51.cs для двери и стены соответственно.
- Перейдем по C:\ProgramData\Nanosoft\nanoCAD 5.1\DataRW (у вас может отличаться) и найдем или создадим файл load.config следующего содержания
<root>
<list>
<module path="C:\Users\...\bin\Debug\ nanodoor2_51.dll"/>
</list>
</root>
У вас пути к файлам будут свои.
Код кардинально различаться не будет поэтому я спрячу под спойлер оба класса и поясню, только различия.
Итак, стена:
Полный код для стены
//Use Microsoft .NET Framework 3.5 and old version of MultiCad.NET (for NC 5.1)
//Class for demonstrating the capabilities of MultiCad.NET
//Assembly for the Nanocad 5.1
//Link mapimgd from Nanocad SDK
//Link System.Windows.Forms and System.Drawing
//The commands: draws a pseudo 3D wall.
//This code in the part of non-infringing rights Nanosoft can be used and distributed in any accessible ways.
//For the consequences of the code application, the developer is not responsible.
//More detailed - https://habrahabr.ru/post/342680/
using System;
using System.ComponentModel;
using Multicad.Runtime;
using Multicad.DatabaseServices;
using Multicad.Geometry;
using Multicad.CustomObjectBase;
using Multicad;
namespace nanowall2
{
//change "8b0986c0-4163-42a4-b005-187111b499d7" for your Guid from Assembly.
// Be careful GUID for door and wall classes must be different!
// Otherwise there will be problems with saving and moving
[CustomEntity(typeof(WalllPseudo3D_nc51), "b4edac1f-7978-483f-91b1-10503d20735a", "WalllPseudo3D_nc51", "WalllPseudo3D_nc51 Sample Entity")]
[Serializable]
public class WalllPseudo3D_nc51 : McCustomBase
{
// First and second vertices of the box
private Point3d _pnt1 = new Point3d(100, 100, 0);
private Point3d _pnt2 = new Point3d(500, 100, 0);
private double _h = 2085;
private double _scale = 1000;
[CommandMethod("DrawWall", CommandFlags.NoCheck | CommandFlags.NoPrefix)]
public void DrawWall() {
WalllPseudo3D_nc51 wall = new WalllPseudo3D_nc51();
wall.PlaceObject();
}
public override void OnDraw(GeometryBuilder dc)
{
dc.Clear();
// Define the basic points for drawing
Point3d pnt1 = _pnt1;
Point3d pnt2 = new Point3d(_pnt2.X, pnt1.Y, 0);
Point3d pnt3 = new Point3d(pnt2.X, pnt1.Y+(150 * _scale), 0);
Point3d pnt4 = new Point3d(pnt1.X , pnt3.Y, 0);
// Set the color to ByObject value
dc.Color = McDbEntity.ByObject;
Vector3d hvec = new Vector3d(0, 0, _h * _scale);
// Draw the upper and lower sidestes
dc.DrawPolyline(new Point3d[] { pnt1, pnt2, pnt3, pnt4, pnt1 });
dc.DrawPolyline(new Point3d[] { _pnt1.Add(hvec),
pnt2.Add(hvec), pnt3.Add(hvec), pnt4.Add(hvec), pnt1.Add(hvec)});
// Draw the edges
dc.DrawLine(pnt1, pnt1.Add(hvec));
dc.DrawLine(pnt2, pnt2.Add(hvec));
dc.DrawLine(pnt3, pnt3.Add(hvec));
dc.DrawLine(pnt4, pnt4.Add(hvec));
}
//Define the custom properties of the object
[DisplayName("WScale")]
[Description("Wall Scale")]
[Category("Wall options")]
public double WScale
{
get
{
return _scale;
}
set
{
if (!TryModify())
return;
_scale = value;
}
}
[DisplayName("Height")]
[Description("Height of wall")]
[Category("Wall options")]
public double HWall
{
get
{
return _h;
}
set
{
//Save Undo state and set the object status to "Changed"
if (!TryModify())
return;
_h = value;
}
}
public override hresult PlaceObject(PlaceFlags lInsertType)
{
InputJig jig = new InputJig();
// Get the first box point from the jig
InputResult res = jig.GetPoint("Select first point:");
if (res.Result != InputResult.ResultCode.Normal)
return hresult.e_Fail;
_pnt1 = res.Point;
// Add the object to the database
this.DbEntity.AddToCurrentDocument();
//Exclude the object from snap points
jig.ExcludeObject(ID);
// Monitoring mouse moving and interactive entity redrawing
jig.MouseMove = (s, a) => { TryModify(); _pnt2 = a.Point; this.DbEntity.Update(); };
// Get the second box point from the jig
res = jig.GetPoint("Select second point:");
if (res.Result != InputResult.ResultCode.Normal)
{
this.DbEntity.Erase();
return hresult.e_Fail;
}
_pnt2 = res.Point;
return hresult.s_Ok;
}
// Create a grip for the base point of the object
public override bool GetGripPoints(GripPointsInfo info)
{
info.AppendGrip(new McSmartGrip<WalllPseudo3D_nc51>(_pnt1, (obj, g, offset) => { obj.TryModify(); obj._pnt1 += offset; }));
info.AppendGrip(new McSmartGrip<WalllPseudo3D_nc51>(_pnt2, (obj, g, offset) => { obj.TryModify(); obj._pnt2 += offset; }));
return true;
}
}
// TODO: There are many shortcomings in this code.
// Including failures when working with copying, moving objects and saving files, you can improve it if you want.
}
В чем разница, во-первых, когда я пытался вставлять объекты в пустой чертеж, то Нанокад мне его так масштабировал, что было не видно ни стенку ни дверь и я добавил поле с масштабом.
Для стены оно изменяет толщину стены, а для двери — толщину и длину.
Соответственно нужные координаты теперь домножаются на масштаб, для которого есть открытое свойство.
И второе отличие в старой версии MultiCAD.NET API — нет класса для работы со штриховкой, могу предположить, что её можно реализовать через API для обычного .NET, но я не стал.
Теперь дверь:
Полный код для двери
//Use Microsoft .NET Framework 3.5 and old version of MultiCad.NET (for NC 5.1)
//Class for demonstrating the capabilities of MultiCad.NET
//Assembly for the Nanocad 5.1
//Link mapimgd from Nanocad SDK
//Link System.Windows.Forms and System.Drawing
//The commands: draws a pseudo 3D door.
//This code in the part of non-infringing rights Nanosoft can be used and distributed in any accessible ways.
//For the consequences of the code application, the developer is not responsible.
//More detailed - https://habrahabr.ru/post/342680/
// P.S. A big thanks to Alexander Vologodsky for help in developing a method for pivoting object.
using System;
using System.ComponentModel;
using System.Windows.Forms;
using Multicad.Runtime;
using Multicad.DatabaseServices;
using Multicad.Geometry;
using Multicad.CustomObjectBase;
using Multicad;
namespace nanodoor2
{
//change "8b0986c0-4163-42a4-b005-187111b499d7" for your Guid from Assembly.
// Be careful GUID for door and wall classes must be different!
// Otherwise there will be problems with saving and moving
[CustomEntity(typeof(DoorPseudo3D_nc51), "b4edac1f-7978-483f-91b1-10503d20735b", "DoorPseudo3D_nc51", "DoorPseudo3D_nc51 Sample Entity")]
[Serializable]
public class DoorPseudo3D_nc51 : McCustomBase
{
// First and second vertices of the box
private Point3d _pnt1 = new Point3d(0, 0, 0);
private double _scale = 1000;
private double _h = 2085;
private Vector3d _vecStraightDirection = new Vector3d(1, 0, 0);
private Vector3d _vecDirectionClosed = new Vector3d(1, 0, 0);
public enum status { closed , middle, open };
private status _dStatus = status.closed;
[CommandMethod("DrawDoor", CommandFlags.NoCheck | CommandFlags.NoPrefix)]
public void DrawDoor() {
DoorPseudo3D_nc51 door = new DoorPseudo3D_nc51();
door.PlaceObject();
}
public override void OnDraw(GeometryBuilder dc)
{
dc.Clear();
// Define the basic points for drawing
Point3d pnt1 = new Point3d(0, 0, 0);
Point3d pnt2 = new Point3d(pnt1.X + (984 * _scale), pnt1.Y, 0);
Point3d pnt3 = new Point3d(pnt2.X + 0, pnt1.Y+(50 * _scale), 0);
Point3d pnt4 = new Point3d(pnt1.X , pnt3.Y, 0) ;
// Set the color to ByObject value
dc.Color = McDbEntity.ByObject;
Vector3d hvec = new Vector3d(0, 0, _h * _scale) ;
// Draw the upper and lower sides
dc.DrawPolyline(new Point3d[] { pnt1, pnt2, pnt3, pnt4, pnt1 });
dc.DrawPolyline(new Point3d[] { pnt1.Add(hvec),
pnt2.Add(hvec), pnt3.Add(hvec), pnt4.Add(hvec), pnt1.Add(hvec)});
// Draw the edges
dc.DrawLine(pnt1, pnt1.Add(hvec));
dc.DrawLine(pnt2, pnt2.Add(hvec));
dc.DrawLine(pnt3, pnt3.Add(hvec));
dc.DrawLine(pnt4, pnt4.Add(hvec));
// Drawing a Door Handle
dc.DrawLine(pnt2.Add(new Vector3d( -190 * _scale, -0, _h*0.45 * _scale)),
pnt2.Add(new Vector3d(-100 * _scale, 0, _h * 0.45 * _scale)));
dc.DrawLine(pnt3.Add(new Vector3d(-190 * _scale, 0, _h * 0.45 * _scale)),
pnt3.Add(new Vector3d(-100 * _scale, 0, _h * 0.45 * _scale)));
}
public override hresult PlaceObject(PlaceFlags lInsertType)
{
InputJig jig = new InputJig();
// Get the first box point from the jig
InputResult res = jig.GetPoint("Select first point:");
if (res.Result != InputResult.ResultCode.Normal)
return hresult.e_Fail;
_pnt1 = res.Point;
// Add the object to the database
DbEntity.AddToCurrentDocument();
return hresult.s_Ok;
}
/// <summary>
/// Method for changing the object's SC (the graph is built at the origin of coordinates).
/// </ summary>
/// <param name = "tfm"> The matrix for changing the position of the object. </ param>
/// <returns> True - if the matrix is passed, False - if not. </ returns>
public override bool GetECS(out Matrix3d tfm)
{
// Create a matrix that transforms the object.
// The object is drawn in coordinates(0.0), then it is transformed with the help of this matrix.
tfm = Matrix3d.Displacement(this._pnt1.GetAsVector()) * Matrix3d.Rotation
(-this._vecStraightDirection.GetAngleTo(Vector3d.XAxis, Vector3d.ZAxis), Vector3d.ZAxis, Point3d.Origin);
return true;
}
public override void OnTransform(Matrix3d tfm)
{
// To be able to cancel(Undo)
McUndoPoint undo = new McUndoPoint();
undo.Start();
// Get the coordinates of the base point and the rotation vector
this.TryModify();
this._pnt1 = this._pnt1.TransformBy(tfm);
this.TryModify();
this._vecStraightDirection = this._vecStraightDirection.TransformBy(tfm);
// We move the door only when it is closed if not - undo
if (_dStatus == status.closed) _vecDirectionClosed = _vecStraightDirection;
else
{
MessageBox.Show("Please transform only closed door (when its status = 0)");
undo.Undo();
}
undo.Stop();
}
//Define the custom properties of the object
[DisplayName("Height")]
[Description("Height of door")]
[Category("Door options")]
public double HDoor
{
get
{
return _h;
}
set
{
//Save Undo state and set the object status to "Changed"
if (!TryModify())
return;
_h = value;
}
}
[DisplayName("DScale")]
[Description("Door Scale")]
[Category("Door options")]
public double DScale
{
get
{
return _scale;
}
set
{
if (!TryModify())
return;
_scale = value;
}
}
[DisplayName("Door status")]
[Description("0-closed, 1-midle, 2-open")]
[Category("Door options")]
public status Stat
{
get
{
return _dStatus;
}
set
{
//Save Undo state and set the object status to "Changed"
if (!TryModify())
return;
// Change the rotation vector for each of the door states
switch (value)
{
case status.closed:
_vecStraightDirection = _vecDirectionClosed;
break;
case status.middle:
_vecStraightDirection = _vecDirectionClosed.Add(_vecDirectionClosed.GetPerpendicularVector().Negate() * 0.575) ;
break;
case status.open:
_vecStraightDirection = _vecDirectionClosed.GetPerpendicularVector()*-1;
break;
default:
break;
}
_dStatus = value;
}
}
// Create a grip for the base point of the object
public override bool GetGripPoints(GripPointsInfo info)
{
info.AppendGrip(new McSmartGrip<DoorPseudo3D_nc51>(_pnt1, (obj, g, offset) => { obj.TryModify(); obj._pnt1 += offset; }));
return true;
}
}
// TODO: There are many shortcomings in this code.
// Including failures when working with copying, moving objects and saving files, you can improve it if you want.
}
Опять почти все тоже самое, единственное, в версии 5.1 похоже поле по-другому обрабатывает перечисления и в окошке свойств объекта вместо слов closed/open, мы увидим значения перечисления: 0, 1, 2 это не очень наглядно, поэтому мы немножко изменили предупреждение об ошибке. Также у двери нет штриховки и есть лишнее свойство для масштаба (его кстати при желании можно реализовать и в классах для NC 8.5).
Получится примерно так:
4. МультиКукиш (Заключение)
Как вы помните из прошлой статьи, я и Нанософт никак не связаны, а значит могу себе позволить небольшой элемент критики. Разработчики заявляют о поддержке MultiCAD.NET API в AutoCAD и ZWCAD через определенную прослойку, есть даже статья про это.
Но похоже, это неприоритетное направление разработки. В прошлой статье я писал, что не смог протестировать эту функцию потому, что у меня на компьютере установлен AutoCAD 2017, а последняя размещенная на сайте разработчиков прослойка — «MultiCAD_AC_ZC_Enabler_2209_RU.zip» (которой уже 1.5 года), не поддерживает ничего старше AutoCAD 2016. Ставить ради такого удовольствия еще одну версию Автокада мне не захотелось.
В этот раз я решил попробовать другой вариант, скачал пробную версию ZWCAD+ 2015, опять-таки последнюю версию, которую поддерживает данная прослойка. Не знаю, может быть я «рукожоп», но ни эта библиотека, ни библиотека из прошлой статьи у меня так и не «взлетела» в ZWCAD. Поэтому если у кого-то получится запустить, и он поделится скриншотом буду признателен.
Ну а в остальном, надо сказать, что чем больше возишься с этим API тем больше втягиваешься и даже начинает понемногу нравится, заметно что API улучшается, часть членов классов API уже имеет вменяемое описание на русском, да и само по себе API становится более самодостаточным.
Думаю, что когда выйдет NanoCAD 9 (или как его там назовут) с поддержкой DWG 2018, то станет еще лучше (особенно если как обещали на форуме разработчики, за ним следом выйдет новый бесплатный Нанокад).
Так что хочется сказать всем участникам форума разработчиков Нанокад — спасибо за помощь, разработчикам — спасибо за то, что выложили NC 8.5, а всем читателям — спасибо за то, что осилили статью до конца.
P.S. Изначально у статьи в заголовке должна была быть другая картинка, но я решил, что она «не айс» и в итоге заменил её на политкорректных Кермита и Гровера (кстати часть картинок ведут на те видео, что я смог найти). А вот исходную картинку я решил спрятать в конце статьи под…
...спойлером