From f569cfcb78e99d3d3f0012891dd75c5e6e44d5ae Mon Sep 17 00:00:00 2001 From: BrandonPacewic <92102436+BrandonPacewic@users.noreply.github.com> Date: Wed, 16 Jul 2025 11:29:00 -0700 Subject: [PATCH 01/22] feat(build): Add initial CMake build system and Fusion addin scaffold --- isotope/.gitignore | 1 + isotope/CMakeLists.txt | 47 ++++++++++++++++++++++++++++++++++++++++ isotope/Makefile | 33 ++++++++++++++++++++++++++++ isotope/README.md | 0 isotope/isotope.manifest | 13 +++++++++++ isotope/src/isotope.cpp | 32 +++++++++++++++++++++++++++ 6 files changed, 126 insertions(+) create mode 100644 isotope/.gitignore create mode 100644 isotope/CMakeLists.txt create mode 100644 isotope/Makefile create mode 100644 isotope/README.md create mode 100644 isotope/isotope.manifest create mode 100644 isotope/src/isotope.cpp diff --git a/isotope/.gitignore b/isotope/.gitignore new file mode 100644 index 0000000000..9bf3746b2a --- /dev/null +++ b/isotope/.gitignore @@ -0,0 +1 @@ +compile_commands.json diff --git a/isotope/CMakeLists.txt b/isotope/CMakeLists.txt new file mode 100644 index 0000000000..2164814a69 --- /dev/null +++ b/isotope/CMakeLists.txt @@ -0,0 +1,47 @@ +cmake_minimum_required(VERSION 3.23) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +project(isotope LANGUAGES CXX) + +if(NOT APPLE) + message(FATAL_ERROR "This project is only supported on macOS.") +endif() + +set(USER_APP_DATA_DIR "$ENV{HOME}/Library/Application Support") +set(USER_APP_DATA_RELATIVE_PATH "Autodesk/Autodesk Fusion 360/API") +set(FUSION_API_DIR "${USER_APP_DATA_DIR}/${USER_APP_DATA_RELATIVE_PATH}") +set(FUSION_API_INCLUDE_DIR "${FUSION_API_DIR}/CPP/include") +set(FUSION_API_LIBRARY_DIR "${FUSION_API_DIR}/CPP/lib") +set(FUSION_API_VERSION_FILE "${FUSION_API_DIR}/version.txt") + +set(FUSION_API_ADDINS_DIR "${FUSION_API_DIR}/Addins") +set(PROJECT_INSTALL_DIR "${FUSION_API_ADDINS_DIR}/${PROJECT_NAME}") + +file(READ ${FUSION_API_VERSION_FILE} FUSION_API_VERSION) +message(STATUS "Fusion 360 API Version: ${FUSION_API_VERSION}") + +set(FUSION_API_TARGET "fusion360") +set(FUSION_API_NAMESPACE "autodesk") +set(FUSION_API_LIBRARY_TARGETS "core" "fusion") + +foreach(target IN LISTS FUSION_API_LIBRARY_TARGETS) + set(libpath "${FUSION_API_LIBRARY_DIR}/${target}${CMAKE_SHARED_LIBRARY_SUFFIX}") + add_library(${target} SHARED IMPORTED) + set_target_properties(${target} PROPERTIES IMPORTED_LOCATION ${libpath}) +endforeach() + +add_library(${FUSION_API_TARGET} INTERFACE) +add_library(${FUSION_API_NAMESPACE}::${FUSION_API_TARGET} ALIAS ${FUSION_API_TARGET}) +target_include_directories(${FUSION_API_TARGET} INTERFACE ${FUSION_API_INCLUDE_DIR}) +target_link_libraries(${FUSION_API_TARGET} INTERFACE ${FUSION_API_LIBRARY_TARGETS}) + +file(GLOB SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/*.cpp") +add_library(${PROJECT_NAME} SHARED ${SOURCES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") +target_link_libraries(${PROJECT_NAME} PRIVATE ${FUSION_API_NAMESPACE}::${FUSION_API_TARGET}) +target_include_directories(${PROJECT_NAME} PRIVATE "${CMAKE_CURRENT_LIST_DIR}/inc") + +install(TARGETS ${PROJECT_NAME} DESTINATION ${PROJECT_INSTALL_DIR}) +install(FILES "${CMAKE_CURRENT_LIST_DIR}/isotope.manifest" DESTINATION ${PROJECT_INSTALL_DIR}) diff --git a/isotope/Makefile b/isotope/Makefile new file mode 100644 index 0000000000..5a48ce82ad --- /dev/null +++ b/isotope/Makefile @@ -0,0 +1,33 @@ +BUILD_DIR ?= build +CMAKE_GENERATOR ?= Unix Makefiles +BUILD_TYPE ?= Debug # Release | RelWithDebInfo ... +JOBS ?= $(shell nproc 2>/dev/null || sysctl -n hw.ncpu) + +CMAKE_FLAGS = \ + -G "$(CMAKE_GENERATOR)" \ + -DCMAKE_BUILD_TYPE=$(BUILD_TYPE) \ + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + +configure: + @mkdir -p "$(BUILD_DIR)" + @cmake -S . -B "$(BUILD_DIR)" $(CMAKE_FLAGS) + @ln -sf "$(BUILD_DIR)/compile_commands.json" . + +build: configure + @cmake --build "$(BUILD_DIR)" -- -j$(JOBS) + +install: build + @cmake --install "$(BUILD_DIR)" + +clean: + @rm -rf "$(BUILD_DIR)" + @rm -f compile_commands.json + +help: + @echo "Targets:" + @echo " configure - configure the project and generate the compilation rules json" + @echo " build - configure and build" + @echo " install - install the built project into your fusion instillation" + @echo " clean - remove build directory and prep for a fresh build" + +.DEFAULT_GOAL := build diff --git a/isotope/README.md b/isotope/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/isotope/isotope.manifest b/isotope/isotope.manifest new file mode 100644 index 0000000000..d1313dab7e --- /dev/null +++ b/isotope/isotope.manifest @@ -0,0 +1,13 @@ +{ + "autodeskProduct": "Fusion360", + "type": "addin", + "id": "d07927b2-1d3a-44a0-8797-560e1bea09d0", + "author": "ADSK FRC", + "description": { + "": "Isotope Synthesis Exporter" + }, + "version": "1.0.0", + "runOnStartup": false, + "supportedOS": "windows|mac", + "editEnabled": true +} diff --git a/isotope/src/isotope.cpp b/isotope/src/isotope.cpp new file mode 100644 index 0000000000..c1a6880f53 --- /dev/null +++ b/isotope/src/isotope.cpp @@ -0,0 +1,32 @@ +#include +#include + +using namespace adsk; + +core::Ptr app; +core::Ptr ui; + +extern "C" XI_EXPORT bool run(const char* context) { + app = core::Application::get(); + if (!app) { + return false; + } + + ui = app->userInterface(); + if (!ui) { + return false; + } + + ui->messageBox("Hello from Isotope!"); + return true; +} + +extern "C" XI_EXPORT bool stop() { + if (ui) { + ui->messageBox("Goodbye from Isotope!"); + } + + app = nullptr; + ui = nullptr; + return true; +} From 0aae3f79f51d00ba2ed81f7056c474a1e3f16f62 Mon Sep 17 00:00:00 2001 From: BrandonPacewic <92102436+BrandonPacewic@users.noreply.github.com> Date: Wed, 16 Jul 2025 13:58:23 -0700 Subject: [PATCH 02/22] feat(context): add global context with Fusion app and UI setup --- isotope/inc/context.h | 16 ++++++++++++++++ isotope/src/context.cpp | 15 +++++++++++++++ isotope/src/isotope.cpp | 27 ++++++++------------------- 3 files changed, 39 insertions(+), 19 deletions(-) create mode 100644 isotope/inc/context.h create mode 100644 isotope/src/context.cpp diff --git a/isotope/inc/context.h b/isotope/inc/context.h new file mode 100644 index 0000000000..67be06ffcb --- /dev/null +++ b/isotope/inc/context.h @@ -0,0 +1,16 @@ +#pragma once +#ifndef ISOTOPE_CONTEXT_H_ +#define ISOTOPE_CONTEXT_H_ + +#include +#include +#include + +struct GlobalContext { + adsk::core::Ptr app; + adsk::core::Ptr ui; + + bool configure(); +}; + +#endif // ISOTOPE_CONTEXT_H_ diff --git a/isotope/src/context.cpp b/isotope/src/context.cpp new file mode 100644 index 0000000000..d72ae99113 --- /dev/null +++ b/isotope/src/context.cpp @@ -0,0 +1,15 @@ +#include "context.h" + +bool GlobalContext::configure() { + this->app = adsk::core::Application::get(); + if (!this->app) { + return false; + } + + this->ui = app->userInterface(); + if (!this->ui) { + return false; + } + + return true; +} diff --git a/isotope/src/isotope.cpp b/isotope/src/isotope.cpp index c1a6880f53..198b31a503 100644 --- a/isotope/src/isotope.cpp +++ b/isotope/src/isotope.cpp @@ -1,32 +1,21 @@ -#include -#include +#include +#include +#include -using namespace adsk; +#include "context.h" -core::Ptr app; -core::Ptr ui; +GlobalContext gctx; extern "C" XI_EXPORT bool run(const char* context) { - app = core::Application::get(); - if (!app) { + if (!gctx.configure()) { return false; } - ui = app->userInterface(); - if (!ui) { - return false; - } - - ui->messageBox("Hello from Isotope!"); + gctx.ui->messageBox("Hello from Isotope! lets go"); return true; } extern "C" XI_EXPORT bool stop() { - if (ui) { - ui->messageBox("Goodbye from Isotope!"); - } - - app = nullptr; - ui = nullptr; + gctx.ui->messageBox("Stopping Isotope... Who yae"); return true; } From cb073876f3e7151390400bb921d0e9227abfde3f Mon Sep 17 00:00:00 2001 From: BrandonPacewic <92102436+BrandonPacewic@users.noreply.github.com> Date: Mon, 21 Jul 2025 12:44:03 -0700 Subject: [PATCH 03/22] feat(isotope): initial user facing extension --- isotope/CMakeLists.txt | 1 + isotope/inc/config_command.h | 30 ++++++++ .../isotope_exporter/16x16-disabled.png | Bin 0 -> 1469 bytes .../isotope_exporter/16x16-normal.png | Bin 0 -> 1535 bytes .../isotope_exporter/32x32-disabled.png | Bin 0 -> 2002 bytes .../isotope_exporter/32x32-normal.png | Bin 0 -> 2176 bytes .../isotope_exporter/64x64-normal.png | Bin 0 -> 3335 bytes isotope/src/config_command.cpp | 19 +++++ isotope/src/isotope.cpp | 67 +++++++++++++++++- 9 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 isotope/inc/config_command.h create mode 100644 isotope/resources/isotope_exporter/16x16-disabled.png create mode 100644 isotope/resources/isotope_exporter/16x16-normal.png create mode 100644 isotope/resources/isotope_exporter/32x32-disabled.png create mode 100644 isotope/resources/isotope_exporter/32x32-normal.png create mode 100644 isotope/resources/isotope_exporter/64x64-normal.png create mode 100644 isotope/src/config_command.cpp diff --git a/isotope/CMakeLists.txt b/isotope/CMakeLists.txt index 2164814a69..1fa871ab4b 100644 --- a/isotope/CMakeLists.txt +++ b/isotope/CMakeLists.txt @@ -45,3 +45,4 @@ target_include_directories(${PROJECT_NAME} PRIVATE "${CMAKE_CURRENT_LIST_DIR}/in install(TARGETS ${PROJECT_NAME} DESTINATION ${PROJECT_INSTALL_DIR}) install(FILES "${CMAKE_CURRENT_LIST_DIR}/isotope.manifest" DESTINATION ${PROJECT_INSTALL_DIR}) +install(DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/resources/" DESTINATION "${PROJECT_INSTALL_DIR}/resources" FILES_MATCHING PATTERN "*.png") diff --git a/isotope/inc/config_command.h b/isotope/inc/config_command.h new file mode 100644 index 0000000000..41f5a315f3 --- /dev/null +++ b/isotope/inc/config_command.h @@ -0,0 +1,30 @@ +#pragma once +#ifndef ISOTOPE_CONFIG_COMMAND_H_ +#define ISOTOPE_CONFIG_COMMAND_H_ + +#include +#include + +#include "context.h" + +class ConfigureCommandCreatedHandler : public adsk::core::CommandCreatedEventHandler { +private: + const GlobalContext* gctx; + +public: + ConfigureCommandCreatedHandler(const GlobalContext* context) : gctx(context) {} + + void notify(const adsk::core::Ptr& args) override; +}; + +class ConfigureCommandExecutedHandler : public adsk::core::CommandEventHandler { +private: + const GlobalContext* gctx; + +public: + ConfigureCommandExecutedHandler(const GlobalContext* context) : gctx(context) {} + + void notify(const adsk::core::Ptr& eventArgs) override; +}; + +#endif // ISOTOPE_CONFIG_COMMAND_H_ diff --git a/isotope/resources/isotope_exporter/16x16-disabled.png b/isotope/resources/isotope_exporter/16x16-disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..f4ba1b8f8393d4c4dbf6f24a1034a85484b017c6 GIT binary patch literal 1469 zcmaJ>eNYr-7(XI1V89QQ2vN2b$Pf1R^KL)lWbSScxa5#dDr^$nyWNF7xZ9iCMNXnA z%@K()(itJfGG`i)!NG>eaReM8KWNHqCUeY!nub5huxuo2m>lh0;LJZ9clO~e@(J+Fp1vo5El%?mJ50wfWnBk{$E)ogyUEnw zmV8G@0O!PT$!5)DN51@rKgnht3k6Msy@ja4TgcX$y87n4GaES zNr!w=wGy(yH;2x^kRsaPmn|O56Lv~9N=aix+SFK9;cu+^gKKm>%%NyQLxZ`2G^>$n6lYl$#R!xj z5TJo*J3_h;MMB!DIgoi6Uef##C9ErI2r^&=pIWcmU=Z|NHo>sRb00mV%@+-n4~+_8 z6gOjNFleMWtF7r?>Hk}>6dA*Kn61OYVl2s!1Y@`3 zxQl05JMQEyEQ!%p7ful|TX#s)g^*v0`Bi}5d0XOlEnKTq=l^bNg-U%C_w9#uL(0`YQ8(^B%pt zBX4)cft*vei|;DqpX}r(4_{bz=nmg5?RGqqvm}v+7e`N5?VpjUOj~+V!okElZYHaU z$~-R**s%=W+!5dB6?a|<96b8r@JDZsO^(hqUGVj9vpLTEwBYsL&#S^qF;Cjm?aXU+ z>Ak(5-Tdb*>nkG4B)wnuER~s3v3A)-nLOvis}8Ox-We@;r!?>E!WC`BceC;WV=o-J zIwcHEz@4SHZvF7P_3wXA8)(~ZetdOmb;p-IFCDvavgM+hl`!t;N!zM4Y#p31Kb_j_ zn|b5C!tFy_S`rJc1S}GkG2EKf0BPA%cbnG#V*7KZX*}&OxWDzsvJ&_6UEb_HLetxp zRP9x0(TGUIomkg>dCfQ1si#g%S1(-Z`Z;%H>H_MJC%GG$`8;`l0 guh#vM?kKsQJh`PS^W>?4Hsc@ac9rqR9ou&O2Pq5pmjD0& literal 0 HcmV?d00001 diff --git a/isotope/resources/isotope_exporter/16x16-normal.png b/isotope/resources/isotope_exporter/16x16-normal.png new file mode 100644 index 0000000000000000000000000000000000000000..34948454a985666777913757ecc4a1fa6102867e GIT binary patch literal 1535 zcmaJ>eNYr-7+;VJgU}<tz!T z%C$<=iCjBy2!R#JiB#wLaG&Ou>y(maU9M>MSBlN`A}1jQ`59@B7!MF4vMIo^NH}Wn zF()zx%!Bv17(?JO6|>%nxZ)n+fUgX8tGWy`HUbrK9ETasCQAYtq(XwY5GHYwzzA?N zD8cX~#SvO+@qg?x9DGiK2n>VKG6&2pf(? zBJmW*v<`}#45jkpl zJx&CCu}O-=<2Z{G1ViHtiQ8F%U>Tyw#j+%c({7rhsRYJ@Yzy-^oTFHZWL++TSnT0A z7vc8gaTLzj7ZWs@u=Pd_Q;3Rk!mk4SPS}zUwdLKqESRcZsjA@w8OrKZQ#I;T4R*Uh z=LrU$?-N8N8W%1|bPDupJavz(D-E(#q^l8ltk}HrfM6(+39$l2qIQW2p)|`%sKD59 zloDA9ltt!3d3nf$y>wp@$9W4RAR#Rg7mwRO~2f`{LDbgO>xG+&?o;+I(^*Qu|2enM%P)LrE9gUIpNVcJCa*pD7hsa%RD=JWb1*tG=0PCzx8;%-*xP-wjaNC745&h=I(&}?a93r zndY6HHTHdKO8?)F$yI&Vhnp55wR0P;_Ixe5Ts=>m8k}o)oQq9wS=8fS5$Rsp)Vem( z-;f~}bnLyA>7R1v;+58o*A;6<-^e?kcUJfQa<(mX0C{R!=I)9=0>+vmM{e-$NY>*+ wcwgE6{1Z8?ySKetQZQ>q?u})0*YkZ)-=$sRbA!Vz@xQQlvEQ?=@RfD{0oLsno&W#< literal 0 HcmV?d00001 diff --git a/isotope/resources/isotope_exporter/32x32-disabled.png b/isotope/resources/isotope_exporter/32x32-disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..770208ec0e4889c412d946e67f8dcd6bc23b24ec GIT binary patch literal 2002 zcmaJ?dsGu=77tR;66t{z&;=^fAQmV|W+v}hB8EgDDbYwH(g+8J$wUGq6DI=+gk_a= zU7$z>!=c+0 zpjaMG0onLYqgl$D?zzMQjCv_+yFd-9Epj}^m{d&S?-Xy=V#RrwM9+%b;Oi4>M`#2S zPN9I^ly9~ob}4Hf7@^2oj5+1esV2!ysSIm+*N@ z7%#Jp7f3(}Pt1eGG8qJIQb;5+NUjh_c%VqQ3F5;`wo0>=Ld_Vy{LCu^oEo-Isc%aO$;zC=0+1@Ov zFo^Xh{q50D4aW^TZ`A2i&+{s7tzp*q<(HRVSF8xq4f_oPWyRVX-IGtwZ%v;2?arZEd{nS-=mD;p_Lt zddf8A<>k9?h1mH?%m+j)YN~?&PFCa^S6E(9v*I6_vX5F-?nY=(49Jc+Mk~7n7G*3 zZzd+Z`~Ldw>5-8XXMO$PosRfJiG>^0ExJ87Z)(@AUw@{gsHoz~6`8rErKPK9eE%D9 zabYzzH8IQOoy*R>%{>QXtlm{xTHV?jalE4g%gbXH_a5s1eUom_!-siM9!jM$CpS0V zZ01aV5Omn(8ec!9`pl;)*Q>g^+G4jyI(A#Io40Q_cXkH)`uZkq*-}q9n{*!V`L?#G zPlt!;>LphfR~-y`Gvb=u*{t&@i}_taK_G@QA7N9z0f;@7P7zQd$JVIHrh zzP^9|_oMeMr@lV8+WttcNPR6?r8%*&uuyocy}eu7OZddj%*>pb?2eB3OKO(Gu|u_S z+qS^u^{Xl?D>a#!)At1369WUU4qnlmxct>uYx9L6(+5g=8yguoj+Y2tX(+pu`_!v7 zYp;{JLbZ{4ops2XLLv=fsq`N}K%}?kZ+_jo@yGX$9|&AgH|ewY+kMY#pZi?Q^>n#h z>1lgC&u}Z7KW2spY#oZI)JpNr3*WT)BY();QS5MB^47%)g+T&=U~0x$qEe~+R;{{v zqO0qJhw|X?3r8ZACxcfu+{r&$`_Jte8KZ>1R;37i9{F`|BTa-|yLN>>8yOi9|7xV! zxjOo-w@)8UUlS9eV9YxtbJaw_NFYag>2KQaF3I-KwN S%~7rU$Ew`4S#dHxtL%T(C<5XD literal 0 HcmV?d00001 diff --git a/isotope/resources/isotope_exporter/32x32-normal.png b/isotope/resources/isotope_exporter/32x32-normal.png new file mode 100644 index 0000000000000000000000000000000000000000..77458fce7d136cceab5bce620c5501e226f82445 GIT binary patch literal 2176 zcmaJ@dsGu=77w^a2#O*PmHHSG-3pRqW|EKxL=6cEL4q`*Qbl2sOdvuYCL;t8X|+&_ zqJS%u9_kCJwX(PZt!S%gK|w);V^=I%c~b=x1X@^L-3fy2AD+&cZ@&5N+{f?U`@84N zXTd>|1teDzfk0Rw^%u$U9k47LBK|ZbBn;!5l}Rq~B^1{$e}NzV5h;)f2!zYW>?V-8 z_}SLr|DlOMu&=Z%t7`q%Dgt57X-!Bd7AgzmDfL@YKqrkh?%N0QQkP2aU()aN~6YrY4ke4;ucZp_hEc84*E?tT7yjX*1OI$ zQ#8DMbTeY0Lo|@C)ml=V);3{s^iM4B)Ha308&J9&HR<;ml_;)GnKj1C`1gS{2vMJz4@WPABROnBB-sxx7TPKnOO)!=bwY~jDPh%(DcBq)1T)#F8YBbsX=dU;=@u6Kg;j4DJ1xa2Ynd4O z+mW%H-i{rr!)MKi4^_r;>Kc5WHBylvBvkS;H!*PM*5%a|BhS{G%Tfl{me_}Q#eAf5 ze5_i`=tvXi4}8iHGa|9%s2}V0SEbc5H;FRJtNc3ld#1`97kBJ@zpgDUf^6sOBF22Y zBB-TBH+P(FXl}Yb^{OkzVHwr$arlAk@Uij9dQJVv$szAwpVqd8bDHCvD4jfJM~nw@ zp-Z*H>GhGWTZR>fZ*i#fL5r>R0qh?0NpIh1L~*s3M28~X4ZhZ8(q4yLIVq#wck_7#HR~=U z%#{7k-Tt6BE6UF}|1 zV#<3h;57q8Rd{4gnCV-aC3e-(5jP5K$BI_mYt4Qxc}|~KWBJxb~jwp{=hEJ^U5=De6v2$It&^w*)ZQXF87?zqxtwxbp^A$Zk>DvbqBI zh1X7WW!swT+Xq*TgIsLsmhUL50qI={MQ!DFDkFYl{$9szdQFPkION*ke<=Br%f%kI zzb+2FR8!@7)BBNi|8uEJR@zVU&(nJiLrL~U9+OtK%esoty;YuskY6b0J)lREo)eC{ zJl(e+|AJVND_Snx(Mir1_F3C}7Tef)GjZuuJS9fvvSa*uNakdbvwwY({hnR4!(_+O zkE-fOOLfhKpNGA;m1osFGB0Y0lRRmD>F(vsF`sV9Kh;))Q*7sz+dQ+rnjj4prMb}$ z+dwt>7GR992R^{{fjQ^&=6T}U-74Kw>dEG_owqaRt#5L_eN0?utrj0e+Mhn@l(ue7 z;9;>8Rz+$~_kqG)q{Ozq{p~$xoHEupw~hAG+ZDYof%Z2RwwTvVyb3aZ`F?B9g^ypJ zJ0Utyn;?#q<~AmOOrtcQ=SHz*C{;tuIsV7ouu>wC_-TZ9)19+jYc^I_zPR!82zZs) zZ8H!!eo%IQMbC|)*uu(yp$FvQP1(bDs!uV0Sfm*k{bY4fOWBH?eIpweF>9!g)630s z@>WI<^&h-dPP*dsFF~*-^MH_cM8hsgrX2~M9R6}3an~vI*|tC8v)05EHgyL)@;b&J zPxjldfJx~dnK_qT9<>K7&1PhsD0TQ>%9H0Ddu7v5cqLyad6sHioS9+gkRvbn@2bu% zgL|yI@BfzMxx_&bOyQ!e=AAta#tFc+!aTPS-Q4#3zc;s6(?`}Ni6vtnZ2NG*?$D?2 z4W5m&l5815wA}i#hQGJG-hVWXWkKj?1aSNW=f^yHN8{+4rjaOqmuw7xQ+D6G+!1*E$TV=Qu_%r;CZ z9DVV9lf(ScMI>VD#86STW2Ia9t|g66lB^jg_s;+A3UW_&sm~5*x|x5egTK(^9l!Ea qiQ#r$+>(aZK+DGSkp;b01MEuDv9?<8gF7t00aCvpQL%5t!T$q3?qw4I literal 0 HcmV?d00001 diff --git a/isotope/resources/isotope_exporter/64x64-normal.png b/isotope/resources/isotope_exporter/64x64-normal.png new file mode 100644 index 0000000000000000000000000000000000000000..3076b744ca20f1537e6a0475c423a89d5e862b07 GIT binary patch literal 3335 zcmaJ^c{o)28y{P?i^^7_rXfo;`!EZ#jeSWIk+m^1n3#R+l_aF-P9;jELYHW>lr`D1 zlr6%9Y+a%3U9@0+qvihYANRTEIq&(N_dTC?f8Re&qO+5=oQ#?b005A)wZRjFr|CLL zZ4f^5sH{QZA;KY8n*$!SsZRq5O>of7uyN9uSAn;J?XTd^55 z;P+zVsQ(fS5`p%|5RnM50U7BJMqw~yFcED410zWoval=^tiQfK=%+sUA4U8zw?c_9 zA?B~Ra4-WH(I18I2gChQ7%-NM@&jW~7$O+%hsL1b2o##)4+8_{xMZQ zCic5jII`;-1O0nt*15mejzSmi8e6zj$?D);0D#n-E#Ax}+-Evn&e^3`xlzCfJ6x5j zoD6)Klj*m|;A*bv-c0S$653IRS1uY|4!CZ$;6{F+VIMcIn|zV)f3K&H)#sFyUVokz z@N|w%}wW2R;X9_DACF9z3|H()B)lc>hpMW$o^LgfJ}nkfu??+ z&q~7$&8iER6j1>wG9z*bQeUO<77@M5vAAmhH@o**U55ogkXMM!M%eX=D5DtjG->7} zf3s_Az^Akz{SUI-9@(bugLO~4U?QXMzBcGxuJwHV+AfrFN_yC@buh7Ai+}A1=jmF$ zD=wjILFJS9$q#@lV|ilYyJ|DLZlB*O?@9(;eMOiQ&1p3iz`<4?B*eob6Qc8jIE%kGF{38pn?%ALnVzqNVzu@I6W7XyZZwtVP(Q3(_gvVtS)}+bwDamR1djLApl@$) zToj8v;C=C2URD;17Oj?~>=~{Q=9d781)gTvZ#M^-k_BFa?8fjAi;`;!^Eb?-I~La%u)Y}_oe(i-*ZP`hUahL{(5v*hi8M)E?c(P=zj^6SjGz_-gQ z@!U_XZ%bL+l-?dfQNl7WfhRTm=qm)CIjRGq4m6%ogWWjo%wlYFvQ`XkXn$%@@Hq85 z=t$3w5ikSvbqqLPAu#Jp$)B>@^d?%=8$9q_N_*#~YL~}iOYip#EF|wnNFVQ1)zo_^ z75j(l&b4OnRHx46mKrTB(b9UA*C}m(d|Y*$kxojPek|r|)yc23IsX2)v9mjPz0uu# z4f9Ll(wvj= z>%OStdMYg8>~airq`-liNboV=ZE7fQudntn_(=Cn7m1!5(aMMFq|2$Sx$RYn8U&k? zj@YtY3d@!!&tvwY`C!Ik!F;X}Mm%0NUytzDO0Hze#;lNbw#D|D3V_B1P}ayoWVudL zeBf$))OtDRQjNsD0h98Oh!xgDaG?M*Shp?Vu^RZR#Q@vdLx*>1*!m>Uxd7P^71ctE zdm)<^I0jbm0x_B_IwT&c?Hd7y6;rwA?k#-kuyqDjwkr-W8;NBfUwG$8RRNN7a%EQ1 zJ;Qh^c*VONiDz%t$8F??_}u@ZGHOlF(ru=GoQOH>TQ6+%ik|6C&uEq$dLVJaP%W|S z8$7Uqmfe2u`K_xPvYmD(7=(z-j+}pdJL%z$ChIu~po6R7VTlO^>izv5Nax(lUHPq( zF@Z?a=nsvrFD}EKY{YpdxYKVeT~=0Z+uOGImp7%h1-Gh}S?@E~0wiC{t1MseB`l)1 zgG<)NOkRdn`DOpeU5gm*%PtbZk8b37;g-2P{;L?l4+;5&~+SigMSoOWoy*eJ}1u#>x z?A`FR1_9C=U-dTe49V=Dhpc{fJETmS%C@wz-4WMv0XaWVYvMQm@Ei~~b;f>+U95ZJ zq(smcoof7rW3S*W=Go9Bb!6I`FYOQNPfxyd-w}fU?bDcJMBLu1;uFi$`jOsCOK#Q< zgW#_GtJSBe&raTQ9_H7Wj4bYIcCQHD($c!P1%r~@I@H`4^Y&SsO>9fku?^DAnlK<0 zgRpYI=Re+RDe!53HX&d3#e`DZenTp+RlwO{hadztUHaq-%W#eJ*7+trUlE+he+cl2jAK zxSlm_WHkmk5?fG=dPJ98UXhotP|j*>vp6&;dZpR7a+Vf)PqR^hmO0D5=xo1nYxf&f zu$W@8)aP))hLwm_rR)0&6b}_`Do=56h*}HT>l>!l=dSUwE3h<1(b7{-&uXWUxkYTW zqzdO6M&oWCCeD7$I&ki|jW~7YrM0s-&m4QI&87Qs7r!cE-%F8Mnn(3T-{5Ctql`uM zS?TM0R(U1aOV*WRiL%y9Rn1FSMU^LIWRR|CeZ98d+a8a5g8Z1xUfNTk$hH%O=i2C+ zh7V+91EQq$iu)(|OMkvdd!Axwcbo9`%RO~*O91J(J|u$?_5Ap&7qM4L!VP7j51RKb zU@+?U1tvY%zARIpQz>Fz{3_~pJNq>=j$!)6f!}!sT9Vzp?Zke=qB+OK8~P`x9w_~u zN3D}wXy2WefBcz-zf{Wfhd%J$*L+Mn>t>oR&bHdlZ_;Z-%ll3Pv&dfe^3hZUb@@FW zVC{v^c?Es#vExd-6B)%Jf0ca8o}G+*s!~W{9Vov2JUgcbePL`?(q8N@eZifzjI~hT zUdf>g)uK|Rt)2>C5s6zkt4P*R@7;(%Dc$M@?+39?Ix4)4%H$V{90jFn0f4$7m)v5I S-@N`eVr%Jye_(#()c*iIn!beq literal 0 HcmV?d00001 diff --git a/isotope/src/config_command.cpp b/isotope/src/config_command.cpp new file mode 100644 index 0000000000..64767e2a82 --- /dev/null +++ b/isotope/src/config_command.cpp @@ -0,0 +1,19 @@ +#include "config_command.h" + +#include +#include +#include + +void ConfigureCommandCreatedHandler::notify(const adsk::core::Ptr& args) { + adsk::core::Ptr command = args->command(); + if (!command || !command->isValid()) { + gctx->ui->messageBox("Invalid command in ConfigureCommandCreatedHandler."); + return; + } + + command->execute()->add(new ConfigureCommandExecutedHandler(gctx)); +} + +void ConfigureCommandExecutedHandler::notify(const adsk::core::Ptr& eventArgs) { + gctx->ui->messageBox("Configure command executed successfully."); +} diff --git a/isotope/src/isotope.cpp b/isotope/src/isotope.cpp index 198b31a503..d31d7be12a 100644 --- a/isotope/src/isotope.cpp +++ b/isotope/src/isotope.cpp @@ -1,8 +1,24 @@ #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include "context.h" +#include "config_command.h" + +#include GlobalContext gctx; @@ -11,11 +27,58 @@ extern "C" XI_EXPORT bool run(const char* context) { return false; } - gctx.ui->messageBox("Hello from Isotope! lets go"); + adsk::core::Ptr workspace = gctx.ui->workspaces()->itemById("FusionSolidEnvironment"); + adsk::core::Ptr tab = workspace->toolbarTabs()->itemById("ToolsTab"); + assert(workspace); + assert(tab); + + tab->activate(); + tab->toolbarPanels()->add("isotope_tool_tab", "Isotope"); + + auto button = gctx.ui->commandDefinitions()->addButtonDefinition( + "isotope_command", + "Isotope", + "This command does something interesting.", + "./resources/isotope_exporter/" + ); + + if (!button || !button->isValid()) { + gctx.ui->messageBox("Failed to create command definition for Isotope."); + return false; + } + + button->commandCreated()->add(new ConfigureCommandCreatedHandler(&gctx)); + + auto panel = gctx.ui->allToolbarPanels()->itemById("isotope_tool_tab"); + + if (!panel || !panel->isValid()) { + gctx.ui->messageBox("Failed to find toolbar panel for Isotope."); + return false; + } + + auto button_control = panel->controls()->addCommand(button); + + if (!button_control || !button_control->isValid()) { + gctx.ui->messageBox("Failed to add command control for Isotope."); + return false; + } + + button_control->isPromoted(true); + button_control->isPromotedByDefault(true); + return true; } extern "C" XI_EXPORT bool stop() { - gctx.ui->messageBox("Stopping Isotope... Who yae"); + auto panel = gctx.ui->allToolbarPanels()->itemById("isotope_tool_tab"); + if (panel && panel->isValid()) { + panel->deleteMe(); + } + + auto button_def = gctx.ui->commandDefinitions()->itemById("isotope_command"); + if (button_def && button_def->isValid()) { + button_def->deleteMe(); + } + return true; } From be496b7649e9cd94d0a61b7594166b91c427a00d Mon Sep 17 00:00:00 2001 From: BrandonPacewic <92102436+BrandonPacewic@users.noreply.github.com> Date: Tue, 22 Jul 2025 09:41:13 -0700 Subject: [PATCH 04/22] deps(isotope): add protobuf cmake dep --- isotope/CMakeLists.txt | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/isotope/CMakeLists.txt b/isotope/CMakeLists.txt index 1fa871ab4b..d3e9765558 100644 --- a/isotope/CMakeLists.txt +++ b/isotope/CMakeLists.txt @@ -43,6 +43,31 @@ set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") target_link_libraries(${PROJECT_NAME} PRIVATE ${FUSION_API_NAMESPACE}::${FUSION_API_TARGET}) target_include_directories(${PROJECT_NAME} PRIVATE "${CMAKE_CURRENT_LIST_DIR}/inc") +set(ABSL_PROPAGATE_CXX_STD ON) +set(protobuf_BUILD_TESTS OFF CACHE BOOL "" FORCE) +set(protobuf_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) +set(protobuf_BUILD_CONFORMANCE OFF CACHE BOOL "" FORCE) +set(protobuf_INSTALL OFF CACHE BOOL "" FORCE) + +include(FetchContent) +FetchContent_Declare( + protobuf + GIT_REPOSITORY https://github.com/protocolbuffers/protobuf.git + GIT_TAG v23.0 +) +FetchContent_MakeAvailable(protobuf) +find_package(Protobuf REQUIRED) + +set(Protobuf_PROTOC_EXECUTABLE $) + +target_link_libraries(${PROJECT_NAME} PRIVATE protobuf::libprotobuf) +file(GLOB PROTO_FILES "${CMAKE_CURRENT_LIST_DIR}/../mirabuf/*.proto") +set(PROTO_GEN_DIR "${CMAKE_CURRENT_BINARY_DIR}/protobuf_gen") +file(MAKE_DIRECTORY ${PROTO_GEN_DIR}) +protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS ${PROTO_FILES} GENERATE_EXTENSIONS ON PROTOC_OUT_DIR ${PROTO_GEN_DIR}) +target_sources(${PROJECT_NAME} PRIVATE ${PROTO_SRCS} ${PROTO_HDRS}) +target_include_directories(${PROJECT_NAME} BEFORE PRIVATE ${PROTO_GEN_DIR}) + install(TARGETS ${PROJECT_NAME} DESTINATION ${PROJECT_INSTALL_DIR}) install(FILES "${CMAKE_CURRENT_LIST_DIR}/isotope.manifest" DESTINATION ${PROJECT_INSTALL_DIR}) install(DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/resources/" DESTINATION "${PROJECT_INSTALL_DIR}/resources" FILES_MATCHING PATTERN "*.png") From 0b10bd4a8d91b9337d49eeb1998c3936b9c9ec7f Mon Sep 17 00:00:00 2001 From: BrandonPacewic <92102436+BrandonPacewic@users.noreply.github.com> Date: Thu, 24 Jul 2025 11:03:14 -0700 Subject: [PATCH 05/22] feat(isotope): material and component parsing --- isotope/CMakeLists.txt | 34 ++++++-- isotope/inc/components.h | 14 ++++ isotope/inc/config_command.h | 12 +-- isotope/inc/context.h | 3 +- isotope/inc/materials.h | 15 ++++ isotope/inc/parser.h | 9 +++ isotope/src/components.cpp | 151 +++++++++++++++++++++++++++++++++++ isotope/src/context.cpp | 4 + isotope/src/isotope.cpp | 40 +++++----- isotope/src/materials.cpp | 84 +++++++++++++++++++ isotope/src/parser.cpp | 63 +++++++++++++++ 11 files changed, 395 insertions(+), 34 deletions(-) create mode 100644 isotope/inc/components.h create mode 100644 isotope/inc/materials.h create mode 100644 isotope/inc/parser.h create mode 100644 isotope/src/components.cpp create mode 100644 isotope/src/materials.cpp create mode 100644 isotope/src/parser.cpp diff --git a/isotope/CMakeLists.txt b/isotope/CMakeLists.txt index d3e9765558..92db35d2c7 100644 --- a/isotope/CMakeLists.txt +++ b/isotope/CMakeLists.txt @@ -57,16 +57,40 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(protobuf) find_package(Protobuf REQUIRED) - set(Protobuf_PROTOC_EXECUTABLE $) target_link_libraries(${PROJECT_NAME} PRIVATE protobuf::libprotobuf) -file(GLOB PROTO_FILES "${CMAKE_CURRENT_LIST_DIR}/../mirabuf/*.proto") + set(PROTO_GEN_DIR "${CMAKE_CURRENT_BINARY_DIR}/protobuf_gen") file(MAKE_DIRECTORY ${PROTO_GEN_DIR}) -protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS ${PROTO_FILES} GENERATE_EXTENSIONS ON PROTOC_OUT_DIR ${PROTO_GEN_DIR}) -target_sources(${PROJECT_NAME} PRIVATE ${PROTO_SRCS} ${PROTO_HDRS}) -target_include_directories(${PROJECT_NAME} BEFORE PRIVATE ${PROTO_GEN_DIR}) +file(GLOB PROTO_FILES "${CMAKE_CURRENT_LIST_DIR}/../mirabuf/*.proto") + +set(PROTO_SRCS "") +set(PROTO_HDRS "") +foreach(_P IN LISTS PROTO_FILES) + get_filename_component(_NWE "${_P}" NAME_WE) + list(APPEND PROTO_SRCS "${PROTO_GEN_DIR}/${_NWE}.pb.cc") + list(APPEND PROTO_HDRS "${PROTO_GEN_DIR}/${_NWE}.pb.h") +endforeach() + +add_custom_command( + OUTPUT ${PROTO_SRCS} ${PROTO_HDRS} + COMMAND ${Protobuf_PROTOC_EXECUTABLE} + --cpp_out=${PROTO_GEN_DIR} + -I ${CMAKE_CURRENT_LIST_DIR}/../mirabuf + ${PROTO_FILES} + DEPENDS ${PROTO_FILES} + COMMENT "Generating protobuf sources" +) + +add_custom_target(generate_protos ALL DEPENDS ${PROTO_SRCS} ${PROTO_HDRS}) +add_dependencies(${PROJECT_NAME} generate_protos) +target_sources(${PROJECT_NAME} + PRIVATE + ${PROTO_SRCS} + ${PROTO_HDRS} +) +target_include_directories(${PROJECT_NAME} SYSTEM PRIVATE ${PROTO_GEN_DIR}) install(TARGETS ${PROJECT_NAME} DESTINATION ${PROJECT_INSTALL_DIR}) install(FILES "${CMAKE_CURRENT_LIST_DIR}/isotope.manifest" DESTINATION ${PROJECT_INSTALL_DIR}) diff --git a/isotope/inc/components.h b/isotope/inc/components.h new file mode 100644 index 0000000000..d33c1c832c --- /dev/null +++ b/isotope/inc/components.h @@ -0,0 +1,14 @@ +#pragma once +#ifndef ISOTOPE_COMPONENTS_H_ +#define ISOTOPE_COMPONENTS_H_ + +#include +#include + +#include "assembly.pb.h" + +mirabuf::Parts map_all_parts( + const adsk::core::Ptr& components, + const mirabuf::material::Materials& materials); + +#endif // ISOTOPE_COMPONENTS_H_ diff --git a/isotope/inc/config_command.h b/isotope/inc/config_command.h index 41f5a315f3..eaa333226c 100644 --- a/isotope/inc/config_command.h +++ b/isotope/inc/config_command.h @@ -2,27 +2,27 @@ #ifndef ISOTOPE_CONFIG_COMMAND_H_ #define ISOTOPE_CONFIG_COMMAND_H_ +#include "context.h" + #include #include -#include "context.h" - class ConfigureCommandCreatedHandler : public adsk::core::CommandCreatedEventHandler { private: - const GlobalContext* gctx; + const GlobalContext& gctx; public: - ConfigureCommandCreatedHandler(const GlobalContext* context) : gctx(context) {} + ConfigureCommandCreatedHandler(const GlobalContext& context) : gctx(context) {} void notify(const adsk::core::Ptr& args) override; }; class ConfigureCommandExecutedHandler : public adsk::core::CommandEventHandler { private: - const GlobalContext* gctx; + const GlobalContext& gctx; public: - ConfigureCommandExecutedHandler(const GlobalContext* context) : gctx(context) {} + ConfigureCommandExecutedHandler(const GlobalContext& context) : gctx(context) {} void notify(const adsk::core::Ptr& eventArgs) override; }; diff --git a/isotope/inc/context.h b/isotope/inc/context.h index 67be06ffcb..dd7ae716c0 100644 --- a/isotope/inc/context.h +++ b/isotope/inc/context.h @@ -3,14 +3,15 @@ #define ISOTOPE_CONTEXT_H_ #include -#include #include +#include struct GlobalContext { adsk::core::Ptr app; adsk::core::Ptr ui; bool configure(); + bool isValid() const; }; #endif // ISOTOPE_CONTEXT_H_ diff --git a/isotope/inc/materials.h b/isotope/inc/materials.h new file mode 100644 index 0000000000..a079e919d4 --- /dev/null +++ b/isotope/inc/materials.h @@ -0,0 +1,15 @@ +#pragma once +#ifndef ISOTOPE_MATERIALS_H_ +#define ISOTOPE_MATERIALS_H_ + +#include + +#include "material.pb.h" + +#include + +mirabuf::material::Materials map_all_materials( + const adsk::core::Ptr& design_appearances, + const adsk::core::Ptr& design_materials); + +#endif // ISOTOPE_MATERIALS_H_ diff --git a/isotope/inc/parser.h b/isotope/inc/parser.h new file mode 100644 index 0000000000..62bf083861 --- /dev/null +++ b/isotope/inc/parser.h @@ -0,0 +1,9 @@ +#pragma once +#ifndef ISOTOPE_PARSER_H_ +#define ISOTOPE_PARSER_H_ + +#include "context.h" + +void export_design(const GlobalContext& gctx); + +#endif // ISOTOPE_PARSER_H_ diff --git a/isotope/src/components.cpp b/isotope/src/components.cpp new file mode 100644 index 0000000000..b543b01064 --- /dev/null +++ b/isotope/src/components.cpp @@ -0,0 +1,151 @@ +#include "components.h" + +#include + +#include "assembly.pb.h" +#include "types.pb.h" + +namespace { + +mirabuf::PhysicalProperties map_physical_properties(const adsk::core::Ptr& properties) { + mirabuf::PhysicalProperties new_properties; + new_properties.set_mass(properties->mass()); + new_properties.set_volume(properties->volume()); + new_properties.set_density(properties->density()); + new_properties.set_area(properties->area()); + if (auto com = properties->centerOfMass()) { + if (auto vec = com->asVector()) { + new_properties.mutable_com()->set_x(vec->x()); + new_properties.mutable_com()->set_y(vec->y()); + new_properties.mutable_com()->set_z(vec->z()); + } + } + + return new_properties; +} + +mirabuf::TriangleMesh map_b_rep_body(const adsk::core::Ptr& body) { + // auto calc = body->meshManager()->createMeshCalculator(); + auto mesh_mgr = body->meshManager(); + if (!mesh_mgr) { + return {}; + } + + auto calc = mesh_mgr->createMeshCalculator(); + if (!calc) { + return {}; + } + + calc->setQuality(adsk::fusion::TriangleMeshQualityOptions::LowQualityTriangleMesh); + auto fus_mesh = calc->calculate(); + if (!fus_mesh) { + return {}; + } + + mirabuf::TriangleMesh mesh; + mesh.mutable_info()->set_name(body->name()); + // TODO: Info guid + mesh.mutable_info()->set_version(1); + mesh.set_has_volume(true); + + std::vector coords = fus_mesh->nodeCoordinatesAsFloat(); + mesh.mutable_mesh()->mutable_verts()->Add(coords.begin(), coords.end()); + + std::vector normals = fus_mesh->normalVectorsAsFloat(); + mesh.mutable_mesh()->mutable_normals()->Add(normals.begin(), normals.end()); + + std::vector node_indicies = fus_mesh->nodeIndices(); + mesh.mutable_mesh()->mutable_indices()->Add(node_indicies.begin(), node_indicies.end()); + + std::vector texture_coords = fus_mesh->textureCoordinatesAsFloat(); + mesh.mutable_mesh()->mutable_uv()->Add(texture_coords.begin(), texture_coords.end()); + + return mesh; +} + +mirabuf::TriangleMesh map_mesh_body(const adsk::core::Ptr& body) { + auto fus_mesh = body->displayMesh(); + + mirabuf::TriangleMesh mesh; + // TODO: Info crap (this is getting annoying) + mesh.set_has_volume(true); + + std::vector coords = fus_mesh->nodeCoordinatesAsFloat(); + mesh.mutable_mesh()->mutable_verts()->Add(coords.begin(), coords.end()); + + std::vector normals = fus_mesh->normalVectorsAsFloat(); + mesh.mutable_mesh()->mutable_normals()->Add(normals.begin(), normals.end()); + + std::vector node_indicies = fus_mesh->nodeIndices(); + mesh.mutable_mesh()->mutable_indices()->Add(node_indicies.begin(), node_indicies.end()); + + std::vector texture_coords = fus_mesh->textureCoordinatesAsFloat(); + mesh.mutable_mesh()->mutable_uv()->Add(texture_coords.begin(), texture_coords.end()); + + return mesh; +} + +} // namespace + +mirabuf::Parts map_all_parts( + const adsk::core::Ptr& components, + const mirabuf::material::Materials& materials) { + mirabuf::Parts parts; + + std::vector> fusion_components; + components->copyTo(std::back_inserter(fusion_components)); + for (const auto& component : fusion_components) { + auto& part = (*parts.mutable_part_definitions())[component->id()]; + part.mutable_info()->set_name(component->name()); + part.mutable_info()->set_guid(component->id()); + part.mutable_info()->set_version(1); + part.set_dynamic(true); + + if (auto props = component->physicalProperties()) { + *part.mutable_physical_data() = map_physical_properties(props); + } + + std::vector> b_rep_bodies; + component->bRepBodies()->copyTo(std::back_inserter(b_rep_bodies)); + for (const auto& body : b_rep_bodies) { + if (!body->isLightBulbOn()) { + continue; + } + + auto& part_body = *part.mutable_bodies()->Add(); + part_body.mutable_info()->set_name(body->name()); + // TODO: guid + part_body.mutable_info()->set_version(1); + part_body.mutable_triangle_mesh()->CopyFrom(map_b_rep_body(body)); + + if (auto appearances = materials.appearances(); // TODO: Replace the parameter + appearances.find(body->appearance()->id()) != appearances.end()) { + part_body.set_appearance_override(body->appearance()->id()); + } else { + part_body.set_appearance_override("default"); + } + } + + std::vector> mesh_bodies; + component->meshBodies()->copyTo(std::back_inserter(mesh_bodies)); + for (const auto& body : mesh_bodies) { + if (!body->isLightBulbOn()) { + continue; + } + + auto& part_body = *part.mutable_bodies()->Add(); + part_body.mutable_info()->set_name(body->name()); + part_body.mutable_info()->set_version(1); + part_body.mutable_triangle_mesh()->CopyFrom(map_mesh_body(body)); + + if (auto appearances = materials.appearances(); // TODO: Replace the parameter + appearances.find(body->appearance()->id()) != appearances.end()) { + part_body.set_appearance_override(body->appearance()->id()); + } else { + part_body.set_appearance_override("default"); + } + } + } + + return parts; +} diff --git a/isotope/src/context.cpp b/isotope/src/context.cpp index d72ae99113..e8825691ba 100644 --- a/isotope/src/context.cpp +++ b/isotope/src/context.cpp @@ -13,3 +13,7 @@ bool GlobalContext::configure() { return true; } + +bool GlobalContext::isValid() const { + return this->app && this->ui && this->app->isValid() && this->ui->isValid(); +} diff --git a/isotope/src/isotope.cpp b/isotope/src/isotope.cpp index d31d7be12a..96501969f3 100644 --- a/isotope/src/isotope.cpp +++ b/isotope/src/isotope.cpp @@ -1,22 +1,22 @@ +#include "config_command.h" +#include "context.h" + #include -#include -#include -#include +#include +#include +#include +#include #include -#include -#include -#include -#include -#include #include #include -#include -#include -#include -#include - -#include "context.h" -#include "config_command.h" +#include +#include +#include +#include +#include +#include +#include +#include #include @@ -28,7 +28,7 @@ extern "C" XI_EXPORT bool run(const char* context) { } adsk::core::Ptr workspace = gctx.ui->workspaces()->itemById("FusionSolidEnvironment"); - adsk::core::Ptr tab = workspace->toolbarTabs()->itemById("ToolsTab"); + adsk::core::Ptr tab = workspace->toolbarTabs()->itemById("ToolsTab"); assert(workspace); assert(tab); @@ -36,18 +36,14 @@ extern "C" XI_EXPORT bool run(const char* context) { tab->toolbarPanels()->add("isotope_tool_tab", "Isotope"); auto button = gctx.ui->commandDefinitions()->addButtonDefinition( - "isotope_command", - "Isotope", - "This command does something interesting.", - "./resources/isotope_exporter/" - ); + "isotope_command", "Isotope", "This command does something interesting.", "./resources/isotope_exporter/"); if (!button || !button->isValid()) { gctx.ui->messageBox("Failed to create command definition for Isotope."); return false; } - button->commandCreated()->add(new ConfigureCommandCreatedHandler(&gctx)); + button->commandCreated()->add(new ConfigureCommandCreatedHandler(gctx)); auto panel = gctx.ui->allToolbarPanels()->itemById("isotope_tool_tab"); diff --git a/isotope/src/materials.cpp b/isotope/src/materials.cpp new file mode 100644 index 0000000000..f6ac399086 --- /dev/null +++ b/isotope/src/materials.cpp @@ -0,0 +1,84 @@ +#include "materials.h" + +#include "material.pb.h" + +#include +#include + +#include + +namespace { + +mirabuf::material::Appearance default_appearance() { + mirabuf::material::Appearance appearance; + appearance.mutable_info()->set_name("Default Appearance"); + appearance.mutable_info()->set_guid("default-appearance-guid"); + appearance.mutable_info()->set_version(1); + appearance.set_roughness(0.5f); + appearance.set_metallic(0.5f); + appearance.set_specular(0.5f); + + appearance.mutable_albedo()->set_r(127); + appearance.mutable_albedo()->set_g(127); + appearance.mutable_albedo()->set_b(127); + appearance.mutable_albedo()->set_a(255); + + return appearance; +} + +mirabuf::material::Appearance map_appearance(const adsk::core::Ptr& appearance) { + mirabuf::material::Appearance new_appearance = default_appearance(); + new_appearance.mutable_info()->set_name(appearance->name()); + new_appearance.mutable_info()->set_guid(appearance->id()); + + // TODO - Map all other appearance properties + return new_appearance; +} + +mirabuf::material::PhysicalMaterial default_physical_material() { + mirabuf::material::PhysicalMaterial physical_material; + physical_material.mutable_info()->set_name("Default Physical Material"); + physical_material.mutable_info()->set_guid("default-physical-material-guid"); + physical_material.mutable_info()->set_version(1); + physical_material.set_dynamic_friction(0.5f); + physical_material.set_static_friction(0.5f); + physical_material.set_restitution(0.5f); + physical_material.set_deformable(false); + physical_material.set_mattype(mirabuf::material::PhysicalMaterial_MaterialType_METAL); + + return physical_material; +} + +mirabuf::material::PhysicalMaterial map_physical_material(const adsk::core::Ptr& material) { + mirabuf::material::PhysicalMaterial new_physical_material = default_physical_material(); + new_physical_material.mutable_info()->set_name(material->name()); + new_physical_material.mutable_info()->set_guid(material->id()); + + // TODO - Map all other physical material properties + return new_physical_material; +} + +} // namespace + +mirabuf::material::Materials map_all_materials( + const adsk::core::Ptr& design_appearances, + const adsk::core::Ptr& design_materials) { + mirabuf::material::Materials materials; + (*materials.mutable_appearances())["default"] = default_appearance(); + + std::vector> appearances; + design_appearances->copyTo(std::back_inserter(appearances)); + for (const auto& appearance : appearances) { + auto& new_appearance = (*materials.mutable_appearances())[appearance->id()]; + new_appearance = map_appearance(appearance); + } + + std::vector> physical_materials; + design_materials->copyTo(std::back_inserter(physical_materials)); + for (const auto& material : physical_materials) { + auto& new_physical_material = (*materials.mutable_physicalmaterials())[material->id()]; + new_physical_material = map_physical_material(material); + } + + return materials; +} diff --git a/isotope/src/parser.cpp b/isotope/src/parser.cpp new file mode 100644 index 0000000000..ec0c49e700 --- /dev/null +++ b/isotope/src/parser.cpp @@ -0,0 +1,63 @@ +#include "parser.h" + +#include "assembly.pb.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "materials.h" +#include "components.h" + +#include + +void export_design(const GlobalContext& gctx) { + assert(gctx.isValid()); + auto document = gctx.app->activeDocument(); + auto design = document->query()->design(); + + mirabuf::Assembly assembly; + assembly.mutable_info()->set_name(design->rootComponent()->name()); + assembly.mutable_info()->set_version(1); + assembly.mutable_info()->set_guid(design->parentDocument()->name()); + + // Determines if the exported design should be treated as a robot or field + // assembly. Currently since there is no UI present in Isotope we default to + // robot always. This could be changed if either 1) a UI is added to Isotope + // or 2) some sort of algorithmic detection is added to determine if the + // design is a robot or field assembly. + assembly.set_dynamic(true); + + // auto process_dialog = gctx.ui->createProgressDialog(); + // process_dialog->isCancelButtonShown(true); + // process_dialog->show("Exporting Design", "Exporting design to Isotope format...", 0, 100, 1); + // process_dialog->progressValue(1); + + gctx.app->userInterface()->messageBox("Exporting design to Isotope format..."); + gctx.app->userInterface()->messageBox("Mapping materials..."); + + auto appearances = design->appearances(); + auto materials = design->materials(); + assembly.mutable_data()->mutable_materials()->CopyFrom(map_all_materials(appearances, materials)); + + gctx.app->userInterface()->messageBox("Mapping components..."); + auto components = design->allComponents(); + assembly.mutable_data()->mutable_parts()->CopyFrom(map_all_parts(components, assembly.data().materials())); + + gctx.app->userInterface()->messageBox("Done"); + + // Print assembly as JSON + std::string json_output; + auto _ = google::protobuf::util::MessageToJsonString(assembly, &json_output); + gctx.app->userInterface()->messageBox("Exported assembly:\n" + json_output); +} + +void map_rigid_groups(); +void parse_component_roots(); +void populate_joints(); +void create_joint_graph(); +void build_joint_part_hierarchy(); From 30722a4b802fbef8a1251c7ae4ce31171c04e97e Mon Sep 17 00:00:00 2001 From: BrandonPacewic <92102436+BrandonPacewic@users.noreply.github.com> Date: Thu, 24 Jul 2025 15:44:20 -0700 Subject: [PATCH 06/22] fix(isotope): disable absl x86 flags --- isotope/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/isotope/CMakeLists.txt b/isotope/CMakeLists.txt index 92db35d2c7..1523d9c0a6 100644 --- a/isotope/CMakeLists.txt +++ b/isotope/CMakeLists.txt @@ -8,6 +8,9 @@ if(NOT APPLE) message(FATAL_ERROR "This project is only supported on macOS.") endif() +set(ABSL_RANDOM_HWAES_DISABLE_HW_FEATURES TRUE) +set(ABSL_RANDOM_HWAES_DISABLE_AESNI TRUE) + set(USER_APP_DATA_DIR "$ENV{HOME}/Library/Application Support") set(USER_APP_DATA_RELATIVE_PATH "Autodesk/Autodesk Fusion 360/API") set(FUSION_API_DIR "${USER_APP_DATA_DIR}/${USER_APP_DATA_RELATIVE_PATH}") From c695aef836495d4887022ecb78548af583bb252f Mon Sep 17 00:00:00 2001 From: BrandonPacewic <92102436+BrandonPacewic@users.noreply.github.com> Date: Tue, 29 Jul 2025 15:47:20 -0700 Subject: [PATCH 07/22] feat(isotope): working joint parser --- isotope/inc/components.h | 11 +- isotope/inc/joints.h | 14 +++ isotope/inc/materials.h | 7 +- isotope/src/components.cpp | 224 +++++++++++++++++++++++---------- isotope/src/config_command.cpp | 12 +- isotope/src/joints.cpp | 106 ++++++++++++++++ isotope/src/materials.cpp | 95 +++++++------- isotope/src/parser.cpp | 37 +++++- 8 files changed, 377 insertions(+), 129 deletions(-) create mode 100644 isotope/inc/joints.h create mode 100644 isotope/src/joints.cpp diff --git a/isotope/inc/components.h b/isotope/inc/components.h index d33c1c832c..d41e20d06e 100644 --- a/isotope/inc/components.h +++ b/isotope/inc/components.h @@ -2,13 +2,16 @@ #ifndef ISOTOPE_COMPONENTS_H_ #define ISOTOPE_COMPONENTS_H_ +#include "assembly.pb.h" +#include "types.pb.h" + #include #include -#include "assembly.pb.h" +mirabuf::Parts map_all_parts(const adsk::core::Ptr& components, + const mirabuf::material::Materials& materials); // TODO: Replace parameter with appearance map -mirabuf::Parts map_all_parts( - const adsk::core::Ptr& components, - const mirabuf::material::Materials& materials); +mirabuf::Node parse_component_root( + const adsk::core::Ptr& component, mirabuf::Parts* parts); #endif // ISOTOPE_COMPONENTS_H_ diff --git a/isotope/inc/joints.h b/isotope/inc/joints.h new file mode 100644 index 0000000000..37349eeaa1 --- /dev/null +++ b/isotope/inc/joints.h @@ -0,0 +1,14 @@ +#pragma once +#ifndef ISOTOPE_JOINTS_H_ +#define ISOTOPE_JOINTS_H_ + +#include +#include + +#include "joint.pb.h" +#include "signal.pb.h" + +std::pair populate_joints( + const adsk::core::Ptr& design); + +#endif // ISOTOPE_JOINTS_H_ diff --git a/isotope/inc/materials.h b/isotope/inc/materials.h index a079e919d4..1d0c4b40bb 100644 --- a/isotope/inc/materials.h +++ b/isotope/inc/materials.h @@ -2,14 +2,13 @@ #ifndef ISOTOPE_MATERIALS_H_ #define ISOTOPE_MATERIALS_H_ -#include - #include "material.pb.h" +#include + #include -mirabuf::material::Materials map_all_materials( - const adsk::core::Ptr& design_appearances, +mirabuf::material::Materials map_all_materials(const adsk::core::Ptr& design_appearances, const adsk::core::Ptr& design_materials); #endif // ISOTOPE_MATERIALS_H_ diff --git a/isotope/src/components.cpp b/isotope/src/components.cpp index b543b01064..748c712f7e 100644 --- a/isotope/src/components.cpp +++ b/isotope/src/components.cpp @@ -1,95 +1,96 @@ #include "components.h" -#include - +#include "Core/Geometry/Matrix3D.h" #include "assembly.pb.h" #include "types.pb.h" +#include + namespace { -mirabuf::PhysicalProperties map_physical_properties(const adsk::core::Ptr& properties) { - mirabuf::PhysicalProperties new_properties; - new_properties.set_mass(properties->mass()); - new_properties.set_volume(properties->volume()); - new_properties.set_density(properties->density()); - new_properties.set_area(properties->area()); - if (auto com = properties->centerOfMass()) { - if (auto vec = com->asVector()) { - new_properties.mutable_com()->set_x(vec->x()); - new_properties.mutable_com()->set_y(vec->y()); - new_properties.mutable_com()->set_z(vec->z()); + mirabuf::PhysicalProperties map_physical_properties( + const adsk::core::Ptr& properties) { + mirabuf::PhysicalProperties new_properties; + new_properties.set_mass(properties->mass()); + new_properties.set_volume(properties->volume()); + new_properties.set_density(properties->density()); + new_properties.set_area(properties->area()); + if (auto com = properties->centerOfMass()) { + if (auto vec = com->asVector()) { + new_properties.mutable_com()->set_x(vec->x()); + new_properties.mutable_com()->set_y(vec->y()); + new_properties.mutable_com()->set_z(vec->z()); + } } + + return new_properties; } - return new_properties; -} + mirabuf::TriangleMesh map_b_rep_body(const adsk::core::Ptr& body) { + // auto calc = body->meshManager()->createMeshCalculator(); + auto mesh_mgr = body->meshManager(); + if (!mesh_mgr) { + return {}; + } -mirabuf::TriangleMesh map_b_rep_body(const adsk::core::Ptr& body) { - // auto calc = body->meshManager()->createMeshCalculator(); - auto mesh_mgr = body->meshManager(); - if (!mesh_mgr) { - return {}; - } + auto calc = mesh_mgr->createMeshCalculator(); + if (!calc) { + return {}; + } - auto calc = mesh_mgr->createMeshCalculator(); - if (!calc) { - return {}; - } + calc->setQuality(adsk::fusion::TriangleMeshQualityOptions::LowQualityTriangleMesh); + auto fus_mesh = calc->calculate(); + if (!fus_mesh) { + return {}; + } - calc->setQuality(adsk::fusion::TriangleMeshQualityOptions::LowQualityTriangleMesh); - auto fus_mesh = calc->calculate(); - if (!fus_mesh) { - return {}; - } + mirabuf::TriangleMesh mesh; + mesh.mutable_info()->set_name(body->name()); + // TODO: Info guid + mesh.mutable_info()->set_version(1); + mesh.set_has_volume(true); - mirabuf::TriangleMesh mesh; - mesh.mutable_info()->set_name(body->name()); - // TODO: Info guid - mesh.mutable_info()->set_version(1); - mesh.set_has_volume(true); + std::vector coords = fus_mesh->nodeCoordinatesAsFloat(); + mesh.mutable_mesh()->mutable_verts()->Add(coords.begin(), coords.end()); - std::vector coords = fus_mesh->nodeCoordinatesAsFloat(); - mesh.mutable_mesh()->mutable_verts()->Add(coords.begin(), coords.end()); + std::vector normals = fus_mesh->normalVectorsAsFloat(); + mesh.mutable_mesh()->mutable_normals()->Add(normals.begin(), normals.end()); - std::vector normals = fus_mesh->normalVectorsAsFloat(); - mesh.mutable_mesh()->mutable_normals()->Add(normals.begin(), normals.end()); + std::vector node_indicies = fus_mesh->nodeIndices(); + mesh.mutable_mesh()->mutable_indices()->Add(node_indicies.begin(), node_indicies.end()); - std::vector node_indicies = fus_mesh->nodeIndices(); - mesh.mutable_mesh()->mutable_indices()->Add(node_indicies.begin(), node_indicies.end()); + std::vector texture_coords = fus_mesh->textureCoordinatesAsFloat(); + mesh.mutable_mesh()->mutable_uv()->Add(texture_coords.begin(), texture_coords.end()); - std::vector texture_coords = fus_mesh->textureCoordinatesAsFloat(); - mesh.mutable_mesh()->mutable_uv()->Add(texture_coords.begin(), texture_coords.end()); + return mesh; + } - return mesh; -} + mirabuf::TriangleMesh map_mesh_body(const adsk::core::Ptr& body) { + auto fus_mesh = body->displayMesh(); -mirabuf::TriangleMesh map_mesh_body(const adsk::core::Ptr& body) { - auto fus_mesh = body->displayMesh(); + mirabuf::TriangleMesh mesh; + // TODO: Info crap (this is getting annoying) + mesh.set_has_volume(true); - mirabuf::TriangleMesh mesh; - // TODO: Info crap (this is getting annoying) - mesh.set_has_volume(true); - - std::vector coords = fus_mesh->nodeCoordinatesAsFloat(); - mesh.mutable_mesh()->mutable_verts()->Add(coords.begin(), coords.end()); + std::vector coords = fus_mesh->nodeCoordinatesAsFloat(); + mesh.mutable_mesh()->mutable_verts()->Add(coords.begin(), coords.end()); - std::vector normals = fus_mesh->normalVectorsAsFloat(); - mesh.mutable_mesh()->mutable_normals()->Add(normals.begin(), normals.end()); + std::vector normals = fus_mesh->normalVectorsAsFloat(); + mesh.mutable_mesh()->mutable_normals()->Add(normals.begin(), normals.end()); - std::vector node_indicies = fus_mesh->nodeIndices(); - mesh.mutable_mesh()->mutable_indices()->Add(node_indicies.begin(), node_indicies.end()); + std::vector node_indicies = fus_mesh->nodeIndices(); + mesh.mutable_mesh()->mutable_indices()->Add(node_indicies.begin(), node_indicies.end()); - std::vector texture_coords = fus_mesh->textureCoordinatesAsFloat(); - mesh.mutable_mesh()->mutable_uv()->Add(texture_coords.begin(), texture_coords.end()); + std::vector texture_coords = fus_mesh->textureCoordinatesAsFloat(); + mesh.mutable_mesh()->mutable_uv()->Add(texture_coords.begin(), texture_coords.end()); - return mesh; -} + return mesh; + } } // namespace mirabuf::Parts map_all_parts( - const adsk::core::Ptr& components, - const mirabuf::material::Materials& materials) { + const adsk::core::Ptr& components, const mirabuf::material::Materials& materials) { mirabuf::Parts parts; std::vector> fusion_components; @@ -149,3 +150,100 @@ mirabuf::Parts map_all_parts( return parts; } + +namespace { + adsk::core::Ptr get_matrix_world(const adsk::core::Ptr& occurrence) { + if (!occurrence) { + return nullptr; + } + + auto matrix = occurrence->transform2()->copy(); + auto next_occurrence = occurrence; + while (next_occurrence->assemblyContext()) { + matrix->transformBy(next_occurrence->assemblyContext()->transform2()); + next_occurrence = next_occurrence->assemblyContext(); + } + + return matrix; + } + + mirabuf::Node parse_child_occurrence( + const adsk::core::Ptr& occurrence, mirabuf::Parts* parts) { + assert(occurrence->isLightBulbOn()); + + mirabuf::Node node; + // TODO: Info stuff + + auto part = parts->mutable_part_instances()->find(occurrence->component()->id()); + + // set top info + + // set occurrence appearance + if (occurrence->appearance()) { + auto appearance_id = occurrence->appearance()->id(); + part->second.set_appearance(appearance_id); + } + + // set part physical material + if (auto material = occurrence->component()->material()) { + part->second.set_physical_material(material->id()); + } + + // set part spatial matrix + part->second.mutable_transform()->mutable_spatial_matrix()->Add( + occurrence->transform()->asArray().begin(), occurrence->transform()->asArray().end()); + + // set part global transform + // auto world_transform = get_matrix_world(occurrence); + // if (world_transform) { + // part->second.mutable_global_transform()->mutable_spatial_matrix()->Add( + // world_transform->asArray().begin(), world_transform->asArray().end()); + // } + + // final recursive step to parse child occurrences + std::vector> child_occurrences; + occurrence->childOccurrences()->copyTo(std::back_inserter(child_occurrences)); + for (const auto& child_occurrence : child_occurrences) { + if (!child_occurrence->isLightBulbOn()) { + continue; + } + + auto child_node = parse_child_occurrence(child_occurrence, parts); + node.mutable_children()->Add()->CopyFrom(child_node); + } + + return node; + } + +} // namespace + +mirabuf::Node parse_component_root(const adsk::core::Ptr& component, mirabuf::Parts* parts) { + mirabuf::Node root_node; + root_node.set_value(component->id()); + + // TODO: Info stuff + + auto part = parts->mutable_part_instances()->find(component->id()); + if (part == parts->part_instances().end()) { + return root_node; // this is problematic + } + + // find out if this is necessary as we are editing objects in place which i really wanted to avoid + auto part_defs = parts->part_definitions(); + if (part_defs.find(component->id()) != part_defs.end()) { + part->second.set_part_definition_reference(component->id()); + } + + std::vector> child_occurrences; + component->occurrences()->copyTo(std::back_inserter(child_occurrences)); + for (const auto& child_occurrence : child_occurrences) { + if (!child_occurrence->isLightBulbOn()) { + continue; + } + + auto child_node = parse_child_occurrence(child_occurrence, parts); + root_node.mutable_children()->Add()->CopyFrom(child_node); + } + + return root_node; +} diff --git a/isotope/src/config_command.cpp b/isotope/src/config_command.cpp index 64767e2a82..47660366dc 100644 --- a/isotope/src/config_command.cpp +++ b/isotope/src/config_command.cpp @@ -1,19 +1,23 @@ #include "config_command.h" -#include +#include "parser.h" + #include +#include #include void ConfigureCommandCreatedHandler::notify(const adsk::core::Ptr& args) { + assert(this->gctx.isValid()); adsk::core::Ptr command = args->command(); if (!command || !command->isValid()) { - gctx->ui->messageBox("Invalid command in ConfigureCommandCreatedHandler."); + this->gctx.ui->messageBox("Invalid command in ConfigureCommandCreatedHandler."); return; } - command->execute()->add(new ConfigureCommandExecutedHandler(gctx)); + command->execute()->add(new ConfigureCommandExecutedHandler(this->gctx)); } void ConfigureCommandExecutedHandler::notify(const adsk::core::Ptr& eventArgs) { - gctx->ui->messageBox("Configure command executed successfully."); + assert(this->gctx.isValid()); + export_design(this->gctx); } diff --git a/isotope/src/joints.cpp b/isotope/src/joints.cpp new file mode 100644 index 0000000000..48f9503e3d --- /dev/null +++ b/isotope/src/joints.cpp @@ -0,0 +1,106 @@ +#include "joints.h" + +#include "assembly.pb.h" +#include "joint.pb.h" +#include "signal.pb.h" +#include + +namespace { + +mirabuf::joint::RigidGroup map_rigid_group(const adsk::fusion::Joint* /* adsk::fusion::joint | adsk::fusion::AsBuiltJoint */ joint +) { + assert(joint); + assert(joint->jointMotion()->jointType() == adsk::fusion::JointTypes::RigidJointType); + + if (!joint->occurrenceOne()->isLightBulbOn() || + !joint->occurrenceTwo()->isLightBulbOn()) { + return {}; + } + + mirabuf::joint::RigidGroup group; + std::string group_name = "group_" + joint->occurrenceOne()->name() + "_" + + joint->occurrenceTwo()->name(); + group.set_name(group_name); + group.add_occurrences(joint->occurrenceOne()->name()); + group.add_occurrences(joint->occurrenceTwo()->name()); + + return group; +} + +} // namespace + +std::pair populate_joints( + const adsk::core::Ptr& design) { + assert(design); + mirabuf::joint::Joints joints; + joints.mutable_info()->set_name(""); + joints.mutable_info()->set_guid("joints-guid"); + joints.mutable_info()->set_version(1); + + mirabuf::signal::Signals signals; + // auto& part = (*parts.mutable_part_definitions())[component->id()]; + + auto& joint_definition_ground = (*joints.mutable_joint_definitions())["grounded"]; + joint_definition_ground.mutable_info()->set_name("grounded"); + // todo: other info stuff + + auto& joint_instance_ground = (*joints.mutable_joint_instances())["grounded"]; + joint_instance_ground.mutable_info()->set_name("grounded"); + // todo: other info stuff + + joint_instance_ground.set_joint_reference(joint_definition_ground.info().guid()); + + auto process_joint = [&joints, &signals](const adsk::fusion::Joint* /* adsk::fusion::joint | adsk::fusion::AsBuiltJoint */ joint) { + assert(joint); + if (joint->isSuppressed()) { + return; + } + + auto motion = joint->jointMotion(); + if (motion->jointType() == adsk::fusion::JointTypes::RigidJointType) { + auto rigidGroup = map_rigid_group(joint); + if (!rigidGroup.occurrences().empty()) { + joints.mutable_rigid_groups()->Add()->CopyFrom(rigidGroup); + } + } + + auto& signal = (*signals.mutable_signal_map())[joint->name()]; + signal.mutable_info()->set_name(joint->name()); + signal.mutable_info()->set_guid(joint->name()); + signal.mutable_info()->set_version(1); + signal.set_io(mirabuf::signal::IOType::OUTPUT); + signal.set_device_type(mirabuf::signal::DeviceType::PWM); + + auto& joint_instance = (*joints.mutable_joint_instances())[joint->name()]; + joint_instance.set_signal_reference(signal.info().guid()); + joint_instance.set_parent_part(joint->occurrenceOne()->name()); + joint_instance.set_child_part(joint->occurrenceTwo()->name()); + + // TODO: Wheel logic should go here + + auto& joint_definition = (*joints.mutable_joint_definitions())[joint->name()]; + joint_definition.set_motor_reference(signal.info().guid()); + + auto& motor = (*joints.mutable_motor_definitions())[joint->name()]; + auto simple_motor = motor.mutable_simple_motor(); + // These are values I just chose on a whim, they need to be checked and changed to make sure + // everything works correctly. + simple_motor->set_stall_torque(0.5f); + simple_motor->set_max_velocity(1.0f); + simple_motor->set_braking_constant(0.8f); + + // TODO: Motion info depending on joint type + }; + + for (const auto& joint : design->rootComponent()->allJoints()) { + process_joint(joint.get()); + } + + for (const auto& asBuiltJoint : design->rootComponent()->allAsBuiltJoints()) { + // TODO: Replace adsk::fusion::Joint* with auto to make this function call valid + // the compiler will make two instances of the lambda, one for each type + // process_joint(asBuiltJoint.get()); + } + + return { joints, signals }; +} diff --git a/isotope/src/materials.cpp b/isotope/src/materials.cpp index f6ac399086..d2daf36919 100644 --- a/isotope/src/materials.cpp +++ b/isotope/src/materials.cpp @@ -9,59 +9,58 @@ namespace { -mirabuf::material::Appearance default_appearance() { - mirabuf::material::Appearance appearance; - appearance.mutable_info()->set_name("Default Appearance"); - appearance.mutable_info()->set_guid("default-appearance-guid"); - appearance.mutable_info()->set_version(1); - appearance.set_roughness(0.5f); - appearance.set_metallic(0.5f); - appearance.set_specular(0.5f); - - appearance.mutable_albedo()->set_r(127); - appearance.mutable_albedo()->set_g(127); - appearance.mutable_albedo()->set_b(127); - appearance.mutable_albedo()->set_a(255); - - return appearance; -} + mirabuf::material::Appearance default_appearance() { + mirabuf::material::Appearance appearance; + appearance.mutable_info()->set_name("Default Appearance"); + appearance.mutable_info()->set_guid("default-appearance-guid"); + appearance.mutable_info()->set_version(1); + appearance.set_roughness(0.5f); + appearance.set_metallic(0.5f); + appearance.set_specular(0.5f); + + appearance.mutable_albedo()->set_r(127); + appearance.mutable_albedo()->set_g(127); + appearance.mutable_albedo()->set_b(127); + appearance.mutable_albedo()->set_a(255); + + return appearance; + } -mirabuf::material::Appearance map_appearance(const adsk::core::Ptr& appearance) { - mirabuf::material::Appearance new_appearance = default_appearance(); - new_appearance.mutable_info()->set_name(appearance->name()); - new_appearance.mutable_info()->set_guid(appearance->id()); + mirabuf::material::Appearance map_appearance(const adsk::core::Ptr& appearance) { + mirabuf::material::Appearance new_appearance = default_appearance(); + new_appearance.mutable_info()->set_name(appearance->name()); + new_appearance.mutable_info()->set_guid(appearance->id()); - // TODO - Map all other appearance properties - return new_appearance; -} + // TODO - Map all other appearance properties + return new_appearance; + } -mirabuf::material::PhysicalMaterial default_physical_material() { - mirabuf::material::PhysicalMaterial physical_material; - physical_material.mutable_info()->set_name("Default Physical Material"); - physical_material.mutable_info()->set_guid("default-physical-material-guid"); - physical_material.mutable_info()->set_version(1); - physical_material.set_dynamic_friction(0.5f); - physical_material.set_static_friction(0.5f); - physical_material.set_restitution(0.5f); - physical_material.set_deformable(false); - physical_material.set_mattype(mirabuf::material::PhysicalMaterial_MaterialType_METAL); - - return physical_material; -} + mirabuf::material::PhysicalMaterial default_physical_material() { + mirabuf::material::PhysicalMaterial physical_material; + physical_material.mutable_info()->set_name("Default Physical Material"); + physical_material.mutable_info()->set_guid("default-physical-material-guid"); + physical_material.mutable_info()->set_version(1); + physical_material.set_dynamic_friction(0.5f); + physical_material.set_static_friction(0.5f); + physical_material.set_restitution(0.5f); + physical_material.set_deformable(false); + physical_material.set_mattype(mirabuf::material::PhysicalMaterial_MaterialType_METAL); + + return physical_material; + } -mirabuf::material::PhysicalMaterial map_physical_material(const adsk::core::Ptr& material) { - mirabuf::material::PhysicalMaterial new_physical_material = default_physical_material(); - new_physical_material.mutable_info()->set_name(material->name()); - new_physical_material.mutable_info()->set_guid(material->id()); + mirabuf::material::PhysicalMaterial map_physical_material(const adsk::core::Ptr& material) { + mirabuf::material::PhysicalMaterial new_physical_material = default_physical_material(); + new_physical_material.mutable_info()->set_name(material->name()); + new_physical_material.mutable_info()->set_guid(material->id()); - // TODO - Map all other physical material properties - return new_physical_material; -} + // TODO - Map all other physical material properties + return new_physical_material; + } } // namespace -mirabuf::material::Materials map_all_materials( - const adsk::core::Ptr& design_appearances, +mirabuf::material::Materials map_all_materials(const adsk::core::Ptr& design_appearances, const adsk::core::Ptr& design_materials) { mirabuf::material::Materials materials; (*materials.mutable_appearances())["default"] = default_appearance(); @@ -70,15 +69,15 @@ mirabuf::material::Materials map_all_materials( design_appearances->copyTo(std::back_inserter(appearances)); for (const auto& appearance : appearances) { auto& new_appearance = (*materials.mutable_appearances())[appearance->id()]; - new_appearance = map_appearance(appearance); + new_appearance = map_appearance(appearance); } std::vector> physical_materials; design_materials->copyTo(std::back_inserter(physical_materials)); for (const auto& material : physical_materials) { auto& new_physical_material = (*materials.mutable_physicalmaterials())[material->id()]; - new_physical_material = map_physical_material(material); + new_physical_material = map_physical_material(material); } - + return materials; } diff --git a/isotope/src/parser.cpp b/isotope/src/parser.cpp index ec0c49e700..01c8e31e1c 100644 --- a/isotope/src/parser.cpp +++ b/isotope/src/parser.cpp @@ -1,6 +1,10 @@ #include "parser.h" #include "assembly.pb.h" +#include "components.h" +#include "materials.h" +#include "joints.h" +#include "types.pb.h" #include #include @@ -9,12 +13,10 @@ #include #include #include - -#include "materials.h" -#include "components.h" - #include +#include + void export_design(const GlobalContext& gctx) { assert(gctx.isValid()); auto document = gctx.app->activeDocument(); @@ -41,23 +43,46 @@ void export_design(const GlobalContext& gctx) { gctx.app->userInterface()->messageBox("Mapping materials..."); auto appearances = design->appearances(); - auto materials = design->materials(); + auto materials = design->materials(); assembly.mutable_data()->mutable_materials()->CopyFrom(map_all_materials(appearances, materials)); gctx.app->userInterface()->messageBox("Mapping components..."); auto components = design->allComponents(); assembly.mutable_data()->mutable_parts()->CopyFrom(map_all_parts(components, assembly.data().materials())); + gctx.app->userInterface()->messageBox("Mapping root node..."); + + mirabuf::Node root_node = parse_component_root(design->rootComponent(), assembly.mutable_data()->mutable_parts()); + assembly.mutable_design_hierarchy()->mutable_nodes()->Add()->CopyFrom(root_node); + + gctx.app->userInterface()->messageBox("Mapping joints..."); + const auto [joints, signals] = populate_joints(design); + + assembly.mutable_data()->mutable_joints()->CopyFrom(joints); + assembly.mutable_data()->mutable_signals()->CopyFrom(signals); + gctx.app->userInterface()->messageBox("Done"); // Print assembly as JSON std::string json_output; auto _ = google::protobuf::util::MessageToJsonString(assembly, &json_output); + + std::string path = std::getenv("HOME") + std::string("/Desktop/assembly_debug.json"); + + // std::ofstream output_file("~/Documents/Repos/synthesis/isotope/build/assembly.json"); + std::ofstream output_file(path); + if (!output_file.is_open()) { + gctx.app->userInterface()->messageBox("Failed to open output file for writing."); + return; + } + + output_file << json_output; + output_file.close(); + gctx.app->userInterface()->messageBox("Exported assembly:\n" + json_output); } void map_rigid_groups(); -void parse_component_roots(); void populate_joints(); void create_joint_graph(); void build_joint_part_hierarchy(); From 488b6766fdada188d8e30baa573998cd9c5a8bc4 Mon Sep 17 00:00:00 2001 From: BrandonPacewic <92102436+BrandonPacewic@users.noreply.github.com> Date: Tue, 5 Aug 2025 10:00:54 -0700 Subject: [PATCH 08/22] feat(isotope): proper joint motion and joint origin parsing --- isotope/inc/util.h | 61 +++++++++++++ isotope/src/joints.cpp | 202 ++++++++++++++++++++++++++++++++++++++++- isotope/src/parser.cpp | 33 ++----- 3 files changed, 266 insertions(+), 30 deletions(-) create mode 100644 isotope/inc/util.h diff --git a/isotope/inc/util.h b/isotope/inc/util.h new file mode 100644 index 0000000000..b91763ed5a --- /dev/null +++ b/isotope/inc/util.h @@ -0,0 +1,61 @@ +#pragma once +#ifndef ISOTOPE_UTILITY_H_ +#define ISOTOPE_UTILITY_H_ + +#include +#include +#include + +#include +#include + +template +struct FusionTypeName; + +#define DEFINE_FUSION_TYPE_NAME(type) \ + template<> \ + struct FusionTypeName { \ + static constexpr std::string_view value = #type; \ + } + +DEFINE_FUSION_TYPE_NAME(adsk::fusion::JointGeometry); +DEFINE_FUSION_TYPE_NAME(adsk::fusion::JointOrigin); +DEFINE_FUSION_TYPE_NAME(adsk::fusion::BRepEdge); +DEFINE_FUSION_TYPE_NAME(adsk::fusion::BRepFace); + +template +const T* fusion_try_cast(const adsk::core::Base* base) { + if (!base) { + return nullptr; + } else if (std::string_view(base->objectType()) == FusionTypeName::value) { + return dynamic_cast(base); + } else { + return nullptr; + } +} + +template +VariantT fusion_base_to_variant_impl(const adsk::core::Base* raw) { + if (auto* p = fusion_try_cast(raw)) { + return VariantT{std::in_place_type, p}; + } else if constexpr (sizeof...(Ts) > 0) { + return fusion_base_to_variant_impl(raw); + } else { + return VariantT{std::in_place_type}; + } +} + +template +std::variant fusion_base_to_variant(const adsk::core::Base* base) { + using VariantT = std::variant; + if (!base) { + return std::variant{}; + } + + return fusion_base_to_variant_impl(base); +} + +template struct overloaded : Ts... { using Ts::operator()...; }; +template overloaded(Ts...) -> overloaded; + +#endif // ISOTOPE_UTILITY_H_ diff --git a/isotope/src/joints.cpp b/isotope/src/joints.cpp index 48f9503e3d..d3651bae99 100644 --- a/isotope/src/joints.cpp +++ b/isotope/src/joints.cpp @@ -1,9 +1,27 @@ #include "joints.h" +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "assembly.pb.h" #include "joint.pb.h" #include "signal.pb.h" -#include +#include "types.pb.h" + +#include "util.h" + +#include +#include namespace { @@ -27,6 +45,165 @@ mirabuf::joint::RigidGroup map_rigid_group(const adsk::fusion::Joint* /* adsk::f return group; } +void fill_revolute_joint_motion(const adsk::core::Ptr& motion, mirabuf::joint::Joint* proto_joint) { + assert(motion); + assert(proto_joint); + + proto_joint->set_joint_motion_type(mirabuf::joint::JointMotion::REVOLUTE); + auto dof = proto_joint->mutable_rotational()->mutable_rotational_freedom(); + dof->set_name("Rotational Joint"); + dof->set_value(motion->rotationValue()); + if (motion->rotationLimits()) { + dof->mutable_limits()->set_lower(motion->rotationLimits()->minimumValue()); + dof->mutable_limits()->set_upper(motion->rotationLimits()->maximumValue()); + } + + auto rotation_axis_vector = motion->rotationAxisVector(); + if (rotation_axis_vector) { + dof->mutable_axis()->set_x(rotation_axis_vector->x()); + dof->mutable_axis()->set_y(rotation_axis_vector->y()); + dof->mutable_axis()->set_z(rotation_axis_vector->z()); + } else { + auto rotation_axis = motion->rotationAxis(); + assert(rotation_axis); + dof->mutable_axis()->set_x((int) rotation_axis == 0); + dof->mutable_axis()->set_y((int) rotation_axis == 2); + dof->mutable_axis()->set_z((int) rotation_axis == 1); + } +} + +void fill_slider_joint_motion(const adsk::core::Ptr& motion, mirabuf::joint::Joint* proto_joint) { + assert(motion); + assert(proto_joint); + + proto_joint->set_joint_motion_type(mirabuf::joint::JointMotion::SLIDER); + auto dof = proto_joint->mutable_prismatic()->mutable_prismatic_freedom(); + dof->mutable_axis()->set_x(-motion->slideDirectionVector()->x()); + dof->mutable_axis()->set_y(-motion->slideDirectionVector()->y()); + dof->mutable_axis()->set_z(-motion->slideDirectionVector()->z()); + + switch (motion->slideDirection()) { + case adsk::fusion::JointDirections::XAxisJointDirection: + dof->set_pivotdirection(mirabuf::Axis::X); + break; + case adsk::fusion::JointDirections::YAxisJointDirection: + dof->set_pivotdirection(mirabuf::Axis::Y); + break; + case adsk::fusion::JointDirections::ZAxisJointDirection: + dof->set_pivotdirection(mirabuf::Axis::Z); + break; + case adsk::fusion::JointDirections::CustomJointDirection: + default: + break; + } + + if (motion->slideLimits()) { + dof->mutable_limits()->set_lower(motion->slideLimits()->minimumValue()); + dof->mutable_limits()->set_upper(motion->slideLimits()->maximumValue()); + } + + dof->set_value(motion->slideValue()); +} + +void fill_motion_from_joint(const adsk::core::Ptr& motion, mirabuf::joint::Joint* proto_joint) { + assert(motion); + assert(proto_joint); + + switch (motion->jointType()) { + case adsk::fusion::JointTypes::RevoluteJointType: + fill_revolute_joint_motion(adsk::core::Ptr(motion), proto_joint); + break; + case adsk::fusion::JointTypes::SliderJointType: + fill_slider_joint_motion(adsk::core::Ptr(motion), proto_joint); + break; + case adsk::fusion::JointTypes::RigidJointType: + proto_joint->set_joint_motion_type(mirabuf::joint::JointMotion::RIGID); + break; + case adsk::fusion::JointTypes::CylindricalJointType: + case adsk::fusion::JointTypes::BallJointType: + case adsk::fusion::JointTypes::PinSlotJointType: + case adsk::fusion::JointTypes::PlanarJointType: + case adsk::fusion::JointTypes::InferredJointType: + default: + break; + } +} + +adsk::core::Ptr origin_from_joint_geometry(const adsk::fusion::JointGeometry* geometry, const adsk::core::Ptr occurrence) { + if (!geometry) { + return adsk::core::Point3D::create(); + } + + + auto entity_one = geometry->entityOne(); + if (!entity_one) { + return adsk::core::Point3D::create(); + } + + auto edge_or_face = fusion_base_to_variant(entity_one.get()); + adsk::core::Ptr result = std::visit(overloaded{ + [&geometry](std::monostate) -> auto { + return geometry->origin(); + }, + [&geometry, &occurrence](const adsk::fusion::BRepEdge* edge) -> auto { + if (!edge->assemblyContext()) { + auto new_entity = edge->createForAssemblyContext(occurrence); + auto min = new_entity->boundingBox()->minPoint(); + auto max = new_entity->boundingBox()->maxPoint(); + auto org = adsk::core::Point3D::create((max->x() + min->x()) / 2.0f, (max->y() + min->y()) / 2.0f, (max->z() + min->z()) / 2.0f); + return org; + } + + return geometry->origin(); + }, + [&geometry, &occurrence](const adsk::fusion::BRepFace* face) -> auto { + if (!face->assemblyContext()) { + auto new_entity = face->createForAssemblyContext(occurrence); + return new_entity->centroid(); + } + + return geometry->origin(); + } + }, edge_or_face); + + return result; +} + +adsk::core::Ptr origin_from_joint_origin(const adsk::fusion::JointOrigin* joint_origin) { + if (!joint_origin) { + return adsk::core::Point3D::create(); + } + + auto origin = joint_origin->geometry()->origin(); + double offset_x = joint_origin->offsetX() ? joint_origin->offsetX()->value() : 0; + double offset_y = joint_origin->offsetY() ? joint_origin->offsetY()->value() : 0; + double offset_z = joint_origin->offsetZ() ? joint_origin->offsetZ()->value() : 0; + return adsk::core::Point3D::create(origin->x() + offset_x, origin->y() + offset_y, origin->z() + offset_z); +} + +adsk::core::Ptr get_joint_origin(const adsk::fusion::Joint* fusion_joint) { + assert(fusion_joint); + auto raw_geo_test = fusion_joint->geometryOrOriginOne(); + auto geometry_or_origin = fusion_base_to_variant(raw_geo_test.get()); + if (std::holds_alternative(geometry_or_origin)) { + return adsk::core::Point3D::create(); + } + + adsk::core::Ptr result = std::visit(overloaded{ + [](std::monostate) -> auto { + return adsk::core::Point3D::create(); + }, + [&fusion_joint](const adsk::fusion::JointGeometry* geometry) -> auto { + return origin_from_joint_geometry(geometry, fusion_joint->occurrenceOne()); + }, + [](const adsk::fusion::JointOrigin* origin) -> auto { + return origin_from_joint_origin(origin); + } + }, geometry_or_origin); + + return result; +} + } // namespace std::pair populate_joints( @@ -38,15 +215,12 @@ std::pair populate_joints( joints.mutable_info()->set_version(1); mirabuf::signal::Signals signals; - // auto& part = (*parts.mutable_part_definitions())[component->id()]; auto& joint_definition_ground = (*joints.mutable_joint_definitions())["grounded"]; joint_definition_ground.mutable_info()->set_name("grounded"); - // todo: other info stuff auto& joint_instance_ground = (*joints.mutable_joint_instances())["grounded"]; joint_instance_ground.mutable_info()->set_name("grounded"); - // todo: other info stuff joint_instance_ground.set_joint_reference(joint_definition_ground.info().guid()); @@ -80,16 +254,34 @@ std::pair populate_joints( auto& joint_definition = (*joints.mutable_joint_definitions())[joint->name()]; joint_definition.set_motor_reference(signal.info().guid()); + joint_definition.mutable_info()->set_name(joint->name()); + joint_definition.mutable_info()->set_guid(joint->name()); + joint_definition.mutable_info()->set_version(1); + + auto joint_origin = get_joint_origin(joint); + + if (joint_origin) { + joint_definition.mutable_origin()->set_x(joint_origin->x()); + joint_definition.mutable_origin()->set_y(joint_origin->y()); + joint_definition.mutable_origin()->set_z(joint_origin->z()); + } else { + joint_definition.mutable_origin()->set_x(0.0f); + joint_definition.mutable_origin()->set_y(0.0f); + joint_definition.mutable_origin()->set_z(0.0f); + } + + joint_definition.set_break_magnitude(0.0f); auto& motor = (*joints.mutable_motor_definitions())[joint->name()]; auto simple_motor = motor.mutable_simple_motor(); + // These are values I just chose on a whim, they need to be checked and changed to make sure // everything works correctly. simple_motor->set_stall_torque(0.5f); simple_motor->set_max_velocity(1.0f); simple_motor->set_braking_constant(0.8f); - // TODO: Motion info depending on joint type + fill_motion_from_joint(motion, &joint_definition); }; for (const auto& joint : design->rootComponent()->allJoints()) { diff --git a/isotope/src/parser.cpp b/isotope/src/parser.cpp index 01c8e31e1c..38e4ae1093 100644 --- a/isotope/src/parser.cpp +++ b/isotope/src/parser.cpp @@ -1,11 +1,5 @@ #include "parser.h" -#include "assembly.pb.h" -#include "components.h" -#include "materials.h" -#include "joints.h" -#include "types.pb.h" - #include #include #include @@ -15,6 +9,13 @@ #include #include +#include "assembly.pb.h" +#include "types.pb.h" + +#include "joints.h" +#include "components.h" +#include "materials.h" + #include void export_design(const GlobalContext& gctx) { @@ -34,43 +35,26 @@ void export_design(const GlobalContext& gctx) { // design is a robot or field assembly. assembly.set_dynamic(true); - // auto process_dialog = gctx.ui->createProgressDialog(); - // process_dialog->isCancelButtonShown(true); - // process_dialog->show("Exporting Design", "Exporting design to Isotope format...", 0, 100, 1); - // process_dialog->progressValue(1); - - gctx.app->userInterface()->messageBox("Exporting design to Isotope format..."); - gctx.app->userInterface()->messageBox("Mapping materials..."); - auto appearances = design->appearances(); auto materials = design->materials(); assembly.mutable_data()->mutable_materials()->CopyFrom(map_all_materials(appearances, materials)); - gctx.app->userInterface()->messageBox("Mapping components..."); auto components = design->allComponents(); assembly.mutable_data()->mutable_parts()->CopyFrom(map_all_parts(components, assembly.data().materials())); - gctx.app->userInterface()->messageBox("Mapping root node..."); - mirabuf::Node root_node = parse_component_root(design->rootComponent(), assembly.mutable_data()->mutable_parts()); assembly.mutable_design_hierarchy()->mutable_nodes()->Add()->CopyFrom(root_node); - gctx.app->userInterface()->messageBox("Mapping joints..."); const auto [joints, signals] = populate_joints(design); assembly.mutable_data()->mutable_joints()->CopyFrom(joints); assembly.mutable_data()->mutable_signals()->CopyFrom(signals); - gctx.app->userInterface()->messageBox("Done"); - // Print assembly as JSON std::string json_output; auto _ = google::protobuf::util::MessageToJsonString(assembly, &json_output); - std::string path = std::getenv("HOME") + std::string("/Desktop/assembly_debug.json"); - - // std::ofstream output_file("~/Documents/Repos/synthesis/isotope/build/assembly.json"); - std::ofstream output_file(path); + std::ofstream output_file(std::getenv("HOME") + std::string("/Desktop/assembly_debug.json")); if (!output_file.is_open()) { gctx.app->userInterface()->messageBox("Failed to open output file for writing."); return; @@ -83,6 +67,5 @@ void export_design(const GlobalContext& gctx) { } void map_rigid_groups(); -void populate_joints(); void create_joint_graph(); void build_joint_part_hierarchy(); From 2f479334340efca049eb0c8506e9b20ce70c2874 Mon Sep 17 00:00:00 2001 From: BrandonPacewic <92102436+BrandonPacewic@users.noreply.github.com> Date: Tue, 5 Aug 2025 10:04:30 -0700 Subject: [PATCH 09/22] fix(isotope): revert cmake build fix --- isotope/CMakeLists.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/isotope/CMakeLists.txt b/isotope/CMakeLists.txt index 1523d9c0a6..92db35d2c7 100644 --- a/isotope/CMakeLists.txt +++ b/isotope/CMakeLists.txt @@ -8,9 +8,6 @@ if(NOT APPLE) message(FATAL_ERROR "This project is only supported on macOS.") endif() -set(ABSL_RANDOM_HWAES_DISABLE_HW_FEATURES TRUE) -set(ABSL_RANDOM_HWAES_DISABLE_AESNI TRUE) - set(USER_APP_DATA_DIR "$ENV{HOME}/Library/Application Support") set(USER_APP_DATA_RELATIVE_PATH "Autodesk/Autodesk Fusion 360/API") set(FUSION_API_DIR "${USER_APP_DATA_DIR}/${USER_APP_DATA_RELATIVE_PATH}") From 2ce8d90bfd894867ccecf3f466666525372121f6 Mon Sep 17 00:00:00 2001 From: BrandonPacewic <92102436+BrandonPacewic@users.noreply.github.com> Date: Tue, 5 Aug 2025 10:05:07 -0700 Subject: [PATCH 10/22] feat(isotope): add xcode build step for debugging --- isotope/.gitignore | 1 + isotope/Makefile | 19 +++++++++++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/isotope/.gitignore b/isotope/.gitignore index 9bf3746b2a..2c806a14bc 100644 --- a/isotope/.gitignore +++ b/isotope/.gitignore @@ -1 +1,2 @@ compile_commands.json +build* diff --git a/isotope/Makefile b/isotope/Makefile index 5a48ce82ad..99b46510dc 100644 --- a/isotope/Makefile +++ b/isotope/Makefile @@ -1,4 +1,5 @@ BUILD_DIR ?= build +XCODE_BUILD_DIR ?= build-xcode CMAKE_GENERATOR ?= Unix Makefiles BUILD_TYPE ?= Debug # Release | RelWithDebInfo ... JOBS ?= $(shell nproc 2>/dev/null || sysctl -n hw.ncpu) @@ -12,10 +13,18 @@ configure: @mkdir -p "$(BUILD_DIR)" @cmake -S . -B "$(BUILD_DIR)" $(CMAKE_FLAGS) @ln -sf "$(BUILD_DIR)/compile_commands.json" . + @ln -sf "./isotope.manifest" "$(BUILD_DIR)" build: configure @cmake --build "$(BUILD_DIR)" -- -j$(JOBS) +build-xcode: + @mkdir -p "$(XCODE_BUILD_DIR)" + @cmake -G Xcode -S . -B "$(XCODE_BUILD_DIR)" -DCMAKE_BUILD_TYPE=Debug + +open-xcode: build-xcode + @open build-xcode/isotope.xcodeproj + install: build @cmake --install "$(BUILD_DIR)" @@ -25,9 +34,11 @@ clean: help: @echo "Targets:" - @echo " configure - configure the project and generate the compilation rules json" - @echo " build - configure and build" - @echo " install - install the built project into your fusion instillation" - @echo " clean - remove build directory and prep for a fresh build" + @echo " configure - configure the project and generate the compilation rules json" + @echo " build - configure and build" + @echo " install - install the built project into your fusion instillation" + @echo " build-xcode - configure and build the xcode project for debugging" + @echo " open-xcode - open the built xcode project in xcode" + @echo " clean - remove build directory and prep for a fresh build" .DEFAULT_GOAL := build From d93b7546384dc133a06e982cafade5d133ff5831 Mon Sep 17 00:00:00 2001 From: BrandonPacewic <92102436+BrandonPacewic@users.noreply.github.com> Date: Tue, 5 Aug 2025 11:56:52 -0700 Subject: [PATCH 11/22] feat(isotope): joint hierarchy parsing --- isotope/inc/joints.h | 3 +++ isotope/src/joints.cpp | 29 +++++++++++++++++++++++++++++ isotope/src/parser.cpp | 5 +++-- 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/isotope/inc/joints.h b/isotope/inc/joints.h index 37349eeaa1..e9d865ae85 100644 --- a/isotope/inc/joints.h +++ b/isotope/inc/joints.h @@ -7,8 +7,11 @@ #include "joint.pb.h" #include "signal.pb.h" +#include "types.pb.h" std::pair populate_joints( const adsk::core::Ptr& design); +mirabuf::GraphContainer create_joint_graph(const mirabuf::joint::Joints& joints); + #endif // ISOTOPE_JOINTS_H_ diff --git a/isotope/src/joints.cpp b/isotope/src/joints.cpp index d3651bae99..fda2d2c4f5 100644 --- a/isotope/src/joints.cpp +++ b/isotope/src/joints.cpp @@ -22,6 +22,7 @@ #include #include +#include namespace { @@ -296,3 +297,31 @@ std::pair populate_joints( return { joints, signals }; } + +mirabuf::GraphContainer create_joint_graph(const mirabuf::joint::Joints& joints) { + std::unordered_map nodes; + auto ground_node = mirabuf::Node(); + ground_node.set_value("ground"); + nodes[ground_node.value()] = ground_node; + + for (const auto& [_, joint] : joints.joint_definitions()) { + if (joint.info().guid().length()) { + auto new_node = mirabuf::Node(); + new_node.set_value(joint.info().guid()); + nodes[new_node.value()] = new_node; + } + } + + for (const auto& [_, joint] : joints.joint_definitions()) { + if (joint.info().guid().length()) { + nodes["ground"].mutable_children()->Add()->CopyFrom(nodes[joint.info().guid()]); + } + } + + mirabuf::GraphContainer joint_tree; + for (const auto& [_, node] : nodes) { + joint_tree.mutable_nodes()->Add()->CopyFrom(node); + } + + return joint_tree; +} diff --git a/isotope/src/parser.cpp b/isotope/src/parser.cpp index 38e4ae1093..e701abd166 100644 --- a/isotope/src/parser.cpp +++ b/isotope/src/parser.cpp @@ -50,6 +50,9 @@ void export_design(const GlobalContext& gctx) { assembly.mutable_data()->mutable_joints()->CopyFrom(joints); assembly.mutable_data()->mutable_signals()->CopyFrom(signals); + auto joint_hierarchy = create_joint_graph(joints); + assembly.mutable_joint_hierarchy()->CopyFrom(joint_hierarchy); + // Print assembly as JSON std::string json_output; auto _ = google::protobuf::util::MessageToJsonString(assembly, &json_output); @@ -66,6 +69,4 @@ void export_design(const GlobalContext& gctx) { gctx.app->userInterface()->messageBox("Exported assembly:\n" + json_output); } -void map_rigid_groups(); -void create_joint_graph(); void build_joint_part_hierarchy(); From e7c536c028aabdc1d272c9a1b4b5ad3ae352ca7d Mon Sep 17 00:00:00 2001 From: BrandonPacewic <92102436+BrandonPacewic@users.noreply.github.com> Date: Fri, 15 Aug 2025 17:49:03 -0700 Subject: [PATCH 12/22] feat(isotope): parse joint part hierarchy --- isotope/inc/joints.h | 2 + isotope/src/joints.cpp | 309 +++++++++++++++++++++++++++++++++++++++++ isotope/src/parser.cpp | 4 +- 3 files changed, 313 insertions(+), 2 deletions(-) diff --git a/isotope/inc/joints.h b/isotope/inc/joints.h index e9d865ae85..b1931968be 100644 --- a/isotope/inc/joints.h +++ b/isotope/inc/joints.h @@ -14,4 +14,6 @@ std::pair populate_joints( mirabuf::GraphContainer create_joint_graph(const mirabuf::joint::Joints& joints); +void build_joint_part_hierarchy(mirabuf::joint::Joints* joints, const adsk::core::Ptr& design); + #endif // ISOTOPE_JOINTS_H_ diff --git a/isotope/src/joints.cpp b/isotope/src/joints.cpp index fda2d2c4f5..bf43e4f64c 100644 --- a/isotope/src/joints.cpp +++ b/isotope/src/joints.cpp @@ -12,6 +12,10 @@ #include #include #include +#include +#include +#include +#include #include "assembly.pb.h" #include "joint.pb.h" @@ -20,9 +24,14 @@ #include "util.h" +#include +#include #include #include +#include #include +#include +#include namespace { @@ -205,6 +214,271 @@ adsk::core::Ptr get_joint_origin(const adsk::fusion::Joint* return result; } +adsk::core::Ptr search_for_grounded(const adsk::core::Ptr& occurrence) { + if (occurrence->isGrounded()) { + return occurrence; + } + + for (const auto occ : occurrence->childOccurrences()) { + auto searched = search_for_grounded(occ); + + if (searched) { + return searched; + } + } + + return nullptr; +} + +adsk::core::Ptr search_for_grounded(const adsk::core::Ptr& root) { + for (const auto occ : root->allOccurrences()) { + auto searched = search_for_grounded(occ); + + if (searched) { + return searched; + } + } + + return nullptr; +} + +enum OccurrenceRelationship { + TRANSFORM, // Hierarchy parenting + CONNECTION, // A rigid joint or other designator + GROUP, // A rigid grouping + NEXT, // The next joint in a list + END, // Orphaned child relationship + NONE, +}; + +struct GraphEdge; + +// TODO: Should maybe separate this out into multiple structs +// overlapping purpose +struct GraphNode { + adsk::core::Ptr data = nullptr; + std::shared_ptr previous = nullptr; + std::vector> edges{}; + + adsk::core::Ptr joint = nullptr; +}; + +struct GraphEdge { + OccurrenceRelationship relationship = NONE; + std::shared_ptr node = nullptr; +}; + +std::optional> populate_node(const adsk::core::Ptr& occurrence, std::shared_ptr prev, OccurrenceRelationship relationship, bool is_ground, std::unordered_set& visited_occurrence_entity_tokens, const std::unordered_map>& dynamic_joints) { + if (occurrence->isGrounded() && !is_ground) { + return std::nullopt; + } + + if (relationship == NEXT && prev) { + auto node = GraphNode{occurrence}; + auto edge = GraphEdge{relationship, std::make_shared(node)}; + prev->edges.push_back(std::make_shared(edge)); + return std::nullopt; + } + + if (prev && dynamic_joints.find(occurrence->entityToken()) != dynamic_joints.end()) { + return std::nullopt; + } + + if (visited_occurrence_entity_tokens.count(occurrence->entityToken())) { + return std::nullopt; + } + + visited_occurrence_entity_tokens.insert(occurrence->entityToken()); + auto node = std::make_shared(GraphNode{occurrence, prev}); + for (auto occ : occurrence->childOccurrences()) { + populate_node(occ, node, TRANSFORM, is_ground, visited_occurrence_entity_tokens, dynamic_joints); + } + + for (auto joint : occurrence->joints()) { + if (!joint || !joint->occurrenceOne() || !joint->occurrenceTwo()) { + continue; + } + + bool is_rigid = joint->jointMotion()->jointType() == adsk::fusion::RigidJointType; + adsk::core::Ptr connection = nullptr; + if (is_rigid) { + if (joint->occurrenceOne() == occurrence) { + connection = joint->occurrenceTwo(); + } else if (joint->occurrenceTwo() == occurrence) { + connection = joint->occurrenceOne(); + } + } else { + if (joint->occurrenceOne() != occurrence) { + connection = joint->occurrenceOne(); + } + } + + if (!connection) { + continue; + } + + if (!prev || connection->entityToken() != prev->data->entityToken()) { + populate_node(connection, node, is_rigid ? CONNECTION : NEXT, is_ground, visited_occurrence_entity_tokens, dynamic_joints); + } + } + + if (prev) { + prev->edges.push_back(std::make_shared(GraphEdge{relationship, node})); + } + + return node; +} + +std::optional create_tree_parts(std::shared_ptr occurrence_node, OccurrenceRelationship relationship) { + if (relationship == NEXT || !occurrence_node->data->isLightBulbOn()) { + return std::nullopt; + } + + mirabuf::Node node; + node.set_value(occurrence_node->data->name()); + for (auto edge : occurrence_node->edges) { + auto dyn_node = std::dynamic_pointer_cast(edge->node); + auto child_node = create_tree_parts(dyn_node, edge->relationship); + if (child_node) { + node.mutable_children()->Add()->CopyFrom(child_node.value()); + } + } + + return node; +} + +void populate_joint(std::shared_ptr sim_node, mirabuf::joint::Joints* joints) { + mirabuf::joint::JointInstance* joint = nullptr; + if (!sim_node->joint) { + joint = &(*joints->mutable_joint_instances())["grounded"]; + } else { + joint = &(*joints->mutable_joint_instances())[sim_node->joint->entityToken()]; + } + + assert(joint); + auto root = create_tree_parts(sim_node, CONNECTION); + if (root) { + joint->mutable_parts()->mutable_nodes()->Add()->CopyFrom(root.value()); + } + + for (auto edge : sim_node->edges) { + populate_joint(edge->node, joints); + } +} + +void get_all_joints(adsk::core::Ptr root_component, adsk::core::Ptr grounded, std::vector>& grounded_connections, std::unordered_map>& dynamic_joints) { + auto process_joint = [&](const auto /* adsk::fusion::joint | adsk::fusion::AsBuiltJoint */ joint) -> void { + assert(joint); + if (!joint->occurrenceOne() || !joint->occurrenceTwo()) { + return; + } + + if (joint->jointMotion()->jointType() != adsk::fusion::RigidJointType) { + if (dynamic_joints.find(joint->occurrenceOne()->entityToken()) == dynamic_joints.end()) { + dynamic_joints[joint->occurrenceOne()->entityToken()] = joint; + } + } else { + if (joint->occurrenceOne()->entityToken() == grounded->entityToken()) { + grounded_connections.push_back(joint->occurrenceTwo()); + } else if (joint->occurrenceTwo()->entityToken() == grounded->entityToken()) { + grounded_connections.push_back(joint->occurrenceOne()); + } + } + }; + + for (const auto& j : root_component->allJoints()) { + process_joint(j); + } + + for (const auto& j : root_component->allAsBuiltJoints()) { + process_joint(j); + } +} + +void look_for_grounded_joints(const std::vector>& grounded_connections, const std::unordered_map>& dynamic_joints, std::shared_ptr root_node) { + for (auto& grounded_connection : grounded_connections) { + std::unordered_set visited; + populate_node(grounded_connection, root_node, CONNECTION, false, visited, dynamic_joints); + } +} + +void populate_axis(const adsk::core::Ptr& design, std::unordered_map>& simulation_nodes, const std::unordered_map>& dynamic_joints, const std::string& occurrence_token, const adsk::core::Ptr& joint) { + auto result = design->findEntityByToken(occurrence_token); + if (result.empty() || !result.at(0)) { + return; + } + + auto occurrence = static_cast>(result[0]); + if (!occurrence) { + return; + } + + std::unordered_set visited; + auto node = populate_node(occurrence, nullptr, NONE, false, visited, dynamic_joints); + if (node) { + node.value()->joint = joint; + simulation_nodes[occurrence_token] = node.value(); + } +} + +std::vector get_connected_axis_tokens(std::shared_ptr start) { + std::vector tokens; + std::unordered_set visited_nodes; + std::unordered_set visited_tokens; + + std::stack stack; + stack.push(start.get()); + + while (!stack.empty()) { + const GraphNode* node = stack.top(); + stack.pop(); + if (!visited_nodes.insert(node).second) { + continue; + } + + for (const auto& edge : node->edges) { + if (edge->relationship == NEXT) { + std::string token = edge->node->data->entityToken(); + if (visited_tokens.insert(token).second) { + tokens.emplace_back(std::move(token)); + } + } else { + stack.push(edge->node.get()); + } + } + } + + return tokens; +} + +void recurse_link_node_axis(std::shared_ptr root_node, const std::unordered_map>& simulation_nodes) { + const std::vector tokens = get_connected_axis_tokens(root_node); + for (const auto& key : tokens) { + auto it = simulation_nodes.find(key); + if (it == simulation_nodes.end()) { + continue; + } + + // The original python exporter has separate enums for tracking + // both occurrence relationships and joint relationships. + // + // This, when transitioning to C++, made the types very complex as each + // node would contain either a occurrence relationship or a joint + // relationship label. + // + // Within this rewrite of the exporter this was omitted as the original + // functionality and necessity for these two distinct label types was + // unclear. + // + // Joint relationships are not tracked, only occurrence relationships are. + // + // For more information visit: + // https://github.com/Autodesk/synthesis/blob/f9bc9be63e21a705d7c8f5be9607f912764e0aa0/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/JointHierarchy.py#L54-L67 + root_node->edges.push_back(std::make_shared(GraphEdge{NONE, it->second})); + recurse_link_node_axis(it->second, simulation_nodes); + } +} + } // namespace std::pair populate_joints( @@ -325,3 +599,38 @@ mirabuf::GraphContainer create_joint_graph(const mirabuf::joint::Joints& joints) return joint_tree; } + +void build_joint_part_hierarchy(mirabuf::joint::Joints *joints, const adsk::core::Ptr& design) { + std::unordered_set visited_occurrence_entity_tokens; + std::unordered_map> dynamic_joints; + std::unordered_map> simulation_nodes; + std::vector> grounded_connections; + + auto grounded = search_for_grounded(design->rootComponent()); + + // If there was anything that represented that the C++ exporter is currently + // experimental it would be this. Not having a grounded node is a very common + // user facing problem and simply asserting this will cause fusion to crash. + // In the future if we want to actually support this section of the project + // we will need to update this into an actual error system. + // + // Note for future development: + // All instances of `assert(..)` need to be removed as Fusion simply cannot catch + // these errors and will crash. + assert(grounded); + + get_all_joints(design->rootComponent(), grounded, grounded_connections, dynamic_joints); + + auto root_node = populate_node(grounded, nullptr, NONE, true, visited_occurrence_entity_tokens, dynamic_joints).value(); + simulation_nodes["ground"] = root_node; + + look_for_grounded_joints(grounded_connections, dynamic_joints, root_node); + + for (const auto&[key, value] : dynamic_joints) { + populate_axis(design, simulation_nodes, dynamic_joints, key, value); + } + + recurse_link_node_axis(root_node, simulation_nodes); + + populate_joint(root_node, joints); +} diff --git a/isotope/src/parser.cpp b/isotope/src/parser.cpp index e701abd166..853781a919 100644 --- a/isotope/src/parser.cpp +++ b/isotope/src/parser.cpp @@ -53,6 +53,8 @@ void export_design(const GlobalContext& gctx) { auto joint_hierarchy = create_joint_graph(joints); assembly.mutable_joint_hierarchy()->CopyFrom(joint_hierarchy); + build_joint_part_hierarchy(assembly.mutable_data()->mutable_joints(), design); + // Print assembly as JSON std::string json_output; auto _ = google::protobuf::util::MessageToJsonString(assembly, &json_output); @@ -68,5 +70,3 @@ void export_design(const GlobalContext& gctx) { gctx.app->userInterface()->messageBox("Exported assembly:\n" + json_output); } - -void build_joint_part_hierarchy(); From bd89487bc0660da8bff8ca55945586e120f4876f Mon Sep 17 00:00:00 2001 From: BrandonPacewic <92102436+BrandonPacewic@users.noreply.github.com> Date: Fri, 15 Aug 2025 17:50:57 -0700 Subject: [PATCH 13/22] build(isotope): treat fus api as system interafce to avoid build warnings --- isotope/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/isotope/CMakeLists.txt b/isotope/CMakeLists.txt index 92db35d2c7..98a859c597 100644 --- a/isotope/CMakeLists.txt +++ b/isotope/CMakeLists.txt @@ -33,7 +33,7 @@ endforeach() add_library(${FUSION_API_TARGET} INTERFACE) add_library(${FUSION_API_NAMESPACE}::${FUSION_API_TARGET} ALIAS ${FUSION_API_TARGET}) -target_include_directories(${FUSION_API_TARGET} INTERFACE ${FUSION_API_INCLUDE_DIR}) +target_include_directories(${FUSION_API_TARGET} SYSTEM INTERFACE ${FUSION_API_INCLUDE_DIR}) target_link_libraries(${FUSION_API_TARGET} INTERFACE ${FUSION_API_LIBRARY_TARGETS}) file(GLOB SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/*.cpp") From 902784abd0efec0d6b5c494677f85f8437d64cc5 Mon Sep 17 00:00:00 2001 From: BrandonPacewic <92102436+BrandonPacewic@users.noreply.github.com> Date: Fri, 15 Aug 2025 18:02:45 -0700 Subject: [PATCH 14/22] chore(isotope): format with clang format --- isotope/.clang-format | 40 ++++++ isotope/Makefile | 4 + isotope/inc/components.h | 9 +- isotope/inc/config_command.h | 4 +- isotope/inc/materials.h | 4 +- isotope/inc/util.h | 20 +-- isotope/src/components.cpp | 223 +++++++++++++++++---------------- isotope/src/config_command.cpp | 4 +- isotope/src/isotope.cpp | 6 +- isotope/src/joints.cpp | 179 ++++++++++++++------------ isotope/src/materials.cpp | 90 ++++++------- isotope/src/parser.cpp | 7 +- 12 files changed, 326 insertions(+), 264 deletions(-) create mode 100644 isotope/.clang-format diff --git a/isotope/.clang-format b/isotope/.clang-format new file mode 100644 index 0000000000..b7b2746b01 --- /dev/null +++ b/isotope/.clang-format @@ -0,0 +1,40 @@ +BasedOnStyle: LLVM +AccessModifierOffset: -4 +AlignAfterOpenBracket: DontAlign +AlignConsecutiveAssignments: Consecutive +AlignConsecutiveMacros: Consecutive +AlignEscapedNewlines: Left +AlignOperands: AlignAfterOperator +AlignTrailingComments: + Kind: Never +AllowShortFunctionsOnASingleLine: Empty +AlwaysBreakTemplateDeclarations: Yes +BreakBeforeBinaryOperators: NonAssignment +ColumnLimit: 120 +IncludeIsMainRegex: '' +SortIncludes: true +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '^<(Fusion|Core)/' + Priority: 1 + - Regex: '^<[^/]+>$' + Priority: 2 + - Regex: '^]$' + Priority: 4 + - Regex: '^"(?!.+\.pb\.h").*"$' + Priority: 5 + - Regex: '.*' + Priority: 6 +IndentCaseLabels: true +IndentWidth: 4 +IndentWrappedFunctionNames: true +InsertBraces: true +InsertNewlineAtEOF: true +LineEnding: LF +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +PointerAlignment: Left +SpaceAfterCStyleCast: true +SpaceBeforeParens: true diff --git a/isotope/Makefile b/isotope/Makefile index 99b46510dc..1faeda9d4d 100644 --- a/isotope/Makefile +++ b/isotope/Makefile @@ -25,11 +25,15 @@ build-xcode: open-xcode: build-xcode @open build-xcode/isotope.xcodeproj +format: + @clang-format -i -style=file $(shell git ls-files '*.h' '*.cpp') + install: build @cmake --install "$(BUILD_DIR)" clean: @rm -rf "$(BUILD_DIR)" + @rm -rf "$(XCODE_BUILD_DIR)" @rm -f compile_commands.json help: diff --git a/isotope/inc/components.h b/isotope/inc/components.h index d41e20d06e..d975cbc482 100644 --- a/isotope/inc/components.h +++ b/isotope/inc/components.h @@ -2,16 +2,15 @@ #ifndef ISOTOPE_COMPONENTS_H_ #define ISOTOPE_COMPONENTS_H_ -#include "assembly.pb.h" -#include "types.pb.h" - #include #include +#include "assembly.pb.h" +#include "types.pb.h" + mirabuf::Parts map_all_parts(const adsk::core::Ptr& components, const mirabuf::material::Materials& materials); // TODO: Replace parameter with appearance map -mirabuf::Node parse_component_root( - const adsk::core::Ptr& component, mirabuf::Parts* parts); +mirabuf::Node parse_component_root(const adsk::core::Ptr& component, mirabuf::Parts* parts); #endif // ISOTOPE_COMPONENTS_H_ diff --git a/isotope/inc/config_command.h b/isotope/inc/config_command.h index eaa333226c..29ad93b4b1 100644 --- a/isotope/inc/config_command.h +++ b/isotope/inc/config_command.h @@ -2,11 +2,11 @@ #ifndef ISOTOPE_CONFIG_COMMAND_H_ #define ISOTOPE_CONFIG_COMMAND_H_ -#include "context.h" - #include #include +#include "context.h" + class ConfigureCommandCreatedHandler : public adsk::core::CommandCreatedEventHandler { private: const GlobalContext& gctx; diff --git a/isotope/inc/materials.h b/isotope/inc/materials.h index 1d0c4b40bb..84b2c2e358 100644 --- a/isotope/inc/materials.h +++ b/isotope/inc/materials.h @@ -2,12 +2,12 @@ #ifndef ISOTOPE_MATERIALS_H_ #define ISOTOPE_MATERIALS_H_ -#include "material.pb.h" - #include #include +#include "material.pb.h" + mirabuf::material::Materials map_all_materials(const adsk::core::Ptr& design_appearances, const adsk::core::Ptr& design_materials); diff --git a/isotope/inc/util.h b/isotope/inc/util.h index b91763ed5a..d42ec2f077 100644 --- a/isotope/inc/util.h +++ b/isotope/inc/util.h @@ -6,15 +6,15 @@ #include #include -#include #include +#include -template +template struct FusionTypeName; -#define DEFINE_FUSION_TYPE_NAME(type) \ - template<> \ - struct FusionTypeName { \ +#define DEFINE_FUSION_TYPE_NAME(type) \ + template <> \ + struct FusionTypeName { \ static constexpr std::string_view value = #type; \ } @@ -23,7 +23,7 @@ DEFINE_FUSION_TYPE_NAME(adsk::fusion::JointOrigin); DEFINE_FUSION_TYPE_NAME(adsk::fusion::BRepEdge); DEFINE_FUSION_TYPE_NAME(adsk::fusion::BRepFace); -template +template const T* fusion_try_cast(const adsk::core::Base* base) { if (!base) { return nullptr; @@ -55,7 +55,11 @@ std::variant fusion_base_to_variant(const adsk::co return fusion_base_to_variant_impl(base); } -template struct overloaded : Ts... { using Ts::operator()...; }; -template overloaded(Ts...) -> overloaded; +template +struct overloaded : Ts... { + using Ts::operator()...; +}; +template +overloaded(Ts...) -> overloaded; #endif // ISOTOPE_UTILITY_H_ diff --git a/isotope/src/components.cpp b/isotope/src/components.cpp index 748c712f7e..92e82f946f 100644 --- a/isotope/src/components.cpp +++ b/isotope/src/components.cpp @@ -1,91 +1,92 @@ #include "components.h" -#include "Core/Geometry/Matrix3D.h" +#include + #include "assembly.pb.h" #include "types.pb.h" -#include +#include "Core/Geometry/Matrix3D.h" namespace { - mirabuf::PhysicalProperties map_physical_properties( - const adsk::core::Ptr& properties) { - mirabuf::PhysicalProperties new_properties; - new_properties.set_mass(properties->mass()); - new_properties.set_volume(properties->volume()); - new_properties.set_density(properties->density()); - new_properties.set_area(properties->area()); - if (auto com = properties->centerOfMass()) { - if (auto vec = com->asVector()) { - new_properties.mutable_com()->set_x(vec->x()); - new_properties.mutable_com()->set_y(vec->y()); - new_properties.mutable_com()->set_z(vec->z()); - } +mirabuf::PhysicalProperties map_physical_properties( + const adsk::core::Ptr& properties) { + mirabuf::PhysicalProperties new_properties; + new_properties.set_mass(properties->mass()); + new_properties.set_volume(properties->volume()); + new_properties.set_density(properties->density()); + new_properties.set_area(properties->area()); + if (auto com = properties->centerOfMass()) { + if (auto vec = com->asVector()) { + new_properties.mutable_com()->set_x(vec->x()); + new_properties.mutable_com()->set_y(vec->y()); + new_properties.mutable_com()->set_z(vec->z()); } - - return new_properties; } - mirabuf::TriangleMesh map_b_rep_body(const adsk::core::Ptr& body) { - // auto calc = body->meshManager()->createMeshCalculator(); - auto mesh_mgr = body->meshManager(); - if (!mesh_mgr) { - return {}; - } + return new_properties; +} - auto calc = mesh_mgr->createMeshCalculator(); - if (!calc) { - return {}; - } +mirabuf::TriangleMesh map_b_rep_body(const adsk::core::Ptr& body) { + // auto calc = body->meshManager()->createMeshCalculator(); + auto mesh_mgr = body->meshManager(); + if (!mesh_mgr) { + return {}; + } - calc->setQuality(adsk::fusion::TriangleMeshQualityOptions::LowQualityTriangleMesh); - auto fus_mesh = calc->calculate(); - if (!fus_mesh) { - return {}; - } + auto calc = mesh_mgr->createMeshCalculator(); + if (!calc) { + return {}; + } - mirabuf::TriangleMesh mesh; - mesh.mutable_info()->set_name(body->name()); - // TODO: Info guid - mesh.mutable_info()->set_version(1); - mesh.set_has_volume(true); + calc->setQuality(adsk::fusion::TriangleMeshQualityOptions::LowQualityTriangleMesh); + auto fus_mesh = calc->calculate(); + if (!fus_mesh) { + return {}; + } - std::vector coords = fus_mesh->nodeCoordinatesAsFloat(); - mesh.mutable_mesh()->mutable_verts()->Add(coords.begin(), coords.end()); + mirabuf::TriangleMesh mesh; + mesh.mutable_info()->set_name(body->name()); + // TODO: Info guid + mesh.mutable_info()->set_version(1); + mesh.set_has_volume(true); - std::vector normals = fus_mesh->normalVectorsAsFloat(); - mesh.mutable_mesh()->mutable_normals()->Add(normals.begin(), normals.end()); + std::vector coords = fus_mesh->nodeCoordinatesAsFloat(); + mesh.mutable_mesh()->mutable_verts()->Add(coords.begin(), coords.end()); - std::vector node_indicies = fus_mesh->nodeIndices(); - mesh.mutable_mesh()->mutable_indices()->Add(node_indicies.begin(), node_indicies.end()); + std::vector normals = fus_mesh->normalVectorsAsFloat(); + mesh.mutable_mesh()->mutable_normals()->Add(normals.begin(), normals.end()); - std::vector texture_coords = fus_mesh->textureCoordinatesAsFloat(); - mesh.mutable_mesh()->mutable_uv()->Add(texture_coords.begin(), texture_coords.end()); + std::vector node_indicies = fus_mesh->nodeIndices(); + mesh.mutable_mesh()->mutable_indices()->Add(node_indicies.begin(), node_indicies.end()); - return mesh; - } + std::vector texture_coords = fus_mesh->textureCoordinatesAsFloat(); + mesh.mutable_mesh()->mutable_uv()->Add(texture_coords.begin(), texture_coords.end()); - mirabuf::TriangleMesh map_mesh_body(const adsk::core::Ptr& body) { - auto fus_mesh = body->displayMesh(); + return mesh; +} - mirabuf::TriangleMesh mesh; - // TODO: Info crap (this is getting annoying) - mesh.set_has_volume(true); +mirabuf::TriangleMesh map_mesh_body(const adsk::core::Ptr& body) { + auto fus_mesh = body->displayMesh(); - std::vector coords = fus_mesh->nodeCoordinatesAsFloat(); - mesh.mutable_mesh()->mutable_verts()->Add(coords.begin(), coords.end()); + mirabuf::TriangleMesh mesh; + // TODO: Info crap (this is getting annoying) + mesh.set_has_volume(true); - std::vector normals = fus_mesh->normalVectorsAsFloat(); - mesh.mutable_mesh()->mutable_normals()->Add(normals.begin(), normals.end()); + std::vector coords = fus_mesh->nodeCoordinatesAsFloat(); + mesh.mutable_mesh()->mutable_verts()->Add(coords.begin(), coords.end()); - std::vector node_indicies = fus_mesh->nodeIndices(); - mesh.mutable_mesh()->mutable_indices()->Add(node_indicies.begin(), node_indicies.end()); + std::vector normals = fus_mesh->normalVectorsAsFloat(); + mesh.mutable_mesh()->mutable_normals()->Add(normals.begin(), normals.end()); - std::vector texture_coords = fus_mesh->textureCoordinatesAsFloat(); - mesh.mutable_mesh()->mutable_uv()->Add(texture_coords.begin(), texture_coords.end()); + std::vector node_indicies = fus_mesh->nodeIndices(); + mesh.mutable_mesh()->mutable_indices()->Add(node_indicies.begin(), node_indicies.end()); - return mesh; - } + std::vector texture_coords = fus_mesh->textureCoordinatesAsFloat(); + mesh.mutable_mesh()->mutable_uv()->Add(texture_coords.begin(), texture_coords.end()); + + return mesh; +} } // namespace @@ -152,69 +153,69 @@ mirabuf::Parts map_all_parts( } namespace { - adsk::core::Ptr get_matrix_world(const adsk::core::Ptr& occurrence) { - if (!occurrence) { - return nullptr; - } - - auto matrix = occurrence->transform2()->copy(); - auto next_occurrence = occurrence; - while (next_occurrence->assemblyContext()) { - matrix->transformBy(next_occurrence->assemblyContext()->transform2()); - next_occurrence = next_occurrence->assemblyContext(); - } +adsk::core::Ptr get_matrix_world(const adsk::core::Ptr& occurrence) { + if (!occurrence) { + return nullptr; + } - return matrix; + auto matrix = occurrence->transform2()->copy(); + auto next_occurrence = occurrence; + while (next_occurrence->assemblyContext()) { + matrix->transformBy(next_occurrence->assemblyContext()->transform2()); + next_occurrence = next_occurrence->assemblyContext(); } - mirabuf::Node parse_child_occurrence( - const adsk::core::Ptr& occurrence, mirabuf::Parts* parts) { - assert(occurrence->isLightBulbOn()); + return matrix; +} - mirabuf::Node node; - // TODO: Info stuff +mirabuf::Node parse_child_occurrence( + const adsk::core::Ptr& occurrence, mirabuf::Parts* parts) { + assert(occurrence->isLightBulbOn()); - auto part = parts->mutable_part_instances()->find(occurrence->component()->id()); + mirabuf::Node node; + // TODO: Info stuff - // set top info + auto part = parts->mutable_part_instances()->find(occurrence->component()->id()); - // set occurrence appearance - if (occurrence->appearance()) { - auto appearance_id = occurrence->appearance()->id(); - part->second.set_appearance(appearance_id); - } + // set top info - // set part physical material - if (auto material = occurrence->component()->material()) { - part->second.set_physical_material(material->id()); - } + // set occurrence appearance + if (occurrence->appearance()) { + auto appearance_id = occurrence->appearance()->id(); + part->second.set_appearance(appearance_id); + } - // set part spatial matrix - part->second.mutable_transform()->mutable_spatial_matrix()->Add( - occurrence->transform()->asArray().begin(), occurrence->transform()->asArray().end()); - - // set part global transform - // auto world_transform = get_matrix_world(occurrence); - // if (world_transform) { - // part->second.mutable_global_transform()->mutable_spatial_matrix()->Add( - // world_transform->asArray().begin(), world_transform->asArray().end()); - // } - - // final recursive step to parse child occurrences - std::vector> child_occurrences; - occurrence->childOccurrences()->copyTo(std::back_inserter(child_occurrences)); - for (const auto& child_occurrence : child_occurrences) { - if (!child_occurrence->isLightBulbOn()) { - continue; - } + // set part physical material + if (auto material = occurrence->component()->material()) { + part->second.set_physical_material(material->id()); + } + + // set part spatial matrix + part->second.mutable_transform()->mutable_spatial_matrix()->Add( + occurrence->transform()->asArray().begin(), occurrence->transform()->asArray().end()); + + // set part global transform + // auto world_transform = get_matrix_world(occurrence); + // if (world_transform) { + // part->second.mutable_global_transform()->mutable_spatial_matrix()->Add( + // world_transform->asArray().begin(), world_transform->asArray().end()); + // } - auto child_node = parse_child_occurrence(child_occurrence, parts); - node.mutable_children()->Add()->CopyFrom(child_node); + // final recursive step to parse child occurrences + std::vector> child_occurrences; + occurrence->childOccurrences()->copyTo(std::back_inserter(child_occurrences)); + for (const auto& child_occurrence : child_occurrences) { + if (!child_occurrence->isLightBulbOn()) { + continue; } - return node; + auto child_node = parse_child_occurrence(child_occurrence, parts); + node.mutable_children()->Add()->CopyFrom(child_node); } + return node; +} + } // namespace mirabuf::Node parse_component_root(const adsk::core::Ptr& component, mirabuf::Parts* parts) { diff --git a/isotope/src/config_command.cpp b/isotope/src/config_command.cpp index 47660366dc..048109428b 100644 --- a/isotope/src/config_command.cpp +++ b/isotope/src/config_command.cpp @@ -1,11 +1,11 @@ #include "config_command.h" -#include "parser.h" - #include #include #include +#include "parser.h" + void ConfigureCommandCreatedHandler::notify(const adsk::core::Ptr& args) { assert(this->gctx.isValid()); adsk::core::Ptr command = args->command(); diff --git a/isotope/src/isotope.cpp b/isotope/src/isotope.cpp index 96501969f3..88e84054a4 100644 --- a/isotope/src/isotope.cpp +++ b/isotope/src/isotope.cpp @@ -1,6 +1,3 @@ -#include "config_command.h" -#include "context.h" - #include #include #include @@ -20,6 +17,9 @@ #include +#include "config_command.h" +#include "context.h" + GlobalContext gctx; extern "C" XI_EXPORT bool run(const char* context) { diff --git a/isotope/src/joints.cpp b/isotope/src/joints.cpp index bf43e4f64c..1e148047d1 100644 --- a/isotope/src/joints.cpp +++ b/isotope/src/joints.cpp @@ -1,22 +1,30 @@ #include "joints.h" -#include #include - -#include -#include #include +#include #include #include +#include #include #include #include #include -#include -#include +#include +#include #include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include + #include "assembly.pb.h" #include "joint.pb.h" #include "signal.pb.h" @@ -24,30 +32,19 @@ #include "util.h" -#include -#include -#include -#include -#include -#include -#include -#include - namespace { -mirabuf::joint::RigidGroup map_rigid_group(const adsk::fusion::Joint* /* adsk::fusion::joint | adsk::fusion::AsBuiltJoint */ joint -) { +mirabuf::joint::RigidGroup map_rigid_group( + const adsk::fusion::Joint* /* adsk::fusion::joint | adsk::fusion::AsBuiltJoint */ joint) { assert(joint); assert(joint->jointMotion()->jointType() == adsk::fusion::JointTypes::RigidJointType); - if (!joint->occurrenceOne()->isLightBulbOn() || - !joint->occurrenceTwo()->isLightBulbOn()) { + if (!joint->occurrenceOne()->isLightBulbOn() || !joint->occurrenceTwo()->isLightBulbOn()) { return {}; } mirabuf::joint::RigidGroup group; - std::string group_name = "group_" + joint->occurrenceOne()->name() + "_" + - joint->occurrenceTwo()->name(); + std::string group_name = "group_" + joint->occurrenceOne()->name() + "_" + joint->occurrenceTwo()->name(); group.set_name(group_name); group.add_occurrences(joint->occurrenceOne()->name()); group.add_occurrences(joint->occurrenceTwo()->name()); @@ -55,7 +52,8 @@ mirabuf::joint::RigidGroup map_rigid_group(const adsk::fusion::Joint* /* adsk::f return group; } -void fill_revolute_joint_motion(const adsk::core::Ptr& motion, mirabuf::joint::Joint* proto_joint) { +void fill_revolute_joint_motion( + const adsk::core::Ptr& motion, mirabuf::joint::Joint* proto_joint) { assert(motion); assert(proto_joint); @@ -82,7 +80,8 @@ void fill_revolute_joint_motion(const adsk::core::Ptr& motion, mirabuf::joint::Joint* proto_joint) { +void fill_slider_joint_motion( + const adsk::core::Ptr& motion, mirabuf::joint::Joint* proto_joint) { assert(motion); assert(proto_joint); @@ -115,7 +114,8 @@ void fill_slider_joint_motion(const adsk::core::Ptrset_value(motion->slideValue()); } -void fill_motion_from_joint(const adsk::core::Ptr& motion, mirabuf::joint::Joint* proto_joint) { +void fill_motion_from_joint( + const adsk::core::Ptr& motion, mirabuf::joint::Joint* proto_joint) { assert(motion); assert(proto_joint); @@ -139,42 +139,41 @@ void fill_motion_from_joint(const adsk::core::Ptr& mo } } -adsk::core::Ptr origin_from_joint_geometry(const adsk::fusion::JointGeometry* geometry, const adsk::core::Ptr occurrence) { +adsk::core::Ptr origin_from_joint_geometry( + const adsk::fusion::JointGeometry* geometry, const adsk::core::Ptr occurrence) { if (!geometry) { return adsk::core::Point3D::create(); } - auto entity_one = geometry->entityOne(); if (!entity_one) { return adsk::core::Point3D::create(); } auto edge_or_face = fusion_base_to_variant(entity_one.get()); - adsk::core::Ptr result = std::visit(overloaded{ - [&geometry](std::monostate) -> auto { - return geometry->origin(); - }, - [&geometry, &occurrence](const adsk::fusion::BRepEdge* edge) -> auto { - if (!edge->assemblyContext()) { - auto new_entity = edge->createForAssemblyContext(occurrence); - auto min = new_entity->boundingBox()->minPoint(); - auto max = new_entity->boundingBox()->maxPoint(); - auto org = adsk::core::Point3D::create((max->x() + min->x()) / 2.0f, (max->y() + min->y()) / 2.0f, (max->z() + min->z()) / 2.0f); - return org; - } - - return geometry->origin(); - }, - [&geometry, &occurrence](const adsk::fusion::BRepFace* face) -> auto { - if (!face->assemblyContext()) { - auto new_entity = face->createForAssemblyContext(occurrence); - return new_entity->centroid(); - } - - return geometry->origin(); - } - }, edge_or_face); + adsk::core::Ptr result = + std::visit(overloaded{[&geometry](std::monostate) -> auto { return geometry->origin(); }, + [&geometry, &occurrence](const adsk::fusion::BRepEdge* edge) -> auto { + if (!edge->assemblyContext()) { + auto new_entity = edge->createForAssemblyContext(occurrence); + auto min = new_entity->boundingBox()->minPoint(); + auto max = new_entity->boundingBox()->maxPoint(); + auto org = adsk::core::Point3D::create((max->x() + min->x()) / 2.0f, + (max->y() + min->y()) / 2.0f, (max->z() + min->z()) / 2.0f); + return org; + } + + return geometry->origin(); + }, + [&geometry, &occurrence](const adsk::fusion::BRepFace* face) -> auto { + if (!face->assemblyContext()) { + auto new_entity = face->createForAssemblyContext(occurrence); + return new_entity->centroid(); + } + + return geometry->origin(); + }}, + edge_or_face); return result; } @@ -184,7 +183,7 @@ adsk::core::Ptr origin_from_joint_origin(const adsk::fusion return adsk::core::Point3D::create(); } - auto origin = joint_origin->geometry()->origin(); + auto origin = joint_origin->geometry()->origin(); double offset_x = joint_origin->offsetX() ? joint_origin->offsetX()->value() : 0; double offset_y = joint_origin->offsetY() ? joint_origin->offsetY()->value() : 0; double offset_z = joint_origin->offsetZ() ? joint_origin->offsetZ()->value() : 0; @@ -194,27 +193,25 @@ adsk::core::Ptr origin_from_joint_origin(const adsk::fusion adsk::core::Ptr get_joint_origin(const adsk::fusion::Joint* fusion_joint) { assert(fusion_joint); auto raw_geo_test = fusion_joint->geometryOrOriginOne(); - auto geometry_or_origin = fusion_base_to_variant(raw_geo_test.get()); + auto geometry_or_origin = + fusion_base_to_variant(raw_geo_test.get()); if (std::holds_alternative(geometry_or_origin)) { return adsk::core::Point3D::create(); } - adsk::core::Ptr result = std::visit(overloaded{ - [](std::monostate) -> auto { - return adsk::core::Point3D::create(); - }, - [&fusion_joint](const adsk::fusion::JointGeometry* geometry) -> auto { - return origin_from_joint_geometry(geometry, fusion_joint->occurrenceOne()); - }, - [](const adsk::fusion::JointOrigin* origin) -> auto { - return origin_from_joint_origin(origin); - } - }, geometry_or_origin); + adsk::core::Ptr result = std::visit( + overloaded{[](std::monostate) -> auto { return adsk::core::Point3D::create(); }, + [&fusion_joint](const adsk::fusion::JointGeometry* geometry) -> auto { + return origin_from_joint_geometry(geometry, fusion_joint->occurrenceOne()); + }, + [](const adsk::fusion::JointOrigin* origin) -> auto { return origin_from_joint_origin(origin); }}, + geometry_or_origin); return result; } -adsk::core::Ptr search_for_grounded(const adsk::core::Ptr& occurrence) { +adsk::core::Ptr search_for_grounded( + const adsk::core::Ptr& occurrence) { if (occurrence->isGrounded()) { return occurrence; } @@ -257,7 +254,7 @@ struct GraphEdge; // overlapping purpose struct GraphNode { adsk::core::Ptr data = nullptr; - std::shared_ptr previous = nullptr; + std::shared_ptr previous = nullptr; std::vector> edges{}; adsk::core::Ptr joint = nullptr; @@ -265,10 +262,13 @@ struct GraphNode { struct GraphEdge { OccurrenceRelationship relationship = NONE; - std::shared_ptr node = nullptr; + std::shared_ptr node = nullptr; }; -std::optional> populate_node(const adsk::core::Ptr& occurrence, std::shared_ptr prev, OccurrenceRelationship relationship, bool is_ground, std::unordered_set& visited_occurrence_entity_tokens, const std::unordered_map>& dynamic_joints) { +std::optional> populate_node(const adsk::core::Ptr& occurrence, + std::shared_ptr prev, OccurrenceRelationship relationship, bool is_ground, + std::unordered_set& visited_occurrence_entity_tokens, + const std::unordered_map>& dynamic_joints) { if (occurrence->isGrounded() && !is_ground) { return std::nullopt; } @@ -278,7 +278,7 @@ std::optional> populate_node(const adsk::core::Ptr(node)}; prev->edges.push_back(std::make_shared(edge)); return std::nullopt; - } + } if (prev && dynamic_joints.find(occurrence->entityToken()) != dynamic_joints.end()) { return std::nullopt; @@ -318,7 +318,8 @@ std::optional> populate_node(const adsk::core::PtrentityToken() != prev->data->entityToken()) { - populate_node(connection, node, is_rigid ? CONNECTION : NEXT, is_ground, visited_occurrence_entity_tokens, dynamic_joints); + populate_node(connection, node, is_rigid ? CONNECTION : NEXT, is_ground, visited_occurrence_entity_tokens, + dynamic_joints); } } @@ -329,7 +330,8 @@ std::optional> populate_node(const adsk::core::Ptr create_tree_parts(std::shared_ptr occurrence_node, OccurrenceRelationship relationship) { +std::optional create_tree_parts( + std::shared_ptr occurrence_node, OccurrenceRelationship relationship) { if (relationship == NEXT || !occurrence_node->data->isLightBulbOn()) { return std::nullopt; } @@ -337,7 +339,7 @@ std::optional create_tree_parts(std::shared_ptr occurr mirabuf::Node node; node.set_value(occurrence_node->data->name()); for (auto edge : occurrence_node->edges) { - auto dyn_node = std::dynamic_pointer_cast(edge->node); + auto dyn_node = std::dynamic_pointer_cast(edge->node); auto child_node = create_tree_parts(dyn_node, edge->relationship); if (child_node) { node.mutable_children()->Add()->CopyFrom(child_node.value()); @@ -366,7 +368,10 @@ void populate_joint(std::shared_ptr sim_node, mirabuf::joint::Joints* } } -void get_all_joints(adsk::core::Ptr root_component, adsk::core::Ptr grounded, std::vector>& grounded_connections, std::unordered_map>& dynamic_joints) { +void get_all_joints(adsk::core::Ptr root_component, + adsk::core::Ptr grounded, + std::vector>& grounded_connections, + std::unordered_map>& dynamic_joints) { auto process_joint = [&](const auto /* adsk::fusion::joint | adsk::fusion::AsBuiltJoint */ joint) -> void { assert(joint); if (!joint->occurrenceOne() || !joint->occurrenceTwo()) { @@ -395,14 +400,19 @@ void get_all_joints(adsk::core::Ptr root_component, ads } } -void look_for_grounded_joints(const std::vector>& grounded_connections, const std::unordered_map>& dynamic_joints, std::shared_ptr root_node) { +void look_for_grounded_joints(const std::vector>& grounded_connections, + const std::unordered_map>& dynamic_joints, + std::shared_ptr root_node) { for (auto& grounded_connection : grounded_connections) { std::unordered_set visited; populate_node(grounded_connection, root_node, CONNECTION, false, visited, dynamic_joints); } } -void populate_axis(const adsk::core::Ptr& design, std::unordered_map>& simulation_nodes, const std::unordered_map>& dynamic_joints, const std::string& occurrence_token, const adsk::core::Ptr& joint) { +void populate_axis(const adsk::core::Ptr& design, + std::unordered_map>& simulation_nodes, + const std::unordered_map>& dynamic_joints, + const std::string& occurrence_token, const adsk::core::Ptr& joint) { auto result = design->findEntityByToken(occurrence_token); if (result.empty() || !result.at(0)) { return; @@ -416,7 +426,7 @@ void populate_axis(const adsk::core::Ptr& design, std::uno std::unordered_set visited; auto node = populate_node(occurrence, nullptr, NONE, false, visited, dynamic_joints); if (node) { - node.value()->joint = joint; + node.value()->joint = joint; simulation_nodes[occurrence_token] = node.value(); } } @@ -451,7 +461,8 @@ std::vector get_connected_axis_tokens(std::shared_ptr st return tokens; } -void recurse_link_node_axis(std::shared_ptr root_node, const std::unordered_map>& simulation_nodes) { +void recurse_link_node_axis(std::shared_ptr root_node, + const std::unordered_map>& simulation_nodes) { const std::vector tokens = get_connected_axis_tokens(root_node); for (const auto& key : tokens) { auto it = simulation_nodes.find(key); @@ -499,7 +510,8 @@ std::pair populate_joints( joint_instance_ground.set_joint_reference(joint_definition_ground.info().guid()); - auto process_joint = [&joints, &signals](const adsk::fusion::Joint* /* adsk::fusion::joint | adsk::fusion::AsBuiltJoint */ joint) { + auto process_joint = [&joints, &signals]( + const adsk::fusion::Joint* /* adsk::fusion::joint | adsk::fusion::AsBuiltJoint */ joint) { assert(joint); if (joint->isSuppressed()) { return; @@ -547,7 +559,7 @@ std::pair populate_joints( joint_definition.set_break_magnitude(0.0f); - auto& motor = (*joints.mutable_motor_definitions())[joint->name()]; + auto& motor = (*joints.mutable_motor_definitions())[joint->name()]; auto simple_motor = motor.mutable_simple_motor(); // These are values I just chose on a whim, they need to be checked and changed to make sure @@ -569,7 +581,7 @@ std::pair populate_joints( // process_joint(asBuiltJoint.get()); } - return { joints, signals }; + return {joints, signals}; } mirabuf::GraphContainer create_joint_graph(const mirabuf::joint::Joints& joints) { @@ -600,7 +612,7 @@ mirabuf::GraphContainer create_joint_graph(const mirabuf::joint::Joints& joints) return joint_tree; } -void build_joint_part_hierarchy(mirabuf::joint::Joints *joints, const adsk::core::Ptr& design) { +void build_joint_part_hierarchy(mirabuf::joint::Joints* joints, const adsk::core::Ptr& design) { std::unordered_set visited_occurrence_entity_tokens; std::unordered_map> dynamic_joints; std::unordered_map> simulation_nodes; @@ -608,7 +620,7 @@ void build_joint_part_hierarchy(mirabuf::joint::Joints *joints, const adsk::core auto grounded = search_for_grounded(design->rootComponent()); - // If there was anything that represented that the C++ exporter is currently + // If there was anything that represented that the C++ exporter is currently // experimental it would be this. Not having a grounded node is a very common // user facing problem and simply asserting this will cause fusion to crash. // In the future if we want to actually support this section of the project @@ -621,12 +633,13 @@ void build_joint_part_hierarchy(mirabuf::joint::Joints *joints, const adsk::core get_all_joints(design->rootComponent(), grounded, grounded_connections, dynamic_joints); - auto root_node = populate_node(grounded, nullptr, NONE, true, visited_occurrence_entity_tokens, dynamic_joints).value(); + auto root_node = + populate_node(grounded, nullptr, NONE, true, visited_occurrence_entity_tokens, dynamic_joints).value(); simulation_nodes["ground"] = root_node; look_for_grounded_joints(grounded_connections, dynamic_joints, root_node); - for (const auto&[key, value] : dynamic_joints) { + for (const auto& [key, value] : dynamic_joints) { populate_axis(design, simulation_nodes, dynamic_joints, key, value); } diff --git a/isotope/src/materials.cpp b/isotope/src/materials.cpp index d2daf36919..eccab7f512 100644 --- a/isotope/src/materials.cpp +++ b/isotope/src/materials.cpp @@ -1,62 +1,62 @@ #include "materials.h" -#include "material.pb.h" - #include #include #include +#include "material.pb.h" + namespace { - mirabuf::material::Appearance default_appearance() { - mirabuf::material::Appearance appearance; - appearance.mutable_info()->set_name("Default Appearance"); - appearance.mutable_info()->set_guid("default-appearance-guid"); - appearance.mutable_info()->set_version(1); - appearance.set_roughness(0.5f); - appearance.set_metallic(0.5f); - appearance.set_specular(0.5f); - - appearance.mutable_albedo()->set_r(127); - appearance.mutable_albedo()->set_g(127); - appearance.mutable_albedo()->set_b(127); - appearance.mutable_albedo()->set_a(255); - - return appearance; - } +mirabuf::material::Appearance default_appearance() { + mirabuf::material::Appearance appearance; + appearance.mutable_info()->set_name("Default Appearance"); + appearance.mutable_info()->set_guid("default-appearance-guid"); + appearance.mutable_info()->set_version(1); + appearance.set_roughness(0.5f); + appearance.set_metallic(0.5f); + appearance.set_specular(0.5f); + + appearance.mutable_albedo()->set_r(127); + appearance.mutable_albedo()->set_g(127); + appearance.mutable_albedo()->set_b(127); + appearance.mutable_albedo()->set_a(255); + + return appearance; +} - mirabuf::material::Appearance map_appearance(const adsk::core::Ptr& appearance) { - mirabuf::material::Appearance new_appearance = default_appearance(); - new_appearance.mutable_info()->set_name(appearance->name()); - new_appearance.mutable_info()->set_guid(appearance->id()); +mirabuf::material::Appearance map_appearance(const adsk::core::Ptr& appearance) { + mirabuf::material::Appearance new_appearance = default_appearance(); + new_appearance.mutable_info()->set_name(appearance->name()); + new_appearance.mutable_info()->set_guid(appearance->id()); - // TODO - Map all other appearance properties - return new_appearance; - } + // TODO - Map all other appearance properties + return new_appearance; +} - mirabuf::material::PhysicalMaterial default_physical_material() { - mirabuf::material::PhysicalMaterial physical_material; - physical_material.mutable_info()->set_name("Default Physical Material"); - physical_material.mutable_info()->set_guid("default-physical-material-guid"); - physical_material.mutable_info()->set_version(1); - physical_material.set_dynamic_friction(0.5f); - physical_material.set_static_friction(0.5f); - physical_material.set_restitution(0.5f); - physical_material.set_deformable(false); - physical_material.set_mattype(mirabuf::material::PhysicalMaterial_MaterialType_METAL); - - return physical_material; - } +mirabuf::material::PhysicalMaterial default_physical_material() { + mirabuf::material::PhysicalMaterial physical_material; + physical_material.mutable_info()->set_name("Default Physical Material"); + physical_material.mutable_info()->set_guid("default-physical-material-guid"); + physical_material.mutable_info()->set_version(1); + physical_material.set_dynamic_friction(0.5f); + physical_material.set_static_friction(0.5f); + physical_material.set_restitution(0.5f); + physical_material.set_deformable(false); + physical_material.set_mattype(mirabuf::material::PhysicalMaterial_MaterialType_METAL); + + return physical_material; +} - mirabuf::material::PhysicalMaterial map_physical_material(const adsk::core::Ptr& material) { - mirabuf::material::PhysicalMaterial new_physical_material = default_physical_material(); - new_physical_material.mutable_info()->set_name(material->name()); - new_physical_material.mutable_info()->set_guid(material->id()); +mirabuf::material::PhysicalMaterial map_physical_material(const adsk::core::Ptr& material) { + mirabuf::material::PhysicalMaterial new_physical_material = default_physical_material(); + new_physical_material.mutable_info()->set_name(material->name()); + new_physical_material.mutable_info()->set_guid(material->id()); - // TODO - Map all other physical material properties - return new_physical_material; - } + // TODO - Map all other physical material properties + return new_physical_material; +} } // namespace diff --git a/isotope/src/parser.cpp b/isotope/src/parser.cpp index 853781a919..00c39a586b 100644 --- a/isotope/src/parser.cpp +++ b/isotope/src/parser.cpp @@ -7,17 +7,18 @@ #include #include #include + +#include + #include #include "assembly.pb.h" #include "types.pb.h" -#include "joints.h" #include "components.h" +#include "joints.h" #include "materials.h" -#include - void export_design(const GlobalContext& gctx) { assert(gctx.isValid()); auto document = gctx.app->activeDocument(); From 4d963be1352128a2f8685326842b6b7167fba76a Mon Sep 17 00:00:00 2001 From: BrandonPacewic <92102436+BrandonPacewic@users.noreply.github.com> Date: Mon, 18 Aug 2025 14:04:39 -0700 Subject: [PATCH 15/22] feat(isotope): rigid group parsing --- isotope/inc/components.h | 4 ++++ isotope/src/components.cpp | 49 ++++++++++++++++++++++---------------- isotope/src/joints.cpp | 16 ++++++------- isotope/src/parser.cpp | 14 +++++++++++ 4 files changed, 54 insertions(+), 29 deletions(-) diff --git a/isotope/inc/components.h b/isotope/inc/components.h index d975cbc482..d77927696e 100644 --- a/isotope/inc/components.h +++ b/isotope/inc/components.h @@ -1,4 +1,6 @@ #pragma once +#include +#include #ifndef ISOTOPE_COMPONENTS_H_ #define ISOTOPE_COMPONENTS_H_ @@ -13,4 +15,6 @@ mirabuf::Parts map_all_parts(const adsk::core::Ptr& co mirabuf::Node parse_component_root(const adsk::core::Ptr& component, mirabuf::Parts* parts); +void map_rigid_groups(const adsk::core::Ptr& root, mirabuf::joint::Joints* joints); + #endif // ISOTOPE_COMPONENTS_H_ diff --git a/isotope/src/components.cpp b/isotope/src/components.cpp index 92e82f946f..16a3d64214 100644 --- a/isotope/src/components.cpp +++ b/isotope/src/components.cpp @@ -1,5 +1,6 @@ #include "components.h" +#include #include #include "assembly.pb.h" @@ -173,26 +174,20 @@ mirabuf::Node parse_child_occurrence( assert(occurrence->isLightBulbOn()); mirabuf::Node node; + node.set_value(occurrence->component()->id()); // TODO: Info stuff - auto part = parts->mutable_part_instances()->find(occurrence->component()->id()); - - // set top info - - // set occurrence appearance + auto& part = (*parts->mutable_part_instances())[occurrence->component()->id()]; if (occurrence->appearance()) { - auto appearance_id = occurrence->appearance()->id(); - part->second.set_appearance(appearance_id); + part.set_appearance(occurrence->appearance()->id()); } - // set part physical material if (auto material = occurrence->component()->material()) { - part->second.set_physical_material(material->id()); + part.set_physical_material(material->id()); } - // set part spatial matrix - part->second.mutable_transform()->mutable_spatial_matrix()->Add( - occurrence->transform()->asArray().begin(), occurrence->transform()->asArray().end()); + auto transform_array = occurrence->transform()->asArray(); + part.mutable_transform()->mutable_spatial_matrix()->Add(transform_array.begin(), transform_array.end()); // set part global transform // auto world_transform = get_matrix_world(occurrence); @@ -223,16 +218,10 @@ mirabuf::Node parse_component_root(const adsk::core::Ptrid()); // TODO: Info stuff - - auto part = parts->mutable_part_instances()->find(component->id()); - if (part == parts->part_instances().end()) { - return root_node; // this is problematic - } - - // find out if this is necessary as we are editing objects in place which i really wanted to avoid - auto part_defs = parts->part_definitions(); + auto& part = (*parts->mutable_part_instances())[component->id()]; + auto& part_defs = parts->part_definitions(); if (part_defs.find(component->id()) != part_defs.end()) { - part->second.set_part_definition_reference(component->id()); + part.set_part_definition_reference(component->id()); } std::vector> child_occurrences; @@ -248,3 +237,21 @@ mirabuf::Node parse_component_root(const adsk::core::Ptr& root, mirabuf::joint::Joints* joints) { + for (const auto& fus_group : root->allRigidGroups()) { + auto mira_group = mirabuf::joint::RigidGroup(); + mira_group.set_name(fus_group->entityToken()); + for (const auto& occurrence : fus_group->occurrences()) { + if (!occurrence || !occurrence->isLightBulbOn()) { + continue; + } + + mira_group.mutable_occurrences()->Add(occurrence->entityToken()); + } + + if (mira_group.occurrences().size()) { + joints->mutable_rigid_groups()->Add()->CopyFrom(mira_group); + } + } +} diff --git a/isotope/src/joints.cpp b/isotope/src/joints.cpp index 1e148047d1..7f1bafb685 100644 --- a/isotope/src/joints.cpp +++ b/isotope/src/joints.cpp @@ -525,24 +525,24 @@ std::pair populate_joints( } } - auto& signal = (*signals.mutable_signal_map())[joint->name()]; - signal.mutable_info()->set_name(joint->name()); - signal.mutable_info()->set_guid(joint->name()); + auto& signal = (*signals.mutable_signal_map())[joint->entityToken()]; + signal.mutable_info()->set_name(joint->entityToken()); + signal.mutable_info()->set_guid(joint->entityToken()); signal.mutable_info()->set_version(1); signal.set_io(mirabuf::signal::IOType::OUTPUT); signal.set_device_type(mirabuf::signal::DeviceType::PWM); - auto& joint_instance = (*joints.mutable_joint_instances())[joint->name()]; + auto& joint_instance = (*joints.mutable_joint_instances())[joint->entityToken()]; joint_instance.set_signal_reference(signal.info().guid()); joint_instance.set_parent_part(joint->occurrenceOne()->name()); joint_instance.set_child_part(joint->occurrenceTwo()->name()); // TODO: Wheel logic should go here - auto& joint_definition = (*joints.mutable_joint_definitions())[joint->name()]; + auto& joint_definition = (*joints.mutable_joint_definitions())[joint->entityToken()]; joint_definition.set_motor_reference(signal.info().guid()); - joint_definition.mutable_info()->set_name(joint->name()); - joint_definition.mutable_info()->set_guid(joint->name()); + joint_definition.mutable_info()->set_name(joint->entityToken()); + joint_definition.mutable_info()->set_guid(joint->entityToken()); joint_definition.mutable_info()->set_version(1); auto joint_origin = get_joint_origin(joint); @@ -559,7 +559,7 @@ std::pair populate_joints( joint_definition.set_break_magnitude(0.0f); - auto& motor = (*joints.mutable_motor_definitions())[joint->name()]; + auto& motor = (*joints.mutable_motor_definitions())[joint->entityToken()]; auto simple_motor = motor.mutable_simple_motor(); // These are values I just chose on a whim, they need to be checked and changed to make sure diff --git a/isotope/src/parser.cpp b/isotope/src/parser.cpp index 00c39a586b..01b01214a9 100644 --- a/isotope/src/parser.cpp +++ b/isotope/src/parser.cpp @@ -51,6 +51,8 @@ void export_design(const GlobalContext& gctx) { assembly.mutable_data()->mutable_joints()->CopyFrom(joints); assembly.mutable_data()->mutable_signals()->CopyFrom(signals); + map_rigid_groups(design->rootComponent(), assembly.mutable_data()->mutable_joints()); + auto joint_hierarchy = create_joint_graph(joints); assembly.mutable_joint_hierarchy()->CopyFrom(joint_hierarchy); @@ -69,5 +71,17 @@ void export_design(const GlobalContext& gctx) { output_file << json_output; output_file.close(); + std::ofstream binary_output( + std::getenv("HOME") + std::string("/Desktop/test_dozer.mira"), std::ios::out | std::ios::binary); + if (!binary_output.is_open()) { + gctx.app->userInterface()->messageBox("Failed to open output file for writing."); + return; + } + + if (!assembly.SerializeToOstream(&binary_output)) { + gctx.app->userInterface()->messageBox("Failed to write binary."); + return; + } + gctx.app->userInterface()->messageBox("Exported assembly:\n" + json_output); } From fc178ba05818410cbdfa0e2dac92a73c93cd71fa Mon Sep 17 00:00:00 2001 From: BrandonPacewic <92102436+BrandonPacewic@users.noreply.github.com> Date: Mon, 18 Aug 2025 14:14:06 -0700 Subject: [PATCH 16/22] feat(isotope): enable world transform parsing --- isotope/src/components.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/isotope/src/components.cpp b/isotope/src/components.cpp index 16a3d64214..94f9d76075 100644 --- a/isotope/src/components.cpp +++ b/isotope/src/components.cpp @@ -189,12 +189,8 @@ mirabuf::Node parse_child_occurrence( auto transform_array = occurrence->transform()->asArray(); part.mutable_transform()->mutable_spatial_matrix()->Add(transform_array.begin(), transform_array.end()); - // set part global transform - // auto world_transform = get_matrix_world(occurrence); - // if (world_transform) { - // part->second.mutable_global_transform()->mutable_spatial_matrix()->Add( - // world_transform->asArray().begin(), world_transform->asArray().end()); - // } + auto world_transform = get_matrix_world(occurrence)->asArray(); + part.mutable_global_transform()->mutable_spatial_matrix()->Add(world_transform.begin(), world_transform.end()); // final recursive step to parse child occurrences std::vector> child_occurrences; From 3a2613436a32c97828bea3ebc461e901f7364585 Mon Sep 17 00:00:00 2001 From: BrandonPacewic <92102436+BrandonPacewic@users.noreply.github.com> Date: Tue, 19 Aug 2025 10:07:38 -0700 Subject: [PATCH 17/22] fix(isotope): correctly handle guids --- isotope/inc/util.h | 45 ++++++++++++++++++++++++++++++++++++++ isotope/src/components.cpp | 29 +++++++++++------------- isotope/src/joints.cpp | 16 ++++++++------ isotope/src/materials.cpp | 4 ++++ isotope/src/parser.cpp | 4 ++-- 5 files changed, 73 insertions(+), 25 deletions(-) diff --git a/isotope/inc/util.h b/isotope/inc/util.h index d42ec2f077..97867baa9d 100644 --- a/isotope/inc/util.h +++ b/isotope/inc/util.h @@ -2,6 +2,8 @@ #ifndef ISOTOPE_UTILITY_H_ #define ISOTOPE_UTILITY_H_ +#include +#include #include #include #include @@ -9,6 +11,8 @@ #include #include +#include "types.pb.h" + template struct FusionTypeName; @@ -59,7 +63,48 @@ template struct overloaded : Ts... { using Ts::operator()...; }; + template overloaded(Ts...) -> overloaded; +template +struct has_name : std::false_type {}; + +template +struct has_name()->name())>> : std::true_type {}; + +template +struct has_entity_token : std::false_type {}; + +template +struct has_entity_token()->entityToken())>> : std::true_type {}; + +template +struct has_id : std::false_type {}; + +template +struct has_id()->id())>> : std::true_type {}; + +template +mirabuf::Info create_info_from_fus_obj(const FusObjPtr& obj) { + mirabuf::Info info; + + // The python exporter sets all version numbers to 5. + // This version number can be used to differentiate between robot exports from + // the C++ and python exporters respectively. + info.set_version(1); + + if constexpr (has_name::value) { + info.set_name(obj->name()); + } + + if constexpr (has_entity_token::value) { + info.set_guid(obj->entityToken()); + } else if constexpr (has_id::value) { + info.set_guid(obj->id()); + } + + return info; +} + #endif // ISOTOPE_UTILITY_H_ diff --git a/isotope/src/components.cpp b/isotope/src/components.cpp index 94f9d76075..7677332550 100644 --- a/isotope/src/components.cpp +++ b/isotope/src/components.cpp @@ -1,12 +1,14 @@ #include "components.h" -#include +#include + #include #include "assembly.pb.h" +#include "joint.pb.h" #include "types.pb.h" -#include "Core/Geometry/Matrix3D.h" +#include "util.h" namespace { @@ -47,9 +49,8 @@ mirabuf::TriangleMesh map_b_rep_body(const adsk::core::Ptrset_name(body->name()); - // TODO: Info guid - mesh.mutable_info()->set_version(1); + mesh.mutable_info()->CopyFrom(create_info_from_fus_obj(body)); + mesh.set_has_volume(true); std::vector coords = fus_mesh->nodeCoordinatesAsFloat(); @@ -71,7 +72,7 @@ mirabuf::TriangleMesh map_mesh_body(const adsk::core::PtrdisplayMesh(); mirabuf::TriangleMesh mesh; - // TODO: Info crap (this is getting annoying) + mesh.mutable_info()->CopyFrom(create_info_from_fus_obj(body)); mesh.set_has_volume(true); std::vector coords = fus_mesh->nodeCoordinatesAsFloat(); @@ -99,9 +100,7 @@ mirabuf::Parts map_all_parts( components->copyTo(std::back_inserter(fusion_components)); for (const auto& component : fusion_components) { auto& part = (*parts.mutable_part_definitions())[component->id()]; - part.mutable_info()->set_name(component->name()); - part.mutable_info()->set_guid(component->id()); - part.mutable_info()->set_version(1); + part.mutable_info()->CopyFrom(create_info_from_fus_obj(component)); part.set_dynamic(true); if (auto props = component->physicalProperties()) { @@ -116,9 +115,7 @@ mirabuf::Parts map_all_parts( } auto& part_body = *part.mutable_bodies()->Add(); - part_body.mutable_info()->set_name(body->name()); - // TODO: guid - part_body.mutable_info()->set_version(1); + part_body.mutable_info()->CopyFrom(create_info_from_fus_obj(body)); part_body.mutable_triangle_mesh()->CopyFrom(map_b_rep_body(body)); if (auto appearances = materials.appearances(); // TODO: Replace the parameter @@ -137,8 +134,7 @@ mirabuf::Parts map_all_parts( } auto& part_body = *part.mutable_bodies()->Add(); - part_body.mutable_info()->set_name(body->name()); - part_body.mutable_info()->set_version(1); + part_body.mutable_info()->CopyFrom(create_info_from_fus_obj(body)); part_body.mutable_triangle_mesh()->CopyFrom(map_mesh_body(body)); if (auto appearances = materials.appearances(); // TODO: Replace the parameter @@ -175,9 +171,9 @@ mirabuf::Node parse_child_occurrence( mirabuf::Node node; node.set_value(occurrence->component()->id()); - // TODO: Info stuff auto& part = (*parts->mutable_part_instances())[occurrence->component()->id()]; + part.mutable_info()->CopyFrom(create_info_from_fus_obj(occurrence)); if (occurrence->appearance()) { part.set_appearance(occurrence->appearance()->id()); } @@ -214,7 +210,8 @@ mirabuf::Node parse_component_root(const adsk::core::Ptrid()); // TODO: Info stuff - auto& part = (*parts->mutable_part_instances())[component->id()]; + auto& part = (*parts->mutable_part_instances())[component->id()]; + part.mutable_info()->CopyFrom(create_info_from_fus_obj(component)); auto& part_defs = parts->part_definitions(); if (part_defs.find(component->id()) != part_defs.end()) { part.set_part_definition_reference(component->id()); diff --git a/isotope/src/joints.cpp b/isotope/src/joints.cpp index 7f1bafb685..9c226539f9 100644 --- a/isotope/src/joints.cpp +++ b/isotope/src/joints.cpp @@ -504,9 +504,13 @@ std::pair populate_joints( auto& joint_definition_ground = (*joints.mutable_joint_definitions())["grounded"]; joint_definition_ground.mutable_info()->set_name("grounded"); + joint_definition_ground.mutable_info()->set_guid("grounded-def-guid"); + joint_definition_ground.mutable_info()->set_version(1); auto& joint_instance_ground = (*joints.mutable_joint_instances())["grounded"]; joint_instance_ground.mutable_info()->set_name("grounded"); + joint_instance_ground.mutable_info()->set_guid("grounded-inst-guid"); + joint_instance_ground.mutable_info()->set_version(1); joint_instance_ground.set_joint_reference(joint_definition_ground.info().guid()); @@ -526,13 +530,12 @@ std::pair populate_joints( } auto& signal = (*signals.mutable_signal_map())[joint->entityToken()]; - signal.mutable_info()->set_name(joint->entityToken()); - signal.mutable_info()->set_guid(joint->entityToken()); - signal.mutable_info()->set_version(1); + signal.mutable_info()->CopyFrom(create_info_from_fus_obj(joint)); signal.set_io(mirabuf::signal::IOType::OUTPUT); signal.set_device_type(mirabuf::signal::DeviceType::PWM); auto& joint_instance = (*joints.mutable_joint_instances())[joint->entityToken()]; + joint_instance.mutable_info()->CopyFrom(create_info_from_fus_obj(joint)); joint_instance.set_signal_reference(signal.info().guid()); joint_instance.set_parent_part(joint->occurrenceOne()->name()); joint_instance.set_child_part(joint->occurrenceTwo()->name()); @@ -541,9 +544,7 @@ std::pair populate_joints( auto& joint_definition = (*joints.mutable_joint_definitions())[joint->entityToken()]; joint_definition.set_motor_reference(signal.info().guid()); - joint_definition.mutable_info()->set_name(joint->entityToken()); - joint_definition.mutable_info()->set_guid(joint->entityToken()); - joint_definition.mutable_info()->set_version(1); + joint_definition.mutable_info()->CopyFrom(create_info_from_fus_obj(joint)); auto joint_origin = get_joint_origin(joint); @@ -559,7 +560,8 @@ std::pair populate_joints( joint_definition.set_break_magnitude(0.0f); - auto& motor = (*joints.mutable_motor_definitions())[joint->entityToken()]; + auto& motor = (*joints.mutable_motor_definitions())[joint->entityToken()]; + motor.mutable_info()->CopyFrom(create_info_from_fus_obj(joint)); auto simple_motor = motor.mutable_simple_motor(); // These are values I just chose on a whim, they need to be checked and changed to make sure diff --git a/isotope/src/materials.cpp b/isotope/src/materials.cpp index eccab7f512..463f8f7843 100644 --- a/isotope/src/materials.cpp +++ b/isotope/src/materials.cpp @@ -7,6 +7,8 @@ #include "material.pb.h" +#include "util.h" + namespace { mirabuf::material::Appearance default_appearance() { @@ -70,6 +72,7 @@ mirabuf::material::Materials map_all_materials(const adsk::core::Ptrid()]; new_appearance = map_appearance(appearance); + new_appearance.mutable_info()->CopyFrom(create_info_from_fus_obj(appearance)); } std::vector> physical_materials; @@ -77,6 +80,7 @@ mirabuf::material::Materials map_all_materials(const adsk::core::Ptrid()]; new_physical_material = map_physical_material(material); + new_physical_material.mutable_info()->CopyFrom(create_info_from_fus_obj(material)); } return materials; diff --git a/isotope/src/parser.cpp b/isotope/src/parser.cpp index 01b01214a9..459a8bca7a 100644 --- a/isotope/src/parser.cpp +++ b/isotope/src/parser.cpp @@ -18,6 +18,7 @@ #include "components.h" #include "joints.h" #include "materials.h" +#include "util.h" void export_design(const GlobalContext& gctx) { assert(gctx.isValid()); @@ -25,8 +26,7 @@ void export_design(const GlobalContext& gctx) { auto design = document->query()->design(); mirabuf::Assembly assembly; - assembly.mutable_info()->set_name(design->rootComponent()->name()); - assembly.mutable_info()->set_version(1); + assembly.mutable_info()->CopyFrom(create_info_from_fus_obj(design->rootComponent())); assembly.mutable_info()->set_guid(design->parentDocument()->name()); // Determines if the exported design should be treated as a robot or field From 2612006717e0a0fda1a7b3aa9e4d87efad90e520 Mon Sep 17 00:00:00 2001 From: BrandonPacewic <92102436+BrandonPacewic@users.noreply.github.com> Date: Tue, 19 Aug 2025 10:10:37 -0700 Subject: [PATCH 18/22] refactor(isotope): remove multiple namespaces --- isotope/src/components.cpp | 109 ++++++++++++++++++------------------- 1 file changed, 53 insertions(+), 56 deletions(-) diff --git a/isotope/src/components.cpp b/isotope/src/components.cpp index 7677332550..b42d97789d 100644 --- a/isotope/src/components.cpp +++ b/isotope/src/components.cpp @@ -90,6 +90,59 @@ mirabuf::TriangleMesh map_mesh_body(const adsk::core::Ptr get_matrix_world(const adsk::core::Ptr& occurrence) { + if (!occurrence) { + return nullptr; + } + + auto matrix = occurrence->transform2()->copy(); + auto next_occurrence = occurrence; + while (next_occurrence->assemblyContext()) { + matrix->transformBy(next_occurrence->assemblyContext()->transform2()); + next_occurrence = next_occurrence->assemblyContext(); + } + + return matrix; +} + +mirabuf::Node parse_child_occurrence( + const adsk::core::Ptr& occurrence, mirabuf::Parts* parts) { + assert(occurrence->isLightBulbOn()); + + mirabuf::Node node; + node.set_value(occurrence->component()->id()); + + auto& part = (*parts->mutable_part_instances())[occurrence->component()->id()]; + part.mutable_info()->CopyFrom(create_info_from_fus_obj(occurrence)); + if (occurrence->appearance()) { + part.set_appearance(occurrence->appearance()->id()); + } + + if (auto material = occurrence->component()->material()) { + part.set_physical_material(material->id()); + } + + auto transform_array = occurrence->transform()->asArray(); + part.mutable_transform()->mutable_spatial_matrix()->Add(transform_array.begin(), transform_array.end()); + + auto world_transform = get_matrix_world(occurrence)->asArray(); + part.mutable_global_transform()->mutable_spatial_matrix()->Add(world_transform.begin(), world_transform.end()); + + // final recursive step to parse child occurrences + std::vector> child_occurrences; + occurrence->childOccurrences()->copyTo(std::back_inserter(child_occurrences)); + for (const auto& child_occurrence : child_occurrences) { + if (!child_occurrence->isLightBulbOn()) { + continue; + } + + auto child_node = parse_child_occurrence(child_occurrence, parts); + node.mutable_children()->Add()->CopyFrom(child_node); + } + + return node; +} + } // namespace mirabuf::Parts map_all_parts( @@ -149,62 +202,6 @@ mirabuf::Parts map_all_parts( return parts; } -namespace { -adsk::core::Ptr get_matrix_world(const adsk::core::Ptr& occurrence) { - if (!occurrence) { - return nullptr; - } - - auto matrix = occurrence->transform2()->copy(); - auto next_occurrence = occurrence; - while (next_occurrence->assemblyContext()) { - matrix->transformBy(next_occurrence->assemblyContext()->transform2()); - next_occurrence = next_occurrence->assemblyContext(); - } - - return matrix; -} - -mirabuf::Node parse_child_occurrence( - const adsk::core::Ptr& occurrence, mirabuf::Parts* parts) { - assert(occurrence->isLightBulbOn()); - - mirabuf::Node node; - node.set_value(occurrence->component()->id()); - - auto& part = (*parts->mutable_part_instances())[occurrence->component()->id()]; - part.mutable_info()->CopyFrom(create_info_from_fus_obj(occurrence)); - if (occurrence->appearance()) { - part.set_appearance(occurrence->appearance()->id()); - } - - if (auto material = occurrence->component()->material()) { - part.set_physical_material(material->id()); - } - - auto transform_array = occurrence->transform()->asArray(); - part.mutable_transform()->mutable_spatial_matrix()->Add(transform_array.begin(), transform_array.end()); - - auto world_transform = get_matrix_world(occurrence)->asArray(); - part.mutable_global_transform()->mutable_spatial_matrix()->Add(world_transform.begin(), world_transform.end()); - - // final recursive step to parse child occurrences - std::vector> child_occurrences; - occurrence->childOccurrences()->copyTo(std::back_inserter(child_occurrences)); - for (const auto& child_occurrence : child_occurrences) { - if (!child_occurrence->isLightBulbOn()) { - continue; - } - - auto child_node = parse_child_occurrence(child_occurrence, parts); - node.mutable_children()->Add()->CopyFrom(child_node); - } - - return node; -} - -} // namespace - mirabuf::Node parse_component_root(const adsk::core::Ptr& component, mirabuf::Parts* parts) { mirabuf::Node root_node; root_node.set_value(component->id()); From 49f54cc0714c56591d747dcdce4a0eea83f51eaa Mon Sep 17 00:00:00 2001 From: BrandonPacewic <92102436+BrandonPacewic@users.noreply.github.com> Date: Tue, 19 Aug 2025 15:49:14 -0700 Subject: [PATCH 19/22] feat(isotope): unify component & occurrence guids --- isotope/inc/util.h | 19 +++++++++++++----- isotope/src/components.cpp | 41 ++++++++++++++++++++++++++++++-------- isotope/src/joints.cpp | 13 ++++++------ isotope/src/util.cpp | 20 +++++++++++++++++++ 4 files changed, 74 insertions(+), 19 deletions(-) create mode 100644 isotope/src/util.cpp diff --git a/isotope/inc/util.h b/isotope/inc/util.h index 97867baa9d..540d32490f 100644 --- a/isotope/inc/util.h +++ b/isotope/inc/util.h @@ -4,9 +4,11 @@ #include #include +#include #include #include #include +#include #include #include @@ -86,7 +88,7 @@ template struct has_id()->id())>> : std::true_type {}; template -mirabuf::Info create_info_from_fus_obj(const FusObjPtr& obj) { +mirabuf::Info create_info_from_fus_obj(const FusObjPtr& obj, const std::string& override_guid = "") { mirabuf::Info info; // The python exporter sets all version numbers to 5. @@ -98,13 +100,20 @@ mirabuf::Info create_info_from_fus_obj(const FusObjPtr& obj) { info.set_name(obj->name()); } - if constexpr (has_entity_token::value) { - info.set_guid(obj->entityToken()); - } else if constexpr (has_id::value) { - info.set_guid(obj->id()); + if (!override_guid.length()) { + if constexpr (has_entity_token::value) { + info.set_guid(obj->entityToken()); + } else if constexpr (has_id::value) { + info.set_guid(obj->id()); + } + } else { + info.set_guid(override_guid); } return info; } +std::string guid_component(const adsk::core::Ptr& component); +std::string guid_occurrence(const adsk::core::Ptr& occurrence); + #endif // ISOTOPE_UTILITY_H_ diff --git a/isotope/src/components.cpp b/isotope/src/components.cpp index b42d97789d..3c66d3029a 100644 --- a/isotope/src/components.cpp +++ b/isotope/src/components.cpp @@ -110,18 +110,33 @@ mirabuf::Node parse_child_occurrence( assert(occurrence->isLightBulbOn()); mirabuf::Node node; - node.set_value(occurrence->component()->id()); - auto& part = (*parts->mutable_part_instances())[occurrence->component()->id()]; + // TODO: Really explicit typing for this sort of thing would be great. + const std::string map_constant = guid_occurrence(occurrence); + node.set_value(map_constant); + + if (parts->part_instances().find(map_constant) != parts->part_instances().end()) { + assert(false); + } + + auto& part = (*parts->mutable_part_instances())[map_constant]; part.mutable_info()->CopyFrom(create_info_from_fus_obj(occurrence)); if (occurrence->appearance()) { - part.set_appearance(occurrence->appearance()->id()); + part.set_appearance(occurrence->appearance()->id()); // TODO: Check if this is correct. + } else { + part.set_appearance("default"); } if (auto material = occurrence->component()->material()) { part.set_physical_material(material->id()); } + auto& part_defs = parts->part_definitions(); + const std::string component_ref = guid_component(occurrence->component()); + if (part_defs.find(component_ref) != part_defs.end()) { + part.set_part_definition_reference(component_ref); + } + auto transform_array = occurrence->transform()->asArray(); part.mutable_transform()->mutable_spatial_matrix()->Add(transform_array.begin(), transform_array.end()); @@ -152,7 +167,12 @@ mirabuf::Parts map_all_parts( std::vector> fusion_components; components->copyTo(std::back_inserter(fusion_components)); for (const auto& component : fusion_components) { - auto& part = (*parts.mutable_part_definitions())[component->id()]; + const std::string component_ref = guid_component(component); + if (parts.part_definitions().find(component_ref) != parts.part_definitions().end()) { + assert(false); + } + + auto& part = (*parts.mutable_part_definitions())[component_ref]; part.mutable_info()->CopyFrom(create_info_from_fus_obj(component)); part.set_dynamic(true); @@ -204,14 +224,19 @@ mirabuf::Parts map_all_parts( mirabuf::Node parse_component_root(const adsk::core::Ptr& component, mirabuf::Parts* parts) { mirabuf::Node root_node; - root_node.set_value(component->id()); + const std::string map_constant = guid_component(component); + root_node.set_value(map_constant); // TODO: Info stuff - auto& part = (*parts->mutable_part_instances())[component->id()]; + if (parts->part_instances().find(map_constant) != parts->part_instances().end()) { + assert(false); + } + + auto& part = (*parts->mutable_part_instances())[map_constant]; part.mutable_info()->CopyFrom(create_info_from_fus_obj(component)); auto& part_defs = parts->part_definitions(); - if (part_defs.find(component->id()) != part_defs.end()) { - part.set_part_definition_reference(component->id()); + if (part_defs.find(map_constant) != part_defs.end()) { + part.set_part_definition_reference(map_constant); } std::vector> child_occurrences; diff --git a/isotope/src/joints.cpp b/isotope/src/joints.cpp index 9c226539f9..f6764b7967 100644 --- a/isotope/src/joints.cpp +++ b/isotope/src/joints.cpp @@ -46,8 +46,8 @@ mirabuf::joint::RigidGroup map_rigid_group( mirabuf::joint::RigidGroup group; std::string group_name = "group_" + joint->occurrenceOne()->name() + "_" + joint->occurrenceTwo()->name(); group.set_name(group_name); - group.add_occurrences(joint->occurrenceOne()->name()); - group.add_occurrences(joint->occurrenceTwo()->name()); + group.add_occurrences(guid_occurrence(joint->occurrenceOne())); + group.add_occurrences(guid_occurrence(joint->occurrenceTwo())); return group; } @@ -337,7 +337,7 @@ std::optional create_tree_parts( } mirabuf::Node node; - node.set_value(occurrence_node->data->name()); + node.set_value(guid_occurrence(occurrence_node->data)); for (auto edge : occurrence_node->edges) { auto dyn_node = std::dynamic_pointer_cast(edge->node); auto child_node = create_tree_parts(dyn_node, edge->relationship); @@ -504,7 +504,8 @@ std::pair populate_joints( auto& joint_definition_ground = (*joints.mutable_joint_definitions())["grounded"]; joint_definition_ground.mutable_info()->set_name("grounded"); - joint_definition_ground.mutable_info()->set_guid("grounded-def-guid"); + // TODO: Add comment + joint_definition_ground.mutable_info()->set_guid("grounded"); joint_definition_ground.mutable_info()->set_version(1); auto& joint_instance_ground = (*joints.mutable_joint_instances())["grounded"]; @@ -537,8 +538,8 @@ std::pair populate_joints( auto& joint_instance = (*joints.mutable_joint_instances())[joint->entityToken()]; joint_instance.mutable_info()->CopyFrom(create_info_from_fus_obj(joint)); joint_instance.set_signal_reference(signal.info().guid()); - joint_instance.set_parent_part(joint->occurrenceOne()->name()); - joint_instance.set_child_part(joint->occurrenceTwo()->name()); + joint_instance.set_parent_part(guid_occurrence(joint->occurrenceOne())); + joint_instance.set_child_part(guid_occurrence(joint->occurrenceTwo())); // TODO: Wheel logic should go here diff --git a/isotope/src/util.cpp b/isotope/src/util.cpp new file mode 100644 index 0000000000..613744f986 --- /dev/null +++ b/isotope/src/util.cpp @@ -0,0 +1,20 @@ +#include "util.h" + +#include +#include + +std::string guid_component(const adsk::core::Ptr& component) { + std::string output; + output += component->entityToken(); + output += "_"; + output += component->id(); + return output; +} + +std::string guid_occurrence(const adsk::core::Ptr& occurrence) { + std::string output; + output += occurrence->entityToken(); + output += "_"; + output += guid_component(occurrence->component()); + return output; +} From b19ae9f6a0422f68a739b5e8b4d83d76fb472fe0 Mon Sep 17 00:00:00 2001 From: BrandonPacewic <92102436+BrandonPacewic@users.noreply.github.com> Date: Wed, 20 Aug 2025 09:18:16 -0700 Subject: [PATCH 20/22] fix(isotope): correct map constants for components and parts --- isotope/src/components.cpp | 6 +++--- isotope/src/joints.cpp | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/isotope/src/components.cpp b/isotope/src/components.cpp index 3c66d3029a..11304e81a9 100644 --- a/isotope/src/components.cpp +++ b/isotope/src/components.cpp @@ -120,7 +120,7 @@ mirabuf::Node parse_child_occurrence( } auto& part = (*parts->mutable_part_instances())[map_constant]; - part.mutable_info()->CopyFrom(create_info_from_fus_obj(occurrence)); + part.mutable_info()->CopyFrom(create_info_from_fus_obj(occurrence, map_constant)); if (occurrence->appearance()) { part.set_appearance(occurrence->appearance()->id()); // TODO: Check if this is correct. } else { @@ -173,7 +173,7 @@ mirabuf::Parts map_all_parts( } auto& part = (*parts.mutable_part_definitions())[component_ref]; - part.mutable_info()->CopyFrom(create_info_from_fus_obj(component)); + part.mutable_info()->CopyFrom(create_info_from_fus_obj(component, component_ref)); part.set_dynamic(true); if (auto props = component->physicalProperties()) { @@ -233,7 +233,7 @@ mirabuf::Node parse_component_root(const adsk::core::Ptrmutable_part_instances())[map_constant]; - part.mutable_info()->CopyFrom(create_info_from_fus_obj(component)); + part.mutable_info()->CopyFrom(create_info_from_fus_obj(component, map_constant)); auto& part_defs = parts->part_definitions(); if (part_defs.find(map_constant) != part_defs.end()) { part.set_part_definition_reference(map_constant); diff --git a/isotope/src/joints.cpp b/isotope/src/joints.cpp index f6764b7967..fcb563f7cb 100644 --- a/isotope/src/joints.cpp +++ b/isotope/src/joints.cpp @@ -538,6 +538,7 @@ std::pair populate_joints( auto& joint_instance = (*joints.mutable_joint_instances())[joint->entityToken()]; joint_instance.mutable_info()->CopyFrom(create_info_from_fus_obj(joint)); joint_instance.set_signal_reference(signal.info().guid()); + joint_instance.set_joint_reference(joint_instance.info().guid()); joint_instance.set_parent_part(guid_occurrence(joint->occurrenceOne())); joint_instance.set_child_part(guid_occurrence(joint->occurrenceTwo())); From 0937bf6986273a9d0bfc7e3ab5bbcb237867f610 Mon Sep 17 00:00:00 2001 From: BrandonPacewic <92102436+BrandonPacewic@users.noreply.github.com> Date: Wed, 20 Aug 2025 09:46:03 -0700 Subject: [PATCH 21/22] feat(isotope): protobuf submodule build --- .gitmodules | 3 +++ isotope/CMakeLists.txt | 21 ++++++++++----------- protobuf | 1 + 3 files changed, 14 insertions(+), 11 deletions(-) create mode 160000 protobuf diff --git a/.gitmodules b/.gitmodules index 00d28c789f..7b6a09edab 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "jolt"] path = jolt url = https://github.com/azaleacolburn/JoltPhysics.js.git +[submodule "protobuf"] + path = protobuf + url = https://github.com/protocolbuffers/protobuf.git diff --git a/isotope/CMakeLists.txt b/isotope/CMakeLists.txt index 98a859c597..ecd4748e65 100644 --- a/isotope/CMakeLists.txt +++ b/isotope/CMakeLists.txt @@ -49,21 +49,20 @@ set(protobuf_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) set(protobuf_BUILD_CONFORMANCE OFF CACHE BOOL "" FORCE) set(protobuf_INSTALL OFF CACHE BOOL "" FORCE) -include(FetchContent) -FetchContent_Declare( - protobuf - GIT_REPOSITORY https://github.com/protocolbuffers/protobuf.git - GIT_TAG v23.0 -) -FetchContent_MakeAvailable(protobuf) -find_package(Protobuf REQUIRED) -set(Protobuf_PROTOC_EXECUTABLE $) +set(GOOGLE_PROTOBUF_SUBMODULE_DIR "${CMAKE_CURRENT_LIST_DIR}/../protobuf") +if(NOT EXISTS "${GOOGLE_PROTOBUF_SUBMODULE_DIR}/.git") + message(FATAL_ERROR "google-protobuf is not checked out; make sure to run\n\tgit submodule update --init ../protobuf") +endif() +add_subdirectory(${GOOGLE_PROTOBUF_SUBMODULE_DIR} "${CMAKE_BINARY_DIR}/protobuf") + +set(Protobuf_PROTOC_EXECUTABLE $) target_link_libraries(${PROJECT_NAME} PRIVATE protobuf::libprotobuf) set(PROTO_GEN_DIR "${CMAKE_CURRENT_BINARY_DIR}/protobuf_gen") +set(MIRABUF_SUBMODULE_DIR "${CMAKE_CURRENT_LIST_DIR}/../mirabuf") file(MAKE_DIRECTORY ${PROTO_GEN_DIR}) -file(GLOB PROTO_FILES "${CMAKE_CURRENT_LIST_DIR}/../mirabuf/*.proto") +file(GLOB PROTO_FILES "${MIRABUF_SUBMODULE_DIR}/*.proto") set(PROTO_SRCS "") set(PROTO_HDRS "") @@ -77,7 +76,7 @@ add_custom_command( OUTPUT ${PROTO_SRCS} ${PROTO_HDRS} COMMAND ${Protobuf_PROTOC_EXECUTABLE} --cpp_out=${PROTO_GEN_DIR} - -I ${CMAKE_CURRENT_LIST_DIR}/../mirabuf + -I ${MIRABUF_SUBMODULE_DIR} ${PROTO_FILES} DEPENDS ${PROTO_FILES} COMMENT "Generating protobuf sources" diff --git a/protobuf b/protobuf new file mode 160000 index 0000000000..ad4a58499d --- /dev/null +++ b/protobuf @@ -0,0 +1 @@ +Subproject commit ad4a58499dc00c6e8eb51d79b31e714fd6043c74 From ec3093f7a6a6bdac53ec020e1ea8bc8388f35f5f Mon Sep 17 00:00:00 2001 From: BrandonPacewic <92102436+BrandonPacewic@users.noreply.github.com> Date: Wed, 20 Aug 2025 11:32:24 -0700 Subject: [PATCH 22/22] feat(isotope): appearance & material property parsing --- isotope/inc/materials.h | 2 - isotope/src/materials.cpp | 144 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 137 insertions(+), 9 deletions(-) diff --git a/isotope/inc/materials.h b/isotope/inc/materials.h index 84b2c2e358..01a192ceb0 100644 --- a/isotope/inc/materials.h +++ b/isotope/inc/materials.h @@ -4,8 +4,6 @@ #include -#include - #include "material.pb.h" mirabuf::material::Materials map_all_materials(const adsk::core::Ptr& design_appearances, diff --git a/isotope/src/materials.cpp b/isotope/src/materials.cpp index 463f8f7843..55f5ab2d65 100644 --- a/isotope/src/materials.cpp +++ b/isotope/src/materials.cpp @@ -1,6 +1,13 @@ #include "materials.h" +#include +#include +#include +#include +#include #include +#include +#include #include #include @@ -14,7 +21,7 @@ namespace { mirabuf::material::Appearance default_appearance() { mirabuf::material::Appearance appearance; appearance.mutable_info()->set_name("Default Appearance"); - appearance.mutable_info()->set_guid("default-appearance-guid"); + appearance.mutable_info()->set_guid("default"); appearance.mutable_info()->set_version(1); appearance.set_roughness(0.5f); appearance.set_metallic(0.5f); @@ -30,10 +37,107 @@ mirabuf::material::Appearance default_appearance() { mirabuf::material::Appearance map_appearance(const adsk::core::Ptr& appearance) { mirabuf::material::Appearance new_appearance = default_appearance(); - new_appearance.mutable_info()->set_name(appearance->name()); - new_appearance.mutable_info()->set_guid(appearance->id()); + new_appearance.mutable_info()->CopyFrom(create_info_from_fus_obj(appearance)); + + new_appearance.set_roughness(0.9f); + new_appearance.set_metallic(0.3f); + new_appearance.set_specular(0.5f); + + new_appearance.mutable_albedo()->set_r(10); + new_appearance.mutable_albedo()->set_g(10); + new_appearance.mutable_albedo()->set_b(10); + new_appearance.mutable_albedo()->set_a(127); + + auto properties = appearance->appearanceProperties(); + if (auto roughness_property = properties->itemById("surface_roughness")) { + new_appearance.set_roughness(dynamic_cast(roughness_property.get())->value()); + } + + adsk::core::Ptr model_item = properties->itemById("interior_model"); + if (!model_item) { + return new_appearance; + } + + adsk::core::Ptr base_color = nullptr; + + int mat_model_type = model_item->value(); + switch (mat_model_type) { + case 0: { + if (auto reflectance_property = properties->itemById("opaque_f0")) { + new_appearance.set_metallic( + dynamic_cast(reflectance_property.get())->value()); + } + + adsk::core::Ptr color = properties->itemById("opaque_albedo"); + if (color && color->value()) { + base_color = color->value(); + base_color->opacity(255); + } + + break; + } + case 1: { + new_appearance.set_metallic(0.8); + + adsk::core::Ptr color = properties->itemById("opaque_albedo"); + if (color && color->value()) { + base_color = color->value(); + base_color->opacity(255); + } + + break; + } + case 2: { + adsk::core::Ptr color = properties->itemById("layered_diffuse"); + if (color && color->value()) { + base_color = color->value(); + base_color->opacity(255); + } + + break; + } + case 3: { + adsk::core::Ptr color = properties->itemById("layered_diffuse"); + adsk::core::Ptr transparent_distance = + properties->itemById("transparent_distance"); + + constexpr float OPACITY_RAMPING_CONSTANT = 14.0f; + float opacity = + (255.0f * transparent_distance->value()) / (transparent_distance->value() + OPACITY_RAMPING_CONSTANT); + if (opacity > 255) { + opacity = 255; + } else if (opacity < 0) { + opacity = 0; + } + + if (color && color->value()) { + base_color = color->value(); + base_color->opacity(static_cast(opacity)); + } + break; + } + } + + if (base_color) { + new_appearance.mutable_albedo()->set_r(base_color->red()); + new_appearance.mutable_albedo()->set_g(base_color->green()); + new_appearance.mutable_albedo()->set_b(base_color->blue()); + new_appearance.mutable_albedo()->set_a(base_color->opacity()); + } else { + for (auto prop : appearance->appearanceProperties()) { + if (prop->name() == "Color") { + auto color_property = dynamic_cast(prop.get()); + if (color_property->value() && color_property->id() != "surface_albedo") { + new_appearance.mutable_albedo()->set_r(color_property->value()->red()); + new_appearance.mutable_albedo()->set_g(color_property->value()->green()); + new_appearance.mutable_albedo()->set_b(color_property->value()->blue()); + new_appearance.mutable_albedo()->set_a(color_property->value()->opacity()); + break; + } + } + } + } - // TODO - Map all other appearance properties return new_appearance; } @@ -51,12 +155,38 @@ mirabuf::material::PhysicalMaterial default_physical_material() { return physical_material; } +#define SET_FROM_PROP(props, id, obj, method) \ + do { \ + if (auto p = (props)->itemById(id)) { \ + if (auto fp = dynamic_cast(p.get())) { \ + (obj)->method(fp->value()); \ + } \ + } \ + } while (0) + mirabuf::material::PhysicalMaterial map_physical_material(const adsk::core::Ptr& material) { mirabuf::material::PhysicalMaterial new_physical_material = default_physical_material(); - new_physical_material.mutable_info()->set_name(material->name()); - new_physical_material.mutable_info()->set_guid(material->id()); + new_physical_material.mutable_info()->CopyFrom(create_info_from_fus_obj(material)); + + new_physical_material.set_deformable(false); + new_physical_material.set_mattype(mirabuf::material::PhysicalMaterial_MaterialType_METAL); + + new_physical_material.set_dynamic_friction(0.5f); + new_physical_material.set_static_friction(0.5f); + new_physical_material.set_restitution(0.5f); + + auto mat_props = material->materialProperties(); + auto mechanical_properties = new_physical_material.mutable_mechanical(); + auto strength_properties = new_physical_material.mutable_strength(); + + SET_FROM_PROP(mat_props, "structural_Young_modulus", mechanical_properties, set_young_mod); + SET_FROM_PROP(mat_props, "structural_Poisson_ratio", mechanical_properties, set_poisson_ratio); + SET_FROM_PROP(mat_props, "structural_Shear_modulus", mechanical_properties, set_shear_mod); + SET_FROM_PROP(mat_props, "structural_Density", mechanical_properties, set_density); + SET_FROM_PROP(mat_props, "structural_Damping_coefficient", mechanical_properties, set_damping_coefficient); + SET_FROM_PROP(mat_props, "structural_Minimum_yield_stress", strength_properties, set_yield_strength); + SET_FROM_PROP(mat_props, "structural_Minimum_tensile_strength", strength_properties, set_tensile_strength); - // TODO - Map all other physical material properties return new_physical_material; }