Доступ к списку файлов внешних ссылок 3D модели

Автор andno, 19.01.06, 15:59:36

« назад - далее »

0 Пользователи и 2 гостей просматривают эту тему.

andno

Приветствую всех!
Стоит задача получения списка всех файлов внешних ссылок 3D модели.
Это тот список, который доступен после открытия файла a3d через опцию оновного меню Компаса:
Файл -> Свойства-> Закладка "Внешние ссылки".

Пытался добраться до него через интерфейс ksPart.
Однако список получается не полным. Перечисляются только файлы физически прописанные в данном файле a3d.
При открытии же свойств через меню, Компас дает общее перечисление файлов, которые записаны как в файле a3d, так и в файлах внешних ссылок, которые записаны файле a3d.
При этом у меня создается впечатление, что при открытии в меню свойств, Компас не получает этот список заново, а просто выдает уже готовый список (операция проходит очень быстро). Мне кажется, что этот список создается во время открытия файла a3d и сохраняется вплоть до его закрытия.

Вопрос! Как можно добраться до этого списка?

IronMaxxx

#1
Как добраться до списка не знаю, но можно создать точно такой же свой, своими силами.
При открытом активном документе 3D (сборке) нужно запустить цикл от 0 до количества деталей в сборке и для каждого компонента определять имя файла, т.е. (пример на Delphi) :

var iPart : ksPart;
     doc3D : ksDocument3D;
     kompas : KompasObject;
     i : integer;
     PColl : ksPartCollection;
     PathList : TStringList;
begin
   doc3D := ksDocument3D(kompas.ActiveDocument3D);
   if (doc3D.IsDetail) then exit;

   PColl := ksPartCollection(doc3D.PartCollection(true));
   PathList := TStringList.Create;

   for i := 0 to PColl.GetCount-1 do
      begin
         iPart := ksPart(doc3D.GetPart(i));
         PathList.Add(iPart.fileName);
                           // если среди деталей есть подсборки, для определения их имен нужно
                           // рекурсивно вызвать эту же процедуру
                           // и передать в нее ссылку на файл, в котором хранится подсборка
                           // таким образом Вы определите все имена файлов (учитывая вложенность деталей и подсборок)
      end;

   PathList.Sort();
      ...  // нужные действия с PathList
   PathList.Free;
end;

Свойство ksPart::fileName вернет полный путь к файлу сборки или детали, и путь к библиотеке моделей для стандартного компонента.
Необязательно имена файлов заносить в список строк. Если Вы желаете отсортировать список по типу документа (сборка, деталь, библиотечный элемент), можно объявить структуру данных, например

type TPathParam = record
   path : WideString;
   Detail : boolean;
end;

и динамический массив:

type TPathArray = array of TPathParam;

ну или массив указателей (быстрее работать будет для больших сборок):

type PPathArray = ^TPathArray;
       TPathArray = array [1..1000000] of TPathParam;

В TPathParam::Detail нужно будет заносить свойство ksPart::IsDetail, по которому потом и сортировать массив. Получится точно такой список, как при выполнении команды "Файл -> Свойства->Внешние ссылки".

Gek

Ironmaxxx, а почему массив указателей будет работать быстрее? И насколько ощутимо быстрее?

andno

Ironmaxxx-су публичное спасибо за совет!!!
Способ сработал.

Сам я его думал, но не решался пробовать из-за боязни, что повторное открытие файлов ссылок, будет занимать много времени (основная сборка открывается около 1 минуты). Оказалось, что после открытия первого a3d, последующие открываются гораздо шустрее. В общем вся сборка (294 объекта) обработалась за 2 с лишком минуты.

Андрей Носов (NIC: andno)

P.S.
Вместо
     iPart := ksPart(doc3D.GetPart(i));
следует использовать
     iPart := ksPart(doc3D.GetByIndex(i));

IronMaxxx

Цитата: andno от 20.01.06, 17:30:57
Ironmaxxx-су публичное спасибо за совет!!!
Способ сработал.

Всегда рад помочь!  :fr:

Цитата: andno от 20.01.06, 17:30:57
P.S.
Вместо
     iPart := ksPart(doc3D.GetPart(i));
следует использовать
     iPart := ksPart(doc3D.GetByIndex(i));

Вот здесь Вы немного заблуждаетесь. В интерфейсе 3D-документа нет метода GetByIndex (т.е. ksDocument3D::GetByIndex() - неверная запись), такой метод есть в ksPartCollection. Так как мы получаем указатель на колекцию объектов перед началом цикла, то следущие строки равносильны:

iPart := ksPart(doc3D.GetPart(i));      ==        iPart := ksPart(PColl.GetByIndex(i));

Т.е. метод ksDocument3D::GetPart и ksPartCollection::GetByIndex при однаковых индексах вернут указатель на один и тот же объект сборки.
Вообще в данном случае можно обойтись без использования ksPartCollection, т.е. не определять заранее количество деталей в сборке. При этом следует организовать цикл с передусловием, и перед каждым шагом проверять iPart на nil.

       ...
i := 0;
iPart := ksPart(doc3D.GetPart(i));
while (iPart <> nil) do
begin
      ...
   i := i + 1;
   iPart := ksPart(doc3D.GetPart(i));
end;

И еще... Относительно боязни больших затрат времени на окрытие файлов по ссылках. Да тут нечего бояться! Посмотрите код: где-нибудь окрывается какой-либо файл, кроме главного файла сборки? Нет... метод ksDocument3D::GetPart не окрывет документ, а лишь дает к нему доступ. Поэтому время затрачивается только на собстенно считывание путей (не может занимать много) и на сортировку (если таковая есть, конечно).

IronMaxxx

Цитата: Gek от 20.01.06, 16:15:03
Ironmaxxx, а почему массив указателей будет работать быстрее? И насколько ощутимо быстрее?

Потому что указатель - это переменная, которая указывает на начало области оперативной памяти, где хранятся данные. Как правило, указатель занимает в памяти 4 байта.
Теперь посмотрим на структуру (запись), что используется в примере:
type TPathParam = record
   path : WideString;
   Detail : boolean;
end;

Переменная типа boolean занимает 1 байт, в строке типа WideString приходится по 2 байта на символ. Очень редко бывает, что путь к файлу состоит менее чем из 10 символов, случается, 30...40 и даже больше. Ну, возьмем в среднем 20 символов, которым будет отвечать 40 байт. Т.е. в среднем структура TPathParam будет занимать в памяти 41 байт.
Как Вы думаете, Gek, что будет быстрее сортироваться: массив записей TPathParam (по 41 байту) или массив указателей на записи (по 4 байта)?
В принципе, в данном случае для небольших сборок  разница вряд ли будет ощутимой, но что если в записи не 2 переменные, а десятки, или же массив состоит из объектов какого-нибудь класса? Тут с указателями не просто работать быстрее, тут без них не обойтись.
Хотя, если массив ссылок не сортировать, то необходимость в указателях отпадает.

IronMaxxx

Я вот тут подумал, в нашем случае можно обойтись без записей, массивов и указателей. Нужно создать три объекта TStringList (по одному для сборок, деталей и библиотечных элементов), для всех установить значение свойства TStringList::Sorted в true, и в каждый заносить ссылки для моделей соответсвующего типа. Класс TStringList сам позаботится о том, чтобы вставлять каждый новый элемент в соответсвующую позицию списка. А по завершению рекурсии все три списка можно объединить в один.
Не думаю, что этот алгоритм будет работать намного дольше, зато никакой возни с указателями и записями...

andno

Цитата: IronMaxxx от 20.01.06, 23:57:14
Я вот тут подумал, в нашем случае можно обойтись без записей, массивов и указателей. Нужно создать три объекта TStringList (по одному для сборок, деталей и библиотечных элементов), для всех установить значение свойства TStringList::Sorted в true, и в каждый заносить ссылки для моделей соответсвующего типа. Класс TStringList сам позаботится о том, чтобы вставлять каждый новый элемент в соответсвующую позицию списка. А по завершению рекурсии все три списка можно объединить в один.
Не думаю, что этот алгоритм будет работать намного дольше, зато никакой возни с указателями и записями...

На самом деле я так и сделал. Ниже идет Дельфевый рабочий код. Если у кого не заработает, я съем свою шляпу!
Да изящьного сдесь мало, зато работает.

А вот как быть с 2D? Для них такой способ не работает. А у них тоже есть внешние ссылки. Может кто знает все-таки - как добраться до готового списка? Или как этот список можно автоматически записать на диск (там есть кнопка в меню)?


var
  Form1: TForm1;
  Kompas: KompasObject;
  pList : array [1..3] of TStringList;
  nst: array[1..3] of string = ('Детали','Сборки','Библиотеки');

// Нажатие кнопки выбора файла
procedure TForm1.ComboEdit1ButtonClick(Sender: TObject);
var
   idx,il: integer;
begin
    if (OpenDialog3D.Execute) then // Выбрать файл в диалоге
            Memo1.Lines.Clear();        // Сюда выведем окончательный список файлов
            pList[1] := TStringList.Create;
            pList[2] := TStringList.Create;
            pList[3] := TStringList.Create;
            pList[1].Sorted := True;
            pList[2].Sorted := True;
            pList[3].Sorted := True;

            if Kompas = nil then begin
                Kompas := KompasObject(CreateOleObject('Kompas.Application.5'));
            end;
            ComboEdit1.Text := OpenDialog3D.FileName;
            Form1.Update;
            if Form2.TreeView1.Items.Count <> 0 then Form2.TreeView1.Items.Clear; // В другом окне построим дерево проекта
            GetListModelFiles(OpenDialog3D.FileName, Form2.TreeView1.Items.Add(nil,OpenDialog3D.FileName)); // Рекурсивно по всем файлам

            // Соединить в единый список
            for il := 1 to 3 do begin
                //Memo1.Lines.Add('');
                Memo1.Lines.Add(nst[il]);
                for idx := 0 to TStringList(pList[il]).Count-1 do begin
                    Memo1.Lines.Add(TStringList(pList[il]).Strings[idx]);
                end;
            end;
            StatusBar1.Panels[0].Text := IntToStr(Memo1.Lines.Count);
            pList[1].Free;
            pList[2].Free;
            pList[3].Free;
end;


// Рекурсивная процедура
procedure TForm1.GetListModelFiles(PathModelFile: string; parentNode: TTreeNode);
var
  doc_3D : ksDocument3D;
  Parts : ksPartCollection;
  pCnt, idx,il: integer;
  st: string;
begin
    if Kompas <> nil then begin
        doc_3D := ksDocument3D(Kompas.Document3D);
        if doc_3D <> nil then begin
            StatusBar1.Panels[1].Text := 'Open...'+ExtractFileName(PathModelFile); // Просто для отслеживания фазы обработки
            Form1.Update;
            if doc_3D.Open(PathModelFile, false) then begin
                StatusBar1.Panels[1].Text := PathModelFile; // Просто для отслеживания фазы обработки
                Form1.Update;
                Parts := ksPartCollection(doc_3D.PartCollection(True));
                Parts.refresh();
                if Parts <> nil then begin
                    pCnt:= Parts.GetCount;
                    StatusBar1.Panels.Items[0].Text := IntToStr(pCnt);
                    for idx := 0 to pCnt-1 do begin
                        st := ksPart(Parts.GetByIndex(idx)).fileName;
                        if ksPart(Parts.GetByIndex(idx)).standardComponent then begin
                            Delete(st, Pos('|', st), Length(st)-Pos('|',st)+1); // оставить только путь и имя библиотеки
                            //TStringList(pList[3]).Sort();
                            if not TStringList(pList[3]).Find(st,il) then // добавить если имя уникально
                                TStringList(pList[3]).Add(st);
                            Form2.TreeView1.Items.AddChildObject(parentNode,st, nil); // В дерево как ребенка для текущего файла
                        end
                        else  begin
                            if ExtractFileExt(st) = '.a3d' then begin
                                if not TStringList(pList[2]).Find(st,il) then // добавить если имя уникально
                                    TStringList(pList[2]).Add(st);
                                GetListModelFiles(st, Form2.TreeView1.Items.AddChild(parentNode, st)); // В дерево как узел содержащий ссылки
                            end
                            else begin
                                if not TStringList(pList[1]).Find(st,il) then // добавить если имя уникально
                                    TStringList(pList[1]).Add(st);
                                Form2.TreeView1.Items.AddChildObject(parentNode,st, nil);  // В дерево как ребенка для текущего файла
                            end;
                        end; // if standartComponent
                    end; //for
                end; // Parts <> nil
            end; // Open
            doc_3D.close; // эту строчку можно и закомментировать. работать будет.
        end;
    end;
end;

andno

IronMaxxx

Цитата: andno от 21.01.06, 11:05:10
А вот как быть с 2D? Для них такой способ не работает. А у них тоже есть внешние ссылки. Может кто знает все-таки - как добраться до готового списка?

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

Цитата: andno от 21.01.06, 11:05:10
Или как этот список можно автоматически записать на диск (там есть кнопка в меню)?

Для 3D уже заполенный список можно сохранить с помощью метода TStringlist::SaveToFile.

andno

Цитата: IronMaxxx от 21.01.06, 12:19:42
Это, как я понимаю, ссылки на внешние франменты и опять же на файлы библиотек для стандартных элементов. Да, здесь нужно уже хорошенько подумать.
А зачем Вам это, если не секрет?

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

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

Это касается не только 3D моделей, но и двумерных чертежей (например сборочных) и спецификаций, которые тоже имеют зависимости от внешних ссылок.

С 3D моделями, понятно. Есть FileName и есть метод SetFileName, который позволяет корректно изменить местоположение файла подсборки. Построй дерево проекта, перемести файлы, установи новый путь к ним и все.
А как быть с 2D документами?
Вроде можно тоже устроить со спецификацией, хотя подробно еще не смотрел, а вот со сборочными чертежами, уже уперся.

Последняя жиденькая идейка заключается в том, чтобы открыть файл сборочного чертежа и каким-то образом попытаться сохранить уже построенный Компасом список внешних ссылок, автоматически активировав меню Файл->Свойства->Закладка "Внешние ссылки". Там есть кнопка "Записать в файл". Получив таким образом список, обработать его, а дальше еще сам незнаю. Искать в бинарнике строчки и резать по живому? Ну в общем пока полный абзац.


IronMaxxx

Да, абзац... причем полный. У меня есть одна полубредовая идея, не знаю, может Вы уже пробовали, а может она ни к чему не приведет, но все равно выскажу. Можно попробовать запустить итератор по 2D-документу и в зависимости от возвращаемых им значений как-то действовать... Т.е. возможно он не будет разбивать стандартные элементы на объекты, а вернет указатель на весь компонент... Вот с ним можно будет попробовать работать дальше...
Но это всего лишь идейка - я сам еще смутно представляю, как это все будет работать и будет ли вообще.

andno

Цитата: IronMaxxx от 21.01.06, 13:49:09
Да, абзац... причем полный. У меня есть одна полубредовая идея, не знаю, может Вы уже пробовали, а может она ни к чему не приведет, но все равно выскажу. Можно попробовать запустить итератор по 2D-документу и в зависимости от возвращаемых им значений как-то действовать... Т.е. возможно он не будет разбивать стандартные элементы на объекты, а вернет указатель на весь компонент... Вот с ним можно будет попробовать работать дальше...
Но это всего лишь идейка - я сам еще смутно представляю, как это все будет работать и будет ли вообще.

Спасибо за совет!
Попробую посмотреть итераторы поподробнее.
Я действительно пробовал итераторы на 3D документе. Расчитывал на то, что подсборки тоже являются 3D документами и соответственно могут быть представлены ими. 

var
  iter : ksIterator;
  ref :  Reference;
  count: integer;
...
     iter := ksIterator(Kompas.GetIterator());
     if iter <> nil then begin
         iter.ksCreateIterator(D3_DOCUMENT_OBJ, 0);
         ref := iter.ksMoveIterator('F');  // смещаем итератор на первый элемент
         Сount :=0;
         while bool(ref) do begin
                Inc(Count);
                ref := iter.ksMoveIterator('N');  // смещаем итератор на следующую позицию
          end;
          iter.ksDeleteIterator(); // удалить итератор
     end;
...

При одном открытом файле основной сборки результирующий Count всегда 1.

Однако попробую проверить с другими опциями ksCreateIterator. Может что и получится...
+ Благодарностей: 1

IgorRUtver

А может подскажет кто, как добраться до коллекции элементов сборки в API7. Задача таже самае, получать полный путь к файлам сборки и устанавливать их количество. Пример приведенный выше работает и в принципето подходит, но из любопытства попытался найти для API7 и чет не накопал. Нашел только IPart7::FileName. Может кто знает?

IParts7 - интерфейс коллекции компонентов 3D документа.
+ Благодарностей: 1

IgorRUtver

Действительно)) Извиняюсь, утром мозг не пашет видимо

Bordes

Цитата: andno от 19.01.06, 15:59:36
Приветствую всех!
Стоит задача получения списка всех файлов внешних ссылок 3D модели.
Это тот список, который доступен после открытия файла a3d через опцию оновного меню Компаса:
Файл -> Свойства-> Закладка "Внешние ссылки".
....
Вопрос! Как можно добраться до этого списка?

возможно поможет IKompasDocument1::GetExternalFilesNamesEx


q

А возможно получить ссылку на файл Детали-заготовки без использования списка Внешних ссылок?