ModPlus Blog

Звезда не активнаЗвезда не активнаЗвезда не активнаЗвезда не активнаЗвезда не активна

Как оказалось, в Revit API нет методов, позволяющих разделить кривую Curve на части заданной длины. Именно такая задача недавно у меня при разработке плагина. В AutoCAD API есть хороший метод GetPointAtDist, который позволяет решить данную задачу, но в Revit API пришлось решать её самому.

В моем случае мне нужно было получить точки деления кривой на части заданной длины. Для решения задачи нам понадобятся параметры кривой. Получаем начальный и конечный параметр и через интерполяцию находим шаг параметризации, равнозначный длине части. Далее просто делаем цикл, прибавляя шаг параметризации, до тех пор, пока сумма шагов не достигнет конечного параметра. Если шаг по длине не делит кривую на равнозначные части (в конце остается участок меньшей длины, чем заданная длина части), берем ещё конечную точку. Если заданная длина части вдруг оказалась меньше длины кривой, берем конечные точки:

/// <summary>
/// Возвращает точки деления кривой на части указанной длины
/// </summary>
/// <param name="curve">Кривая</param>
/// <param name="step">Шаг деления</param>
public static IEnumerable<XYZ> Divide(this Curve curve, double step)
{
    if (curve.Length > step)
    {
        var startParameter = curve.GetEndParameter(0);
        var endParameter = curve.GetEndParameter(1);
        var parameterStep = (endParameter - startParameter) / (curve.Length / step);
        var parameterSum = startParameter;
        
        do
        {
            yield return curve.Evaluate(parameterSum, false);
            parameterSum += parameterStep;
        }
        while (parameterSum < endParameter);
        
        if (Math.Abs(parameterSum - endParameter) > 0.0001)
            yield return curve.GetEndPoint(1);
    }
    else
    {
        yield return curve.GetEndPoint(0);
        yield return curve.GetEndPoint(1);
    }
}

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

Звезда не активнаЗвезда не активнаЗвезда не активнаЗвезда не активнаЗвезда не активна

В данной небольшой заметке я не открою Америку и не расскажу что-то уникальное. Но заметку я решил написать, так как не сразу можно догадаться о причинах такого поведения.

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

Tags: , ,

Рейтинг:  5 / 5

Звезда активнаЗвезда активнаЗвезда активнаЗвезда активнаЗвезда активна

Стояла у меня задача, для выполнения которой требовалось создание собственного типа для системного семейства Текст (Текстовое примечание).

Немного поискав информацию на просторах интернета, я наткнулся на данный пример, объясняющий, что для создания типа требуется создавать дубликат существующего типа. Вот только в примере совсем не уделено внимание тому, как и какие параметры задавать. Особенно остро стоит вопрос задания значения для параметра «Цвет».

Почитав в теме комментарии, я понял, что вопрос волновал многих и, к моему сожалению, в комментариях нет правильного ответа. Я решил исправить эту маленькую несправедливость и сделал небольшой пример создания типа для системного семейства Текст. Все пояснения я оставил в виде комментариев к коду.

Вот и сам пример:

Tags: ,

Рейтинг:  2 / 5

Звезда активнаЗвезда активнаЗвезда не активнаЗвезда не активнаЗвезда не активна

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

Но есть решение куда проще в реализации и понятнее. И поможет нам в этом скалярное произведение векторов (DotProduct):

Если скалярное произведение двух единичных векторов по модулю равно 1, то эти два вектора коллинеарны (параллельны друг другу).

Если произведение будет 1 – вектора направлены в одну сторону, а если -1 – в противоположные. Но в нашей задаче это не важно и нас интересует только модуль произведения. Из первого утверждения получаем метод проверки параллельности:

/// <summary>
/// Проверка параллельности двух векторов
/// </summary>
/// <param name="vector">Первый вектор</param>
/// <param name="checkedVector">Второй вектор</param>
/// <param name="tolerance">Допуск расстояния при проверке</param>
public static bool IsParallelTo(this XYZ vector, XYZ checkedVector, double tolerance = 0.0001)
{
    return Math.Abs(Math.Abs(vector.DotProduct(checkedVector)) - 1.0) < tolerance;
}

/// <summary>
/// Проверка параллельности двух отрезков
/// </summary>
/// <param name="line">Первый отрезок</param>
/// <param name="checkedLine">Второй отрезок</param>
/// <param name="tolerance">Допуск расстояния при проверке</param>
public static bool IsParallelTo(this Line line, Line checkedLine, double tolerance = 0.0001)
{
    return line.Direction.IsParallelTo(checkedLine.Direction, tolerance);
}

Мы уже решили половину задачи. А полное решение искомой задачи звучит так:

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

Причем это утверждение будет работать и в 3D пространстве, а не только на плоскости.

Ну и конечно код этого решения в виде метода расширения для отрезка:

/// <summary>
/// Лежат ли текущий и проверяемый отрезок на одной прямой. 
/// </summary>
/// <param name="firstLine">Текущий отрезок</param>
/// <param name="secondLine">Проверяемый отрезок</param>
/// <param name="tolerance">Допуск на сравнение чисел</param>
public static bool IsLieOnSameStraightLine(this Line firstLine, Line secondLine, double tolerance = 0.0001)
{
    // Если два отрезка не параллельны, то и дальнейшая проверка не требуется
    if (!firstLine.IsParallelTo(secondLine))
        return false;

    // Можно получить все концевые точки методом GetEndPoint(int), но это приведет к раздутию
    // кода и его некрасивости. Поэтому используем метод Tessellate(), который для отрезков вернет
    // те же самые две концевые точки
    var firstLinePoints = firstLine.Tessellate();
    var secondLinePoints = secondLine.Tessellate();

    // Вектор первого отрезка будем использовать как эталон проверки.
    // Свойство Direction всегда содержит единичный вектор
    var fv = firstLine.Direction;

    // Нам требуется проверять попарно концевые точки первого отрезка с концевыми точками второго.
    // Это удобно сделать двумя итерациями
    foreach (var firstLinePoint in firstLinePoints)
    {
        foreach (var secondLinePoint in secondLinePoints)
        {
            // Если два отрезка будут будут иметь общую концевую точку, то проверка сработает не верно,
            // так как произведение векторов даст 0.0. Такие пары просто пропускаем
            if (Math.Abs(firstLinePoint.DistanceTo(secondLinePoint)) < tolerance)
                continue;

            // Не важно из какой какую точку отнимать. Главное, привести к единичному вектору
            var v = (secondLinePoint - firstLinePoint).Normalize();

            // Если вектора не параллельны, то и отрезки не лежат на одной прямой
            if (!fv.IsParallelTo(v))
                return false;
        }
    }

    // Если в предыдущих итерациях мы не вышли из метода, значит два отрезка лежат на одной прямой
    return true;
}

Не забывайте, что в Revit есть отрезки, а есть лучи, и оба они представлены типом Line. Данный код не учитывает таких различий

© 2018-2024 modplus.org