12 января, 2015

Определение зависимостей детали. Тройник. Урок 1 (часть 1).


Надеюсь, вы уже полностью прочитали справку СПДС GraphiCS и Руководство разработчика базы данных СПДС GraphiCS. Изучили информацию в интернете, но, тем не менее, остались вопросы по определению зависимостей между параметрическими объектами.
В рамках этого и возможно последующих уроков рассмотрим узкие места создания объектов СПДС и определения для них параметрических и геометрических зависимостей.

Как вы уже знаете из справки, в СПДС GraphiCS существуют два типа зависимостей:
Параметрические (устанавливаются SetParamConstraint) - можно связывать любые числовые и строковые параметры объектов;
Геометрические (сборочные), (SetGeomConstraint) - определяют взаимное положение деталей в пространстве.
Рассмотрим, что необходимо сделать, для того, что бы зависимости работали, как задумано, на примере тройника для трубопровода.
В дальнейшем, поняв принцип, вы сможете с легкостью создавать зависимости и для своих деталей.
Итак, по порядку.
Чертим и распознаем фронтальный вид детали.
Для вновь созданного объекта берем пустой шаблон скрипта.
Все параметры, которые мы будем связывать зависимостями с другими деталями, должны быть публичными (доступны извне для других объектов)
SVersion = 2;
ObjectDescription = "Тройник не по ГОСТ";

function ActHeader {
 NSelect= 1; //число запросов, у нас всего один (точка вставки)
 NPart= 0;  //параметры не выбираем, поэтому 0
 
 Public( 
  H, "Высота",
  L, "Линейная длина",
  D, "Основной диаметр",
  Dm, "Диаметр отвода",
  WP1, "Рабочая плоскость 1",
  WP2, "Рабочая плоскость 2",
  WP3, "Рабочая плоскость 3",
  
  rdE1,"hidden",//1 Диаметр для соединения  \
  rdE2,"hidden",//2 Диаметр для соединения   номера соответствуют WP
  rdE3,"hidden",//3 Диаметр для соединения  /
 );
 Protected(
  seted
 );
 Changeable(
  H, L, D, Dm        // задекларированы, как изменяемые, что бы можно было изменять из свойств Акад.
 );
 OnDlgBeforeSelectParam = 1; //Показывать диалог вставки перед динамическим выбором параметров
 ShowWhenSelPnt = 1;     //Отрисовывать объект во время вставки
 ContourOnLine = 0;     //Не регенирировать контур подавления после вставки
}
Инициализируем переменные, здесь не должно возникнуть сложностей, все как всегда, не обращайте внимания, что значения присвоены не для всех переменных, они будут заданы позже в функции OnMakeParameters:
function OnInitialization {
 if(seted == UnknownValue) {
  seted=1;
  rZOrder=100;
  H = 50;
  L = 100;
  D = 50;
  Dm = 40;
 }
}
Для геометрических зависимостей должны быть определены так называемые «рабочие плоскости» (WP1, WP2, WP…,WPnormal), естественно, как публичные параметры.
В справке об этом написано достаточно подробно, тем не менее, у пользователей этот момент вызывает некоторые сложности.
Для того, что бы мы могли двигаться дальше, немного вспомним тригонометрию и рассмотрим подробнее базовые понятия: ТОЧКУ, ВЕКТОР и ПЛОСКОСТЬ.
Точка.
В пространстве, имеет три координаты (x, y, z). Частный случай точка вставки (pntOrigin) начало локальных координат детали.
В общем случае:
 pntGrip1 = Point(1.0, 10.0, 10.0); //явно задаем точку в координатах чертежа
Этот способ задания координат применяется достаточно редко, т. к. деталь может быть в любом положении относительно модели, а точка привязана к координатам чертежа.
Обычно положение произвольной точки может быть задано относительно pntOrigin в направлении векторов vecDirection или vecPlane, смещением относительно точки вставки:
 pntGrip1 = pntOrigin+vecDirection*L;
Определение положения точки возможно не только в направлении основных векторов, вспоминаем теорему Пифагора.
 pntGrip2=pntOrigin+ vecDirection*L/2 +vecPlane*(H);

Кроме этого мы можем задать положение точки относительно произвольного вектора, который может не находиться в одной плоскости с vecDirection и vecPlane.
 pntGrip1= pntOrigin+vecUser*140;
Вектор.
Представляет собой направление в пространстве относительно точки вставки (pntOrigin) или плоскости. Если явно не задана длина, то имеет vecLen (длину) равную единице.
В общем случае:
 vec1 = Vector(1.0, 1.0, 0); // вектор длиной 1, начало из точки pntOrigin, конец координаты (1.0, 1.0, 0) в координатах модели.
В детали СПДС GraphiCS есть два определяющих вектора направления vecDirection и vecPlane.
vecDirection – совпадает с направлением вставки детали (ось ОХ),
vecPlane – ортогонален vecDirection (условно можно принять его аналогом оси Z в 3D пространстве),
т.к. мы строим деталь в проекциях, то порой нужен третий вектор аналог в 3D оси Y, он получается векторным произведением vecDirection и vecPlane.
 vecNormal=getLocalNormal(vecDirection,vecPlane);


Важно понимать, что от того, какой из видов мы выберем фронтальным зависит направление векторов vecDirection и vecPlane.
Плоскость.
Собственно на нее и будут устанавливаться геометрические зависимости.
Определяется точкой и вектором нормали построенным из этой точки.
В общем случае:
 WP1 = Plane( pntBase, vecUser ); 
 //pntBase- Это базовая точка для плоскости WP1
 // vecUser - Это вектор нормали для плоскости WP1
Для рисунка ниже:
 vecUser= - vecDirection, 
 pntBase= pntOrigin-vecDirection* /расстояние до точки/;
Для работы с векторами, точками и плоскостями существует еще ряд функций, которые мы рассмотрим в следующих статьях.

Точки мы задавать научились, с векторами и плоскостью разобрались, теперь не составит труда задать для нашей детали три рабочих плоскости, на которые будем устанавливать геометрические зависимости:
 WP1 = Plane(pntOrigin, -vecDirection);
 WP2 = Plane(pntOrigin+vecDirection*L, vecDirection);
 WP3 = Plane(pntOrigin+vecPlane*(H)+vecDirection*(L/2), vecPlane);
Опишем функцию OnMakeParameters:
function OnMakeParameters {
 strTheType = "vaPipe";  //тип 
 strTheSubType = "Round"; //подтип
 strTheName = "Tee";    //имя объекта, пока просто тройник.
 rdE1=D;          // диаметр тройника у плоскости WP1
 rdE2=D;          // диаметр тройника у плоскости WP2
 rdE3=Dm;         // диаметр тройника у плоскости WP3
 WP1 = Plane(pntOrigin, -vecDirection);
 WP2 = Plane(pntOrigin+vecDirection*(L), vecDirection);
 WP3 = Plane(pntOrigin+vecPlane*(H)+vecDirection*(L/2), vecPlane);
}
Небольшое отступление: strTheType – тип детали, влияет на логику ее поведения. В случае если типы деталей совпадают и определены зависимости, то возможна вставка детали в «цепочку объектов» этого типа.
rdE1=D, rdE2=D, rdE3=Dm - заданы для удобства определения параметрических зависимостей при коннекте. Каждому номеру плоскости сопоставлен свой номер диаметра.
Ручка у детали будет одна:
function SetGripPoint {
 NGrip = 1;
 pntGrip0 = pntOrigin;
}
За нее же будем и перемещать деталь
function OnMoveGripPoint {
 pntOrigin=pntGrip0;
}
С формой то же мудрить не будем, пока воспользуемся стандартной:
function OnDialog {
 UniDialog( 
  VFLD, 
   H,"Высота", 
   L,"Линейная длина", 
   D,"Основной диаметр", 
   Dm,"Диаметр отвода", 
  TVIDS,lViewType,"All",
  VIEW,"Vids"
 );
}
Функцию обработки параметров, при изменении их извне опишем пока только для размеров детали изменяемых из свойств AutoCAD. Дальше по тексту статьи, мы еще вернемся к этому коду.
function OnChangeParameters {
 H = new.H;  //  эти праметры меняются
 L = new.L;  // из свойств 
 D = new.D;  // AUTOCAD
 Dm = new.Dm;  //поэтому никаких условий, просто изменяем и все.
// ......
}
Сбрасываем раннее установленные зависимости, и в случае необходимости выводим подсказку, по присоединяемой детали.
function BeforeConnect {
 ResetLastConstraint();
 strPromt = "Выберите объект, трубу или деталь трубопровода."; //подсказка по выбору объектов
}
Теперь разберем, как устанавливаются зависимости на детали, в функциии OnConnect.
Для написания кода по установке зависимостей мы не будем пользоваться мастером по двум причинам, во-первых, нам необходимо понимание процесса изнутри, во-вторых, мастер генерирует не совсем корректный скрипт, в последующем его все равно необходим исправлять руками.
Рассмотрим, что происходит с деталью при вставке на чертеж и присоединении ее к другой детали:
Определяем, какой номер запроса для вставки (используется, если деталь присоединяется к нескольким объектам или в процессе вставки выполняется выбор параметров), в нашем случае присоединение происходит к одной детали и параметры никакие не выбираем, поэтому rPart=0, но эту проверку делать необходимо всегда, что бы избежать проблем в дальнейшем.
Затем убеждаемся, возможно ли присоединение к этой детали (в нашем случае это все круглые трубы или детали трубопроводов).
В случае если вышеперечисленные условия выполнены, мы сбрасываем все зависимости, установленные раннее (во избежание зацикливания).
Находим ближайшую курсору рабочую плоскость, к которой будет выполнено присоединение.
Убеждаемся, что найдена хоть одна из рабочих плоскостей от 1 до 4 (можно больше, но пока этого достаточно).
И наконец, устанавливаем зависимости.
В нашем случае мы устанавливаем для тройника геометрическую однонаправленную зависимость плоскости WP1 к ближайшей найденной плоскости и параметрическую двунаправленную зависимость диаметра rdE1 к диаметру детали на чертеже.
Отключаем выбор вектора направления для детали, поскольку положение детали задано геометрической зависимостью.
В конце функции сообщаем объекту, от которого устанавливаются зависимости, что зависимости установлены.
function OnConnect {
 if(rPart == 0){ //номер запроса для вставки 
  if((obj.strTheType == "Tube" && obj.strTheSubType == "Round") || (obj.strTheType=="vaPipe")){ //проверка, что это круглые трубы или детали трубопроводов
   ResetAllConstraint (); //сбрасываем все зависимости перед вставкой объекта в цепь. Исключает циклическую зависимость при вставке между деталями.
   iWPindx=findNearest(pntOrigin, obj.WP,1,4); //находим номер ближайшей рабочей плоскости относительно точки встаки
   if (iWPindx==1 || iWPindx==2 || iWPindx==3 || iWPindx==4){ //Убеждаемся, что найдена хоть одна из рабочих плоскостей от 1 до 4
    SetGeomConstraint(INSERT,CONTRDIRECT,obj,WP1,obj.WP[iWPindx],0,1); //устанавливаем для тройника геометрическую двунаправленную зависимость  плоскости WP1 к ближайшей найденной плоскости 
    SetParamConstraint(rdE1,obj,EXPR,"obj.rdE"+iWPindx,TRUE); //устанавливаем для тройника параметрическую двунаправленную зависимость диаметра rdE1 к диаметру детали на чертеже
    NoVectorSelect = 1; //Отключаем выбор вектора направления для детали, поскольку деталь уже присоединена.
    Handled = OBJ_HANDLED; //Сообщаем объекту от которого устанавливаются зависимости, что зависимости установлены.
   }
  }
 }
}
Зависимости нами определены, но при вставке детали на чертеж, несмотря на то, что, присоединяемый объект подсвечивается, наша деталь никак не реагирует на изменения родительского объекта, однако, объекты из базы реагируют на изменения положения и размеров нашего тройника.
В чем же дело?
Все просто, несмотря на то, что зависимости заданы, в скрипте детали не определена реакция на изменения присоединенного объекта в функции OnChangeParameters.
Еще раз вернемся к этой функции.
Рассмотрим, какие изменения нам необходимо обработать:
Очевидно, что у нас изменяются rdE1, rdE2 основной диаметр связан с D, rdE3 диаметр отвода связан с Dm.
Проверим по очереди эти параметры на изменение и присвоим им новые значения.
Затем, сообщим родительскому объекту, от которого берутся параметры, что изменения выполнены и выполним проверку, все ли параметры соответствуют новым (это необходимо, в случае если значения какого-либо объекта берутся из таблицы параметров или существуют другие ограничения на изменения.)
//...
// проверка изменения размеров от другой детали и обновление их если истина
 if (rdE1!=new.rdE1){ //проверка на изменение диаметра присоединенного к WP1
  D=new.rdE1;    //если изменился обновляем D
 } else if (rdE2!=new.rdE2){ //проверка на изменение диаметра присоединенного к WP2
  D=new.rdE2;   //если изменился обновляем D
 } else if (rdE3!=new.rdE3){ //проверка на изменение диаметра присоединенного к WP3
  Dm=new.rdE3;  //если изменился обновляем Dm
 }
 Handled = OBJ_HANDLED; //Сообщаем объекту, изменявшему параметры, что необходимые значения параметров установлены.
//проверяем, что у нас обновились все диаметры
 if (D!=new.D || Dm!=new.Dm ) { //если обновились не все
  Handled = OBJ_WARNING; //сообщаем родительскому объекту, что не удалось установить размеры точно.
 }
//...
В случае если родительский объект изменил свое положение, нам необходимо обработать это изменение. Очевидно, что оно определяется положением плоскостей WP.
Для каждой плоскости выполняем проверку, установлена ли на нее зависимость и изменилась ли она (IsFixedParam).
В случае если зависимость изменилась, из плоскости извлекаем вектор нормали (Vector(new.WP..)), для плоскостей WP1 (WP2), нашей детали, это будет vecDirection (-vecDirection) соответственно.
Затем определяем новое положение точки вставки.
Так как vecDirection не является вектором нормали к плоскости WP3, то рассмотрим его нахождение при изменении WP3 несколько подробнее.
С помощью функции getLocalNormal, зная положение двух векторов, всегда можно определить положение третьего. Получим третий вектор vecNormal из  векторов vecDirection и vecPlane (до перемещения детали).
 vecNormal = getLocalNormal(vecDirection,vecPlane);
Далее для нового положения WP3 определяем вектор нормали, в этом случае это будет vecPlane.
 vecPlane = Vector(new.WP3);
Для определения нового положения детали в пространстве нам необходимо вычислить новое направление vecDirection.
 vecDirection = getLocalNormal(vecPlane,vecNormal);
В конце определяем новое положение точки вставки:
 pntOrigin=Point(new.WP3)-vecDirection*L/2-vecPlane*H;
Теперь мы можем полностью записать функцию OnChangeParameters
function OnChangeParameters {
 H = new.H;  //  эти праметры меняются
 L = new.L;  // из свойств 
 D = new.D;  // AUTOCAD
 Dm = new.Dm;  //поэтому никаких условий, просто изменяем и все.
//------------------------------------------------------------ 
//параметры ниже изменяются от другого объекта, поэтому проверяем, какой параметр изменился
//и изменяем связанный параметр детали

// проверка изменения размеров от другой детали и обновление их если истина
 if (rdE1!=new.rdE1){ //проверка на изменение диаметра присоединенного к WP1
  D=new.rdE1;    //если изменился обновляем D
 } else if (rdE2!=new.rdE2){ //проверка на изменение диаметра присоединенного к WP2
  D=new.rdE2;   //если изменился обновляем D
 } else if (rdE3!=new.rdE3){ //проверка на изменение диаметра присоединенного к WP3
  Dm=new.rdE3;  //если изменился обновляем Dm
 }
 Handled = OBJ_HANDLED; //Сообщаем объекту, изменявшему параметры, что необходимые значения параметров установлены.
//проверяем, что у нас обновились все диаметры
 if (D!=new.D || Dm!=new.Dm ) { //если обновились не все
  Handled = OBJ_WARNING; //сообщаем родительскому объекту, что не удалось установить размеры точно.
 }
 //  зависимости по изменению положения родительского объекта.
 if(IsFixedParam(WP1)){ //установлена ли зависимость и изменилась ли плоскость WP1?
  vecDirection=-Vector(new.WP1); //определяем новое положение vecDirection
  pntOrigin=Point(new.WP1);    //определяем новое положение точки вставки
 }
 if(IsFixedParam(WP2)){ //установлена ли зависимость и изменилась ли плоскость WP2?
  vecDirection=Vector(new.WP2);        //определяем новое положение vecDirection
  pntOrigin=Point(new.WP2)-vecDirection*L;  //определяем новое положение точки вставки
 }
 if(IsFixedParam(WP3)){ //установлена ли зависимость и изменилась ли плоскость WP3?
  vecNormal = getLocalNormal(vecDirection,vecPlane); //находим vecNormal
  vecPlane = Vector(new.WP3);             //получаем вектор нормали к плоскости WP3
  vecDirection = getLocalNormal(vecPlane,vecNormal); //вычисляем вектор направления вставки детали
  pntOrigin=Point(new.WP3)-vecDirection*L/2-vecPlane*H; //определяем новое положение точки вставки
 }
}
Конечно, созданный тройник не подходит для применения в рабочих проектах. В  этой статье я ставил задачу всего лишь рассказать и показать на максимально упрощенном примере, каким образом можно связывать объекты СПДС зависимостями.
Учебные материалы по статье.

15 комментариев :

  1. Еще пример установки зависимостей и создания формы условий отработки соединения http://www.spds.ru/info/examples/messagebox.html

    ОтветитьУдалить
  2. Андрей, скорее вы перескочили через уровень! :-)
    Ну или параллельная ветка развития

    ОтветитьУдалить
  3. Часть рутинной работы по созданию зависимостей в скрипте можно автоматизировать:
    http://www.youtube.com/watch?v=HEJc506Waz4
    Легенда:
    0:08 Вставляем «Родительский» объект
    0:15 Выбираем «Дочерний» объект
    0:24 Открывается диалог «Управления зависимостями». Так как диалог появился в процессе редактирования скрипта «Родительский» и «Дочерний» объекты уже определены.
    0:30 Назначаем «Параметрические» зависимости, выбирая их в дереве построения «модели объекта»
    0:45 Последовательно задаем все необходимые параметры для зависимых объектов. Если необходимо, указываем, что зависимость «Двуунаправленная»
    0:46 Переходим к определению сборочных зависомостей
    0:50 Добавляем «Сборочную» зависимость и указываем плоскости на чертеже.
    0:60 Задаем зависимости через параметры модели
    1:12 Прямо в диалоге можно отредактировать формулу или заменить параметры
    1:16 Нажимаем “Ok” и переходим к автоматическому формированию скрипта.
    1:18 Можем отредактировать название объектов и/или расширить список применения объектов
    1:21 Автоматически сформированная функция «onConnect»
    1:25 работа «Мастера зависимостей» завершена. В «Скрипт» вставлена нужная функция. При необходимости её можно отредактировать.
    1:28 Показана работа «модифицированного» с помощью мастера объектов скрипта описания поведения модели в контексте сборки.

    ОтветитьУдалить
    Ответы
    1. мастер зависимостей, конечно отчасти избавляет от рутины, но:
      1. очень часто скрипт созданный в нем не проходит проверку
      2. пользователь не понимает, что мастер делает на самом деле (думаю, что ты со мной согласишься, что шесть-семь строк кода в функции OnConnect проще набросать руками, чем заводиться с мастером)
      3. результат работы мастера надо еще обработать напильником, что бы все работало как надо.
      имхо мастера для обучения пользователей это зло.

      Удалить
    2. Все выше сказанное справедливо для человека в совершенстве владеющего всем инструментарием. Для большинства пользователей, на начальном этапе, "Мастер зависимостей" - существенно ускоряет работу. Плюс к этому он дает базовые знания и понимание что именно должно получится и из чего состоит OnConnect :-)

      Удалить
    3. если бы так было на самом деле, то... по аналогии, в начальной школе дети не учили бы арифметику.. им с первого класса давали калькуляторы и вперед считайте, а самые продвинутые из них со временем учились бы это делать без калькулятора-мастера.
      В самом деле веришь, что для неподготовленного пользователя, код который генерируют мастера "дает базовые понятия и понимание, что в коде за что отвечает"???
      На мой взгляд для неподготовленного пользователя это китайская грамота, справку читают через раз.
      Что удивительно, ту же справку в интернете , в виде статей читают с удовольствием.
      Все это к тому, что если хоть один пользователь пройдет этот урок от начала до конца, то он получит понимание того, как работает скрипт и происходит взаимодействие параметров.
      Я не ставлю задачу научить пользователей делать что-то конкретное, моя задача немного проще научить делать, что требуется пользователю.
      PS Возможно ли мастером сделать коннект к разным WP, а выбор объектов по условию для коннекта, а коннект через промежуточный объект???
      PPS простые пользователи воспользуются им раз, удивятся, что получилось немного не то, как довести до ума знаний нет, и забудут,
      продвинутые возможно однажды воспользуются, пожалеют о потраченном времени и то же забудут.
      PPPS имхо, мастер, ошибочный маркетинговый ход предназначенный для популяризации СПДС, показать как легко и классно можно создавать объекты и связывать их зависимостями. Ошибочный потому как в том виде в котором находятся мастера сейчас, это только отталкивает потенциальных пользователей.

      Удалить
    4. Если серьезно, то про создание детали при помощи мастеров, в сети существует некоторое количество уроков, как видео, так и текст с картинками (думаю, что к большинству из них ты имеешь прямое отношение), а вот про то как написать скрипт детали руками (осмысленно), и что за что отвечает, есть немного в справке, больше ничего нигде нет. Пытаюсь в меру сил закрыть этот пробел ))))
      PS Насчет мастеров.. если у C и nano Soft дойдут руки и найдутся специалисты довести существующие мастера хотя бы до уровня мастера который распознает графику, тогда конечно, руками писать будет почти незачем.

      Удалить
    5. Наткнулся на обсуждение, и соглашусь с Андреем: мастер скриптов непонятен, и он отпугивает, писать код ручками намного проще.

      Удалить
    6. п.с.: Андрей, твои статьи всегда на высоком уровне, и отличаются какой-то академичностью что ли...респект!

      Удалить
    7. Спасибо.
      Задумок много , времени совсем нет на их оформление )))

      Удалить
  4. Андрей, добрый день!
    А как сделать чтобы параметр в функции OnAddObject принимал значение в зависимости от того куда присоединяется объект

    ОтветитьУдалить
    Ответы
    1. Зачем делать через это место??
      При коннекте проверяем условие и если оно удовлетворяет передаем параметр дочернему. Или меняем у родительского.
      В твоем случае скрыть линию, значит передаем hidden=0
      При отсоединении в Onchange по IsFixed проверяем и показываем или скрываем в зависимости от условий.

      Удалить
  5. Большое спасибо за статью!

    ОтветитьУдалить